-
Notifications
You must be signed in to change notification settings - Fork 281
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: improved documentation and examples
- Add migration guide for v19 - Improved README.md - Migrated examples to use inject()
- Loading branch information
1 parent
7d76f62
commit 2664d0c
Showing
5 changed files
with
326 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,313 @@ | ||
# Migration Guide: Keycloak Angular v19 | ||
|
||
Keycloak Angular v19 introduces significant changes and improvements that align with modern Angular practices and address longstanding issues. | ||
|
||
In previous versions, wrapping the `keycloak-js` client introduced challenges such as maintaining outdated Promise mappings, tightly coupled configurations, and limited flexibility with interceptors and events. Additionally, the evolution of Angular towards a functional paradigm called for a comprehensive refactor. | ||
|
||
Keycloak Angular v19 embraces Angular's functional design and introduces a range of enhancements: | ||
|
||
- The new `provideKeycloak` function simplifies client initialization at the application startup, leveraging Angular's `provideAppInitializer`. See [Bootstrapping Keycloak with `provideKeycloak`](#bootstrapping-keycloak-with-providekeycloak). | ||
- Direct usage of the `keycloak-js` client eliminates the need for a wrapper service. See [Migrating from `KeycloakService`](#migrating-from-keycloakservice). | ||
- Refactored token interceptors now rely on injection tokens for configuration, promoting modularity and reducing complexity. See [Migrating from `KeycloakBearerInterceptor`](#migrating-from-keycloakbearerinterceptor). | ||
- The legacy `KeycloakAuthGuard` has been replaced with higher-order functions for creating custom route guards. See [Migrating from `KeycloakAuthGuard`](#migrating-from-keycloakauthguard). | ||
- Event handling has transitioned from RxJS Subjects to Angular Signals, improving clarity and integration. See [Migrating from `KeycloakEvents` RxJS Subject](#migrating-from-keycloakevents-rxjs-subject). | ||
|
||
These changes enable better scalability, configurability, and alignment with Angular’s evolving ecosystem. | ||
|
||
--- | ||
|
||
- [Bootstrapping Keycloak with `provideKeycloak`](#bootstrapping-keycloak-with-providekeycloak) | ||
- [Migrating from `KeycloakService`](#migrating-from-keycloakservice) | ||
- [Migrating from `KeycloakBearerInterceptor`](#migrating-from-keycloakbearerinterceptor) | ||
- [Migrating from `KeycloakAuthGuard`](#migrating-from-keycloakauthguard) | ||
- [Migrating from `KeycloakEvents` RxJS Subject](#migrating-from-keycloakevents-rxjs-subject) | ||
- [Deprecation of `KeycloakAngularModule`](#deprecation-of-keycloakangularmodule) | ||
|
||
--- | ||
|
||
## Bootstrapping Keycloak with `provideKeycloak` | ||
|
||
The new method for bootstrapping Keycloak in your application uses the `provideKeycloak` function. This approach integrates seamlessly with Angular’s `ApplicationConfig`, allowing for cleaner organization and enhanced flexibility. | ||
|
||
### Example | ||
|
||
```typescript | ||
export const appConfig: ApplicationConfig = { | ||
providers: [ | ||
provideKeycloak({ | ||
config: { | ||
url: 'keycloak-server-url', | ||
realm: 'realm-id', | ||
clientId: 'client-id' | ||
}, | ||
initOptions: { | ||
onLoad: 'check-sso', | ||
silentCheckSsoRedirectUri: `${window.location.origin}/silent-check-sso.html` | ||
} | ||
}), | ||
provideZoneChangeDetection({ eventCoalescing: true }), | ||
provideRouter(routes) | ||
] | ||
}; | ||
``` | ||
|
||
**Key highlights:** | ||
|
||
- `provideKeycloak` initializes the Keycloak client and optionally accepts additional providers for modular configuration. | ||
- Additional providers can help organize configurations, such as loading Keycloak settings from a separate file. | ||
|
||
** Additional Resources** | ||
For more details, refer to the [Keycloak Provider](../provider.md) documentation. | ||
|
||
## Migrating from `KeycloakService` | ||
|
||
The `KeycloakService` wrapper is no longer required in Keycloak Angular v19. Instead, the library provides direct access to the `keycloak-js` client instance using Angular’s `inject()` function. This new approach simplifies usage and provides greater flexibility for future updates to `keycloak-js`. | ||
|
||
### Accessing the Keycloak Client | ||
|
||
You can now inject the `keycloak-js` client instance directly into your components, services, or other Angular classes using `inject(Keycloak)`. This method ensures compatibility with updates to `keycloak-js` while reducing dependency on custom wrappers. | ||
|
||
### Example | ||
|
||
```typescript | ||
import { Component, inject } from '@angular/core'; | ||
import Keycloak from 'keycloak-js'; | ||
|
||
@Component({ | ||
selector: 'app-menu', | ||
templateUrl: './menu.component.html', | ||
styleUrls: ['./menu.component.css'] | ||
}) | ||
export class MenuComponent { | ||
private readonly keycloak = inject(Keycloak); | ||
|
||
login() { | ||
this.keycloak.login(); | ||
} | ||
|
||
logout() { | ||
this.keycloak.logout(); | ||
} | ||
} | ||
``` | ||
|
||
**Key Benefits:** | ||
|
||
- **Direct Access:** The inject(Keycloak) approach gives you direct access to the keycloak-js client without requiring an intermediate wrapper. | ||
- **Future-proofing:** Reduces dependencies on library-specific abstractions, making it easier to adapt to updates or changes in keycloak-js. | ||
- **Simpler Code:** Eliminates the need for additional services, resulting in cleaner and more maintainable code. | ||
|
||
## Migrating from KeycloakBearerInterceptor | ||
|
||
## Migrating from `KeycloakBearerInterceptor` | ||
|
||
In previous versions of Keycloak Angular, the `KeycloakBearerInterceptor` was automatically included when importing the `KeycloakAngularModule`. Starting with version 19, the library no longer adds any interceptors automatically. All interceptor configurations must now be defined declaratively, giving full control to the application to decide the implementation and configuration. | ||
|
||
### Key Changes | ||
|
||
1. **No Automatic Interceptors**: | ||
- In v19, interceptors are not added by default. This ensures that the application explicitly configures them, avoiding unintentional behaviors or security risks. | ||
2. **Deprecation of `ExcludeUrls` Configuration**: | ||
- The previous approach, which allowed excluding certain URLs via the `ExcludeUrls` configuration, has been removed. This change addresses a critical issue where Bearer tokens could unintentionally be sent to external services if the `ExcludeUrls` were improperly configured, potentially compromising security. | ||
- Instead, applications must now specify the URLs where the Authorization Bearer token should be added. This is handled using declarative interceptor configurations. | ||
3. **Decoupled Configurations**: | ||
- Interceptor configurations are no longer tied to the `KeycloakService` configuration. Instead, each interceptor uses specific injection tokens for its configuration, promoting modularity and flexibility. | ||
|
||
### Example | ||
|
||
```typescript | ||
import { provideKeycloak } from 'keycloak-angular'; | ||
import { | ||
createInterceptorCondition, | ||
INCLUDE_BEARER_TOKEN_INTERCEPTOR_CONFIG, | ||
includeBearerTokenInterceptor | ||
} from 'keycloak-angular'; | ||
import { ApplicationConfig, provideRouter } from '@angular/router'; | ||
import { provideZoneChangeDetection, provideHttpClient, withInterceptors } from '@angular/core'; | ||
|
||
const localhostCondition = createInterceptorCondition<IncludeBearerTokenCondition>({ | ||
urlPattern: /^(http:\/\/localhost:8181)(\/.*)?$/i // Match URLs starting with http://localhost:8181 | ||
}); | ||
|
||
export const provideKeycloakAngular = () => | ||
provideKeycloak({ | ||
config: { | ||
realm: 'keycloak-angular-sandbox', | ||
url: 'http://localhost:8080', | ||
clientId: 'keycloak-angular' | ||
}, | ||
initOptions: { | ||
onLoad: 'check-sso', | ||
silentCheckSsoRedirectUri: `${window.location.origin}/silent-check-sso.html`, | ||
redirectUri: window.location.origin + '/' | ||
}, | ||
providers: [ | ||
{ | ||
provide: INCLUDE_BEARER_TOKEN_INTERCEPTOR_CONFIG, | ||
useValue: [localhostCondition] // Specify conditions for adding the Bearer token | ||
} | ||
] | ||
}); | ||
|
||
export const appConfig: ApplicationConfig = { | ||
providers: [ | ||
provideKeycloakAngular(), | ||
provideZoneChangeDetection({ eventCoalescing: true }), | ||
provideRouter(routes), | ||
provideHttpClient(withInterceptors([includeBearerTokenInterceptor])) // Add the configured interceptor | ||
] | ||
}; | ||
``` | ||
|
||
**Key highlights:** | ||
|
||
- Custom Interceptor Configuration: | ||
- The includeBearerTokenInterceptor requires the INCLUDE_BEARER_TOKEN_INTERCEPTOR_CONFIG injection token. | ||
- The configuration accepts an array of conditions, allowing customization based on URL patterns, HTTP methods, or other criteria. | ||
- Security by Design: | ||
- Only explicitly defined URLs will include the Bearer token, reducing the risk of exposing tokens to unintended endpoints. | ||
- Flexibility: | ||
- The declarative approach enables advanced scenarios, such as using different headers or Bearer prefixes for specific endpoints. | ||
|
||
** Additional Resources** | ||
For more details on the available interceptors and their configurations, refer to the [Keycloak HttpClient Interceptors](../interceptors.md) documentation. | ||
|
||
## Migrating from `KeycloakAuthGuard` | ||
|
||
In previous versions of Keycloak Angular, the `KeycloakAuthGuard` was a class that required the application to implement the logic for granting or denying access to routes. This concept remains in version 19, but it has been modernized with the introduction of the `createAuthGuard` higher-order function. This function allows you to create functional route guards using Angular's `CanActivateFn` or `CanActivateChildFn`. | ||
|
||
### Key Changes | ||
|
||
1. **Higher-Order Function**: | ||
- The `KeycloakAuthGuard` class is replaced by `createAuthGuard`, which generates functional guards. | ||
- This aligns with Angular's move toward functional APIs, simplifying guard creation and usage. | ||
2. **Authentication Data**: | ||
- The new `createAuthGuard` function provides an `AuthGuardData` object containing: | ||
- The Keycloak instance. | ||
- The `authenticated` flag. | ||
- The user's granted roles. | ||
3. **Flexibility**: | ||
- Guards can now be implemented as concise and reusable functions. | ||
|
||
### Example | ||
|
||
```typescript | ||
import { ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router'; | ||
import { inject } from '@angular/core'; | ||
import { createAuthGuard, AuthGuardData } from 'keycloak-angular'; | ||
|
||
const isAccessAllowed = async ( | ||
route: ActivatedRouteSnapshot, | ||
_: RouterStateSnapshot, | ||
authData: AuthGuardData | ||
): Promise<boolean | UrlTree> => { | ||
const { authenticated, grantedRoles } = authData; | ||
|
||
const requiredRole = route.data['role']; | ||
if (!requiredRole) { | ||
return false; | ||
} | ||
|
||
const hasRequiredRole = (role: string): boolean => | ||
Object.values(grantedRoles.resourceRoles).some((roles) => roles.includes(role)); | ||
|
||
if (authenticated && hasRequiredRole(requiredRole)) { | ||
return true; | ||
} | ||
|
||
const router = inject(Router); | ||
return router.parseUrl('/forbidden'); | ||
}; | ||
|
||
export const canActivateAuthRole = createAuthGuard<CanActivateFn>(isAccessAllowed); | ||
``` | ||
|
||
**Key Highlights** | ||
|
||
1. **Access Logic:** | ||
- The isAccessAllowed function handles the access control logic based on route data (role), user authentication status, and granted roles. | ||
2. **Injection of Angular Services:** | ||
- Angular's inject() function can be used to access services like Router within the guard logic. | ||
3. **Customizable Behavior:** | ||
- Guards can return a UrlTree (e.g., router.parseUrl('/forbidden')) to redirect unauthorized users. | ||
4. **Reusability:** | ||
- The guard logic (isAccessAllowed) is separated from its implementation, making it reusable across multiple routes. | ||
|
||
## Migrating from `KeycloakEvents` RxJS Subject | ||
|
||
In previous versions of Keycloak Angular, Keycloak events were managed using an RxJS `Subject`. This approach was often cumbersome and error-prone. In version 19, Keycloak events have been refactored to use Angular Signals, providing a cleaner, more reactive, and intuitive API for handling events. | ||
|
||
### Key Changes | ||
|
||
1. **Angular Signals**: | ||
- Keycloak events are now exposed as Angular Signals, offering a more declarative and reactive way to handle client events. | ||
2. **Early Initialization**: | ||
- Keycloak events are instantiated at the start of the application lifecycle, before the Keycloak client is initialized. | ||
3. **Injection Token**: | ||
- The events are injected via the `KEYCLOAK_EVENT_SIGNAL` injection token, ensuring seamless integration and availability wherever needed. | ||
|
||
### Example | ||
|
||
```typescript | ||
import { Component, effect, inject } from '@angular/core'; | ||
import { KEYCLOAK_EVENT_SIGNAL, KeycloakEventType, typeEventArgs, ReadyArgs } from 'keycloak-angular'; | ||
|
||
@Component({ | ||
selector: 'app-menu', | ||
templateUrl: './menu.component.html', | ||
styleUrls: ['./menu.component.css'] | ||
}) | ||
export class MenuComponent { | ||
authenticated = false; | ||
private readonly keycloakSignal = inject(KEYCLOAK_EVENT_SIGNAL); | ||
|
||
constructor() { | ||
effect(() => { | ||
const keycloakEvent = this.keycloakSignal(); | ||
|
||
if (keycloakEvent.type === KeycloakEventType.Ready) { | ||
this.authenticated = typeEventArgs<ReadyArgs>(keycloakEvent.args); | ||
} | ||
|
||
if (keycloakEvent.type === KeycloakEventType.AuthLogout) { | ||
this.authenticated = false; | ||
} | ||
}); | ||
} | ||
} | ||
``` | ||
|
||
**Key Highlights:** | ||
|
||
1. **Declarative Event Handling:** | ||
- The effect function reacts automatically to changes in the Signal, eliminating the need to manually subscribe and unsubscribe. | ||
2. **Improved Readability:** | ||
- Event handling logic is more concise and integrates seamlessly with Angular's reactive programming model. | ||
3. **Strong Typing:** | ||
- The typeEventArgs utility ensures that event arguments are strongly typed, reducing runtime errors and improving developer experience. | ||
4. **Early Event Availability:** | ||
- Events are available immediately after application startup, even before the Keycloak client is fully initialized. | ||
|
||
## Deprecation of `KeycloakAngularModule` | ||
|
||
While the previous implementation of Keycloak Angular remains available in version 19, it is highly recommended to transition to the new architecture as soon as possible. The older implementation will be removed in a future release, likely in Keycloak Angular v20. | ||
|
||
### Key Deprecations | ||
|
||
The following classes have been deprecated in version 19: | ||
|
||
- `KeycloakAngularModule` | ||
- `KeycloakService` | ||
- `KeycloakAuthGuard` | ||
- `KeycloakBearerInterceptor` | ||
|
||
These classes remain available for compatibility purposes, but applications should migrate to the new APIs and approaches outlined in this guide. | ||
|
||
### Renamed Interfaces | ||
|
||
To avoid conflicts with the new implementation, certain interfaces have been renamed: | ||
|
||
| Previous Name | New Name | | ||
| :-----------------: | :-----------------------: | | ||
| `KeycloakEvent` | `KeycloakEventLegacy` | | ||
| `KeycloakEventType` | `KeycloakEventTypeLegacy` | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.