@devlearning/jwt-auth
v20.3.9
Published
Jwt Angular Authentication manager with automatic Refresh Token management.
Maintainers
Readme
JwtAuth
JWT Angular Authentication manager with automatic Refresh Token management, multi-tab sync, and mutex-based concurrent refresh protection.
Installation
npm i @devlearning/jwt-authConfiguration
Add JwtAuthModule.forRoot(...) to your AppModule:
import { JwtAuthModule } from '@devlearning/jwt-auth';
@NgModule({
imports: [
JwtAuthModule.forRoot({
tokenUrl: environment.jwtAuthToken,
refreshUrl: environment.jwtAuthRefreshToken,
})
]
})
export class AppModule {}JwtAuthConfig options
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
| tokenUrl | string | ✅ | — | URL to obtain the bearer token |
| refreshUrl | string | ✅ | — | URL to refresh the bearer token |
| useManualInitialization | boolean | — | false | If true, you must call init() manually |
| logLevel | JwtAuthLogLevel | — | — | Minimum log level (VERBOSE, INFO, WARNING, ERROR, NONE) |
| storageType | StorageType | — | LOCAL_STORAGE | Storage used for token persistence (LOCAL_STORAGE or SESSION_STORAGE) |
| refreshTokenRequestFactory | (token: JwtTokenBase) => object | — | — | Custom factory to build the refresh token request body. Use when your API requires extra fields beyond username and refreshToken |
Example with all options:
JwtAuthModule.forRoot({
tokenUrl: '/api/auth/token',
refreshUrl: '/api/auth/refresh',
useManualInitialization: false,
logLevel: JwtAuthLogLevel.ERROR,
storageType: StorageType.SESSION_STORAGE,
})Token model
The server response for both tokenUrl and refreshUrl must be compatible with JwtTokenBase:
export class JwtTokenBase {
username: string | undefined;
accessToken: string | undefined;
expiresIn: number | undefined; // Unix timestamp (ms)
refreshToken: string | undefined;
refreshTokenExpiresIn: number | undefined; // Unix timestamp (ms)
}You can extend it with your own fields:
export class MyToken extends JwtTokenBase {
email: string;
role: string;
}Then pass it as the generic parameter to the service:
constructor(private readonly _jwtAuth: JwtAuthService<MyToken>) {}Usage
Login
Call token() with your login request object. The method is generic so you can pass any typed request:
import { JwtAuthService } from '@devlearning/jwt-auth';
export interface LoginRequest {
username: string;
password: string;
}
@Injectable()
export class AuthService {
constructor(private readonly _jwtAuth: JwtAuthService<MyToken>) {}
login(username: string, password: string) {
return this._jwtAuth.token<LoginRequest>({ username, password });
}
}Logout
this._jwtAuth.logout();Reactive state
| Member | Type | Description |
|---|---|---|
| isLoggedIn$ | Observable<boolean> | Emits whenever the login state changes |
| jwtToken$ | Observable<Token \| null> | Emits whenever the token changes |
| refreshingToken$ | Observable<boolean> | Emits true while a refresh is in progress |
| isLoggedIn | boolean | Current login state (synchronous) |
| jwtToken | Token \| null | Current token (synchronous) |
Custom refresh token request
If your refresh endpoint requires extra fields (e.g. an application code), provide a refreshTokenRequestFactory in the config:
JwtAuthModule.forRoot({
tokenUrl: '/api/auth/token',
refreshUrl: '/api/auth/refresh',
refreshTokenRequestFactory: (token) => ({
authApplicationCode: 'MY_APP',
username: token.username,
refreshToken: token.refreshToken,
}),
})When refreshTokenRequestFactory is not provided, the default request body sent to refreshUrl is:
{
"username": "...",
"refreshToken": "..."
}Guard
Extend JwtAuthGuard to protect your routes:
import { Injectable } from '@angular/core';
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { JwtAuthGuard, JwtAuthService } from '@devlearning/jwt-auth';
import { Observable } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import { of } from 'rxjs';
@Injectable()
export class AuthGuard extends JwtAuthGuard implements CanActivate {
constructor(
private readonly _router: Router,
private readonly _jwtAuth: JwtAuthService<any>
) {
super(_jwtAuth);
}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
return this.canActivateBase(route, state)
.pipe(
map(x => {
if (x) {
return true;
} else {
this._router.navigateByUrl('/login');
return false;
}
}),
catchError(() => {
this._router.navigateByUrl('/login');
return of(false);
})
);
}
}Manual initialization
If useManualInitialization: true, call init() at app startup (e.g. in APP_INITIALIZER). This will attempt to restore the session from storage, refreshing the token automatically if needed:
export function initializeAuth(jwtAuth: JwtAuthService<any>) {
return () => jwtAuth.init().toPromise();
}
@NgModule({
providers: [
{
provide: APP_INITIALIZER,
useFactory: initializeAuth,
deps: [JwtAuthService],
multi: true,
}
]
})
export class AppModule {}Multi-tab sync
When using LOCAL_STORAGE, token changes in other browser tabs are automatically detected and synced via the storage event. This ensures all tabs share the same authentication state.
SESSION_STORAGE is scoped to a single tab and does not sync across tabs.
