Add authentication to your Angular app
This guide walks you through adding authentication and access control to an Angular application using @lumoauth/sdk as an Angular service.
Before you start
You need:
- A LumoAuth account with a configured tenant (sign up)
- A registered OAuth application with a redirect URI (e.g.,
http://localhost:4200/auth/callback) - Angular 17+ (uses standalone components and signals)
Install the SDK
npm install @lumoauth/sdk
Environment variables
src/environments/environment.ts:
export const environment = {
production: false,
lumoauthDomain: 'https://app.lumoauth.dev',
lumoauthTenant: 'YOUR_TENANT_SLUG',
lumoauthClientId: 'YOUR_CLIENT_ID',
};
Create the LumoAuth service
src/app/auth/lumoauth.service.ts:
import { Injectable, signal } from '@angular/core';
import { LumoAuth, AuthModule } from '@lumoauth/sdk';
import type { ZanzibarCheckRequest, AbacCheckRequest } from '@lumoauth/sdk';
import { environment } from '../../environments/environment';
@Injectable({ providedIn: 'root' })
export class LumoAuthService {
private readonly authModule: AuthModule;
private readonly client: LumoAuth;
readonly isSignedIn = signal(false);
readonly isLoaded = signal(false);
private readonly redirectUri = `${window.location.origin}/auth/callback`;
constructor() {
this.authModule = new AuthModule({
baseUrl: environment.lumoauthDomain,
tenantSlug: environment.lumoauthTenant,
clientId: environment.lumoauthClientId,
});
this.client = new LumoAuth({
baseUrl: environment.lumoauthDomain,
tenantSlug: environment.lumoauthTenant,
clientId: environment.lumoauthClientId,
token: () => this.getAccessToken(),
});
this.init();
}
async signIn(): Promise<void> {
const { url, codeVerifier, state } = await this.authModule.buildAuthorizationUrl({
redirectUri: this.redirectUri,
});
sessionStorage.setItem('lumoauth_verifier', codeVerifier);
sessionStorage.setItem('lumoauth_state', state);
window.location.href = url;
}
async handleCallback(): Promise<void> {
const params = new URLSearchParams(window.location.search);
const code = params.get('code');
const returnedState = params.get('state');
const codeVerifier = sessionStorage.getItem('lumoauth_verifier');
const savedState = sessionStorage.getItem('lumoauth_state');
if (!code || returnedState !== savedState || !codeVerifier) {
throw new Error('Invalid OAuth callback');
}
const tokens = await this.authModule.exchangeCodeForTokens({
code,
codeVerifier,
redirectUri: this.redirectUri,
});
sessionStorage.setItem('lumoauth_tokens', JSON.stringify(tokens));
sessionStorage.removeItem('lumoauth_verifier');
sessionStorage.removeItem('lumoauth_state');
this.isSignedIn.set(true);
}
async signOut(): Promise<void> {
const token = this.getAccessToken();
if (token) this.authModule.revokeToken(token, token).catch(() => {});
sessionStorage.removeItem('lumoauth_tokens');
this.isSignedIn.set(false);
}
checkPermission(permission: string): Promise<boolean> {
return this.client.permissions.check(permission);
}
checkZanzibar(request: ZanzibarCheckRequest): Promise<boolean> {
return this.client.zanzibar.check(request);
}
checkAbac(request: AbacCheckRequest): Promise<boolean> {
return this.client.abac.isAllowed(
request.resourceType, request.action, request.resourceId, request.context
);
}
getAccessToken(): string {
try {
const stored = sessionStorage.getItem('lumoauth_tokens');
if (stored) return JSON.parse(stored).access_token || '';
} catch {}
return '';
}
private init(): void {
this.isSignedIn.set(!!this.getAccessToken());
this.isLoaded.set(true);
}
}
Auth callback component
src/app/auth/callback/callback.component.ts:
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { LumoAuthService } from '../lumoauth.service';
@Component({
selector: 'app-callback',
standalone: true,
template: '<p>Completing sign-in…</p>',
})
export class CallbackComponent implements OnInit {
constructor(private auth: LumoAuthService, private router: Router) {}
async ngOnInit() {
await this.auth.handleCallback();
this.router.navigate(['/dashboard']);
}
}
Register the route in app.routes.ts:
{ path: 'auth/callback', component: CallbackComponent }
Auth guard
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { LumoAuthService } from './auth/lumoauth.service';
export const authGuard: CanActivateFn = () => {
const auth = inject(LumoAuthService);
if (auth.isSignedIn()) return true;
inject(Router).navigate(['/sign-in']);
return false;
};
Apply to routes:
{ path: 'dashboard', component: DashboardComponent, canActivate: [authGuard] }
Permission checks in components
import { Component, signal, OnInit } from '@angular/core';
import { LumoAuthService } from '../auth/lumoauth.service';
@Component({
selector: 'app-admin',
standalone: true,
template: `<admin-panel *ngIf="canAccess()" />`,
})
export class AdminComponent implements OnInit {
canAccess = signal(false);
constructor(private auth: LumoAuthService) {}
async ngOnInit() {
this.canAccess.set(await this.auth.checkPermission('admin.dashboard.view'));
}
}