Add authentication to your Angular app
This guide shows you how to add authentication and access control to an Angular application using @lumoauth/client as an Angular service.
Before you start
You need:
- A LumoAuth account with an organization (sign up)
- A registered OAuth application with a redirect URI (e.g.,
http://localhost:4200/auth/callback) - Angular 17+ (uses standalone components and signals). To scaffold a new app:
npx @angular/cli@17 new my-app --standalone --routing(the steps below rely on the default standalone setup withapp.routes.ts).
To register the application, open Applications in the portal sidebar and click New application. Enter an Application name, choose Single-page or mobile app, enter the Redirect URI, and click Create application.

This app finishes sign-in in the browser, so also add its origin (for example http://localhost:4200) under Browser CORS origins on the application's Configuration tab. Then click Save configuration. Otherwise the token request is blocked by CORS right after sign-in.

Install the SDK
npm install @lumoauth/client
Environment variables
The Quickstarts page (under Applications & developer tools) has an Angular tab that shows the domain, organization ID and client ID pre-filled, ready to copy.

The tab's first step lists only OAuth clients of the kind that these snippets need. If none exists, Create one for this framework opens the create form with the right type preselected and brings you back. Its last step, Check it worked, shows the first sign-in that completes through the selected client.
src/environments/environment.ts:
export const environment = {
production: false,
lumoauthDomain: 'https://app.lumoauth.dev',
lumoauthOrgId: 'YOUR_ORG_ID',
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/client';
import type { ZanzibarCheckRequest, AbacCheckRequest } from '@lumoauth/client';
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,
orgId: environment.lumoauthOrgId,
clientId: environment.lumoauthClientId,
});
this.client = new LumoAuth({
baseUrl: environment.lumoauthDomain,
orgId: environment.lumoauthOrgId,
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,
// Angular 17+ built-in control flow (@if) needs no imports.
// Replace the <div> with your own component, adding it to `imports`.
template: `@if (canAccess()) {
<div>Admin tools</div>
}`,
})
export class AdminComponent implements OnInit {
canAccess = signal(false);
constructor(private auth: LumoAuthService) {}
async ngOnInit() {
this.canAccess.set(await this.auth.checkPermission('admin.dashboard.view'));
}
}