@delon/auth Authentication & Authorization Skill
This skill helps implement authentication and authorization using @delon/auth library.
Core Principles
Token Management
- JWT Storage: Secure token storage using DA_SERVICE_TOKEN
- Auto Refresh: Automatic token renewal before expiration
- Interceptors: Automatic token injection in HTTP requests
- Logout: Clean token removal and session cleanup
Authorization
- RBAC: Role-based access control
- Permissions: Fine-grained permission checking
- Guards: Route protection with role/permission checks
- ACL Integration: Works with @delon/acl for UI-level controls
Configuration
App Config
// src/app/app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideDelonAuth, DA_SERVICE_TOKEN, ITokenService } from '@delon/auth';
import { JWTTokenModel } from '@delon/auth';
export const appConfig: ApplicationConfig = {
providers: [
provideDelonAuth({
token_send_key: 'Authorization',
token_send_template: 'Bearer ${token}',
token_send_place: 'header',
login_url: '/passport/login',
ignores: [/\/login/, /assets\//],
allow_anonymous_key: 'allow_anonymous',
executeOtherInterceptors: true,
refreshTime: 3000, // Refresh token check interval (ms)
refreshOffset: 6000 // Refresh token before expiration (ms)
})
]
};
Token Model
// src/app/core/models/auth-token.model.ts
import { JWTTokenModel } from '@delon/auth';
export interface AuthTokenModel extends JWTTokenModel {
token: string;
uid: string;
email: string;
displayName: string;
role: string;
permissions: string[];
exp: number;
}
Authentication Service
// src/app/core/services/auth.service.ts
import { Injectable, inject } from '@angular/core';
import { Router } from '@angular/router';
import { DA_SERVICE_TOKEN, ITokenService } from '@delon/auth';
import { Auth, signInWithEmailAndPassword, signOut, User } from '@angular/fire/auth';
import { AuthTokenModel } from '@core/models/auth-token.model';
import { signal, computed } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class AuthService {
private auth = inject(Auth);
private tokenService = inject<ITokenService>(DA_SERVICE_TOKEN);
private router = inject(Router);
// State
private currentUser = signal<User | null>(null);
private authToken = signal<AuthTokenModel | null>(null);
// Computed
isAuthenticated = computed(() => this.tokenService.get()?.token != null);
userRole = computed(() => this.authToken()?.role);
userPermissions = computed(() => this.authToken()?.permissions || []);
constructor() {
// Listen to Firebase Auth state
this.auth.onAuthStateChanged(async (user) => {
this.currentUser.set(user);
if (user) {
await this.refreshToken();
} else {
this.clearSession();
}
});
}
/**
* Login with email and password
*/
async login(email: string, password: string): Promise<void> {
try {
const credential = await signInWithEmailAndPassword(this.auth, email, password);
const token = await credential.user.getIdToken();
const tokenData = await this.parseToken(token);
// Store token in @delon/auth
this.tokenService.set(tokenData);
this.authToken.set(tokenData);
// Navigate to dashboard
await this.router.navigateByUrl('/dashboard');
} catch (error) {
console.error('Login failed:', error);
throw error;
}
}
/**
* Logout
*/
async logout(): Promise<void> {
try {
await signOut(this.auth);
this.clearSession();
await this.router.navigateByUrl('/passport/login');
} catch (error) {
console.error('Logout failed:', error);
throw error;
}
}
/**
* Refresh token
*/
async refreshToken(): Promise<void> {
const user = this.currentUser();
if (!user) return;
try {
const token = await user.getIdToken(true); // Force refresh
const tokenData = await this.parseToken(token);
this.tokenService.set(tokenData);
this.authToken.set(tokenData);
} catch (error) {
console.error('Token refresh failed:', error);
await this.logout();
}
}
/**
* Check if user has specific role
*/
hasRole(role: string): boolean {
return this.userRole() === role;
}
/**
* Check if user has specific permission
*/
hasPermission(permission: string): boolean {
return this.userPermissions().includes(permission);
}
/**
* Check if user has any of the specified permissions
*/
hasAnyPermission(permissions: string[]): boolean {
const userPerms = this.userPermissions();
return permissions.some(p => userPerms.includes(p));
}
/**
* Check if user has all of the specified permissions
*/
hasAllPermissions(permissions: string[]): boolean {
const userPerms = this.userPermissions();
return permissions.every(p => userPerms.includes(p));
}
/**
* Parse JWT token
*/
private async parseToken(token: string): Promise<AuthTokenModel> {
// Decode JWT payload
const payload = JSON.parse(atob(token.split('.')[1]));
// Load user permissions from Firestore (custom claims)
const permissions = await this.loadUserPermissions(payload.uid);
return {
token,
uid: payload.uid,
email: payload.email,
displayName: payload.name || payload.email,
role: payload.role || 'member',
permissions,
exp: payload.exp
};
}
/**
* Load user permissions from Firestore
*/
private async loadUserPermissions(uid: string): Promise<string[]> {
// Implementation: query user's permissions from Firestore
// This is placeholder - implement based on your schema
return ['read:tasks', 'write:tasks'];
}
/**
* Clear session
*/
private clearSession(): void {
this.tokenService.clear();
this.authToken.set(null);
this.currentUser.set(null);
}
}
Auth Guard
// src/app/core/guards/auth.guard.ts
import { inject } from '@angular/core';
import { Router, CanActivateFn } from '@angular/router';
import { AuthService } from '@core/services/auth.service';
/**
* Guard to protect routes requiring authentication
*/
export const authGuard: CanActivateFn = () => {
const authService = inject(AuthService);
const router = inject(Router);
if (authService.isAuthenticated()) {
return true;
}
return router.createUrlTree(['/passport/login']);
};
Role Guard
// src/app/core/guards/role.guard.ts
import { inject } from '@angular/core';
import { Router, CanActivateFn, ActivatedRouteSnapshot } from '@angular/router';
import { AuthService } from '@core/services/auth.service';
/**
* Guard to protect routes by role
*/
export const roleGuard: CanActivateFn = (route: ActivatedRouteSnapshot) => {
const authService = inject(AuthService);
const router = inject(Router);
const requiredRole = route.data['role'] as string;
if (!requiredRole) {
console.warn('Role guard used without required role');
return true;
}
if (authService.hasRole(requiredRole)) {
return true;
}
// Redirect to unauthorized page
return router.createUrlTree(['/exception/403']);
};
/**
* Usage in routes:
* {
* path: 'admin',
* component: AdminComponent,
* canActivate: [authGuard, roleGuard],
* data: { role: 'admin' }
* }
*/
Permission Guard
// src/app/core/guards/permission.guard.ts
import { inject } from '@angular/core';
import { Router, CanActivateFn, ActivatedRouteSnapshot } from '@angular/router';
import { AuthService } from '@core/services/auth.service';
/**
* Guard to protect routes by permissions
*/
export const permissionGuard: CanActivateFn = (route: ActivatedRouteSnapshot) => {
const authService = inject(AuthService);
const router = inject(Router);
const requiredPermissions = route.data['permissions'] as string[];
const requireAll = route.data['requireAll'] as boolean ?? true;
if (!requiredPermissions || requiredPermissions.length === 0) {
console.warn('Permission guard used without required permissions');
return true;
}
const hasAccess = requireAll
? authService.hasAllPermissions(requiredPermissions)
: authService.hasAnyPermission(requiredPermissions);
if (hasAccess) {
return true;
}
return router.createUrlTree(['/exception/403']);
};
/**
* Usage in routes:
* {
* path: 'tasks',
* component: TaskListComponent,
* canActivate: [authGuard, permissionGuard],
* data: {
* permissions: ['read:tasks'],
* requireAll: true
* }
* }
*/
Login Component
// src/app/routes/passport/login/login.component.ts
import { Component, inject, signal } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { AuthService } from '@core/services/auth.service';
import { SHARED_IMPORTS } from '@shared';
@Component({
selector: 'app-login',
standalone: true,
imports: [SHARED_IMPORTS],
template: `
<div class="login-container">
<nz-card>
<h2>Login</h2>
<form nz-form [formGroup]="loginForm" (ngSubmit)="handleLogin()">
<nz-form-item>
<nz-form-control nzErrorTip="Please input your email!">
<nz-input-group nzPrefixIcon="mail">
<input
nz-input
formControlName="email"
placeholder="Email"
type="email"
/>
</nz-input-group>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-control nzErrorTip="Please input your password!">
<nz-input-group nzPrefixIcon="lock">
<input
nz-input
formControlName="password"
placeholder="Password"
type="password"
/>
</nz-input-group>
</nz-form-control>
</nz-form-item>
@if (error()) {
<nz-alert
nzType="error"
[nzMessage]="error()!"
nzShowIcon
/>
}
<button
nz-button
nzType="primary"
nzBlock
[nzLoading]="loading()"
[disabled]="loginForm.invalid"
>
Login
</button>
</form>
</nz-card>
</div>
`
})
export class LoginComponent {
private fb = inject(FormBuilder);
private authService = inject(AuthService);
loading = signal(false);
error = signal<string | null>(null);
loginForm: FormGroup = this.fb.group({
email: ['', [Validators.required, Validators.email]],
password: ['', [Validators.required, Validators.minLength(6)]]
});
async handleLogin(): Promise<void> {
if (this.loginForm.invalid) return;
this.loading.set(true);
this.error.set(null);
try {
const { email, password } = this.loginForm.value;
await this.authService.login(email, password);
} catch (err) {
this.error.set(err instanceof Error ? err.message : 'Login failed');
} finally {
this.loading.set(false);
}
}
}
HTTP Interceptor (Auto Token Injection)
@delon/auth automatically provides an interceptor when configured. No manual implementation needed!
// Automatic token injection in all HTTP requests
// Configured in app.config.ts with provideDelonAuth()
// Example HTTP call - token automatically added
this.http.get('/api/tasks').subscribe(tasks => {
// Authorization: Bearer {token} header automatically added
});
Component-Level Permission Check
import { Component, inject, computed } from '@angular/core';
import { AuthService } from '@core/services/auth.service';
@Component({
selector: 'app-task-list',
template: `
@if (canCreate()) {
<button nz-button nzType="primary" (click)="createTask()">
Create Task
</button>
}
@if (canEdit()) {
<button nz-button (click)="editTask()">Edit</button>
}
@if (canDelete()) {
<button nz-button nzDanger (click)="deleteTask()">Delete</button>
}
`
})
export class TaskListComponent {
private authService = inject(AuthService);
canCreate = computed(() => this.authService.hasPermission('create:tasks'));
canEdit = computed(() => this.authService.hasPermission('edit:tasks'));
canDelete = computed(() => this.authService.hasPermission('delete:tasks'));
}
Integration with @delon/acl
// src/app/app.config.ts
import { provideDelonACL } from '@delon/acl';
export const appConfig: ApplicationConfig = {
providers: [
provideDelonACL()
]
};
// Sync permissions to ACL
import { ACLService } from '@delon/acl';
@Injectable({ providedIn: 'root' })
export class AuthService {
private aclService = inject(ACLService);
private syncPermissionsToACL(permissions: string[], role: string): void {
this.aclService.setRole([role]);
this.aclService.setAbility(permissions);
}
}
Routes Configuration
// src/app/routes/routes.ts
import { Routes } from '@angular/router';
import { authGuard } from '@core/guards/auth.guard';
import { roleGuard } from '@core/guards/role.guard';
import { permissionGuard } from '@core/guards/permission.guard';
export const routes: Routes = [
{
path: '',
canActivate: [authGuard],
children: [
{
path: 'dashboard',
loadComponent: () => import('./dashboard/dashboard.component')
},
{
path: 'admin',
canActivate: [roleGuard],
data: { role: 'admin' },
loadComponent: () => import('./admin/admin.component')
},
{
path: 'tasks',
canActivate: [permissionGuard],
data: {
permissions: ['read:tasks'],
requireAll: true
},
loadComponent: () => import('./tasks/task-list.component')
}
]
},
{
path: 'passport',
children: [
{
path: 'login',
loadComponent: () => import('./passport/login/login.component')
}
]
}
];
Checklist
When implementing authentication:
- [ ] Configure @delon/auth in app.config.ts
- [ ] Create AuthService with token management
- [ ] Implement login/logout methods
- [ ] Set up token refresh mechanism
- [ ] Create auth guard for protected routes
- [ ] Create role/permission guards as needed
- [ ] Integrate with Firebase Auth
- [ ] Sync permissions with @delon/acl
- [ ] Handle token expiration
- [ ] Test authentication flow
- [ ] Test authorization checks
- [ ] Handle error states (invalid credentials, network errors)