Skip to main content

Add authentication to your Angular app

Open in Cursor

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'));
}
}