@sftech/ng-auth
v2.1.1
Published

Downloads
464
Readme
@sftech/ng-auth
Angular library for OAuth2/OIDC authentication with anonymous session fallback, guards, and HTTP interceptor.
Installation
npm install @sftech/ng-authPeer Dependencies
| Package | Minimum Version |
|---------|----------------|
| @angular/common | >=20.0.0 |
| @angular/core | >=20.0.0 |
Configuration
Add AuthModule.forRoot() to your app.config.ts:
import { ApplicationConfig, importProvidersFrom } from '@angular/core';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { AuthModule, IAuthConfig, authenticationInterceptor } from '@sftech/ng-auth';
const authConfig: IAuthConfig = {
authProvider: 'oauth', // 'oauth' | 'none'
authBackendUrl: '/api/auth', // Backend auth API base URL
postLogoutRedirectUri: 'http://localhost:4200',
anonymousFallback: true, // Optional: enable anonymous sessions (default: false)
autoRefreshToken: true, // Optional: enable automatic token refresh (default: true)
refreshBeforeExpiryMinutes: 1, // Optional: minutes before expiry to refresh (default: 1)
};
export function appConfig(): ApplicationConfig {
return {
providers: [
importProvidersFrom(AuthModule.forRoot(authConfig)),
provideHttpClient(withInterceptors([authenticationInterceptor])),
],
};
}IAuthConfig Reference
interface IAuthConfig {
/** Authentication provider. Use 'oauth' for OAuth2/OIDC, 'none' to disable auth entirely. */
authProvider: 'oauth' | 'none';
/** Base URL of the backend auth API (e.g. '/api/auth' or 'https://api.example.com/auth'). */
authBackendUrl: string;
/** URI to redirect to after OAuth provider logout. */
postLogoutRedirectUri?: string;
/**
* When true, users without an OAuth session automatically receive an anonymous session.
* The anonymous session is created via POST /auth/anonymous. Default: false.
*/
anonymousFallback?: boolean;
/** When false, disables automatic token refresh scheduling. Default: true. */
autoRefreshToken?: boolean;
/** Minutes before token expiry to trigger a refresh. Default: 1. */
refreshBeforeExpiryMinutes?: number;
}Routing
Add the auth routes to your application:
import { Route } from '@angular/router';
import { authenticationGuard } from '@sftech/ng-auth';
export const appRoutes: Route[] = [
{
path: 'auth',
loadChildren: () => import('@sftech/ng-auth').then((m) => m.authRoutes),
},
{
path: 'protected',
component: MyComponent,
canActivate: [authenticationGuard],
},
];The authRoutes include:
/auth/callback- OAuth callback handler (CallbackComponent)/auth/recovery- Anonymous session recovery page (RecoveryComponent)
Features
- OAuth2/OIDC authentication via backend redirect flow
- Signal-based reactive authentication state (
isAuthenticated,isAnonymous,recoveryCode) - Anonymous session fallback with recovery code support
- Automatic token refresh scheduling based on session expiry
- Unified auth status endpoint for both OAuth and anonymous sessions
- Functional guards:
authenticationGuard,adminGuard - HTTP interceptor that adds
withCredentials: trueto auth-related requests - Session recovery component for restoring anonymous sessions by code
- localStorage persistence of recovery codes across page refreshes
API Reference
Services
| Service | Description | Key Methods |
|---------|-------------|-------------|
| AuthenticationService | Signal-based authentication service | checkAuthStatus(), login(), logout(), refreshToken(), recoverByCode(), loadUserInfo() |
Guards
| Guard | Type | Description |
|-------|------|-------------|
| authenticationGuard | CanActivateFn | Protects routes. Redirects to login when not authenticated. With anonymousFallback: true, creates an anonymous session instead. When authProvider is 'none', allows all access. |
| adminGuard | CanActivateFn | Restricts routes to users with the admin role. |
Interceptors
| Interceptor | Description |
|-------------|-------------|
| authenticationInterceptor | HttpInterceptorFn that adds withCredentials: true to requests matching /api/auth, /api/orchestrator, or /api/orchestrator-db. |
Models
| Model | Description | Factory Methods |
|-------|-------------|-----------------|
| AuthStatus | Represents authentication state | fromDto(), fromAnonymousSessionDto(), notAuthenticated(), authenticated() |
| UserInfo | OAuth user profile data | fromDto() |
DTOs
| DTO | Description |
|-----|-------------|
| IAuthStatusResponseDto | Response from GET /auth/status. Covers both OAuth and anonymous sessions. |
| IAnonymousSessionResponseDto | Response from POST /auth/anonymous and POST /auth/anonymous/recover. Contains full session data. |
| ILogoutResponseDto | Response from POST /auth/logout. Includes optional anonymousSession field for fallback setup. |
| IUserInfoResponseDto | Response from GET /auth/user. OAuth user profile. |
| ITokenRefreshResponseDto | Response from POST /auth/refresh. |
Enums
| Enum | Values | Description |
|------|--------|-------------|
| EAuthApiEndpoint | STATUS, USER, LOGIN, LOGOUT, REFRESH, ANONYMOUS_CREATE, ANONYMOUS_RECOVER | Backend endpoint path segments |
| EAuthFailureReason | Library-defined values | Reason codes returned when authenticated: false |
| EAuthStorageKey | RECOVERY_CODE | Keys used for localStorage |
| EAuthRole | ADMIN | Role values checked by adminGuard |
DTO Interfaces
IAuthStatusResponseDto
interface IAuthStatusResponseDto {
authenticated: boolean;
userId?: string;
/** Visitor ID for anonymous sessions. Mapped to userId in AuthStatus. */
visitorId?: string;
expiresAt?: string;
timeRemaining?: number;
reason?: EAuthFailureReason;
/** Present when the session is an anonymous session (since 2.0.0). */
isAnonymous?: boolean;
/** Recovery code for anonymous sessions, returned when isAnonymous is true (since 2.0.0). */
recoveryCode?: string;
}IAnonymousSessionResponseDto
interface IAnonymousSessionResponseDto {
authenticated: boolean;
isAnonymous: boolean;
userId: string;
visitorId: string;
recoveryCode: string;
sessionId: string;
expiresAt: string;
}ILogoutResponseDto
interface ILogoutResponseDto {
message: string;
providerLogoutUrl: string;
success: boolean;
/** Anonymous session returned by the backend after OAuth logout when anonymousFallback is true (since 2.0.0). */
anonymousSession?: {
sessionId: string;
userId: string;
visitorId: string;
recoveryCode: string;
expiresAt: string;
};
}Usage Examples
Basic Setup (OAuth only)
// app.config.ts
import { ApplicationConfig, importProvidersFrom } from '@angular/core';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { AuthModule, IAuthConfig, authenticationInterceptor } from '@sftech/ng-auth';
const authConfig: IAuthConfig = {
authProvider: 'oauth',
authBackendUrl: '/api/auth',
};
export function appConfig(): ApplicationConfig {
return {
providers: [
importProvidersFrom(AuthModule.forRoot(authConfig)),
provideHttpClient(withInterceptors([authenticationInterceptor])),
],
};
}Advanced: Anonymous Session Fallback
When anonymousFallback: true is set, unauthenticated users automatically receive an anonymous session. This is useful for applications that want to track anonymous activity before users log in.
// app.config.ts
const authConfig: IAuthConfig = {
authProvider: 'oauth',
authBackendUrl: '/api/auth',
anonymousFallback: true,
};// my.component.ts
import { Component, inject } from '@angular/core';
import { AuthenticationService } from '@sftech/ng-auth';
@Component({
selector: 'app-my',
template: `
<div *ngIf="auth.isAnonymous()">
You are browsing anonymously.
Recovery code: {{ auth.recoveryCode() }}
</div>
<div *ngIf="auth.isAuthenticated() && !auth.isAnonymous()">
Welcome, {{ auth.currentUser()?.name }}!
</div>
`,
})
export class MyComponent {
protected auth = inject(AuthenticationService);
}Advanced: Using AuthenticationService Signals
import { Component, inject } from '@angular/core';
import { AuthenticationService } from '@sftech/ng-auth';
@Component({ ... })
export class MyComponent {
private auth = inject(AuthenticationService);
// Signals — use in templates or effects
isAuthenticated = this.auth.isAuthenticated; // Signal<boolean>
isAnonymous = this.auth.isAnonymous; // Signal<boolean>
userId = this.auth.userId; // Signal<string | undefined>
currentUser = this.auth.currentUser; // Signal<UserInfo | null>
recoveryCode = this.auth.recoveryCode; // Signal<string | null>
timeRemaining = this.auth.timeRemaining; // Signal<number | undefined>
login() { this.auth.login(); }
logout() { this.auth.logout().subscribe(); }
}Advanced: Session Recovery
Users with an anonymous session can obtain a recovery code from auth.recoveryCode(). If they lose their session cookie (e.g., different browser), they can restore the session:
import { Component, inject } from '@angular/core';
import { AuthenticationService } from '@sftech/ng-auth';
@Component({ ... })
export class RecoveryPageComponent {
private auth = inject(AuthenticationService);
recover(code: string) {
this.auth.recoverByCode(code).subscribe((status) => {
if (status.authenticated) {
// Session restored successfully
}
});
}
}The built-in RecoveryComponent (available at the /auth/recovery route) handles this flow automatically.
Advanced: Protecting Admin Routes
import { Route } from '@angular/router';
import { authenticationGuard, adminGuard } from '@sftech/ng-auth';
export const appRoutes: Route[] = [
{
path: 'admin',
component: AdminComponent,
canActivate: [authenticationGuard, adminGuard],
},
];Auth Flows
1. OAuth Login
login() → GET /auth/login?redirect_url=<encoded-origin>/auth/callback → OAuth provider → callback → GET /auth/status → GET /auth/userThe GET /auth/status endpoint is the single unified status check for both OAuth and anonymous sessions.
2. Anonymous Fallback (initial page load, no session)
GET /auth/status → { authenticated: false }
→ POST /auth/anonymous → { authenticated: true, isAnonymous: true, recoveryCode: "..." }The POST /auth/anonymous response contains all session data needed to construct AuthStatus directly — no follow-up status call is made.
3. Resuming Existing Anonymous Session
GET /auth/status → { authenticated: true, isAnonymous: true, visitorId: "...", recoveryCode: "..." }A single status call detects the existing anonymous session. The visitorId field is mapped to userId in the AuthStatus model.
4. OAuth Logout with Anonymous Fallback
POST /auth/logout → { success: true, anonymousSession: { ... } }
→ AuthStatus constructed from embedded anonymousSession (no additional calls)If anonymousSession is absent from the logout response (backend version compatibility), a POST /auth/anonymous call is made as a fallback.
5. Anonymous Logout (reset identity)
clearAuthState()
→ POST /auth/anonymous → { authenticated: true, isAnonymous: true, ... }There is no /auth/anonymous/logout endpoint. Anonymous logout clears local state and creates a fresh session.
6. Token Refresh
POST /auth/refresh (OAuth sessions)
GET /auth/status (anonymous sessions — re-checks the unified endpoint)Refresh is scheduled automatically based on timeRemaining from the status response. Configure with autoRefreshToken and refreshBeforeExpiryMinutes.
7. Session Recovery
POST /auth/anonymous/recover { recoveryCode } → { authenticated: true, isAnonymous: true, ... }The response contains full session data. AuthStatus is constructed directly without a follow-up status call.
API Requirements
The library expects the following endpoints on the backend at the configured authBackendUrl:
| Method | Path | Description |
|--------|------|-------------|
| GET | /status | Unified auth status. Returns OAuth or anonymous session data. |
| GET | /user | OAuth user profile. Only called when authenticated: true and not anonymous. |
| GET | /login | Initiates OAuth login redirect. Accepts optional redirect_url query parameter (URL-encoded callback URL). When provided, the backend redirects to this URL after OAuth completion instead of its own configured frontendCallbackUrl. Required for Capacitor environments. |
| POST | /logout | OAuth logout. Returns optional anonymousSession for fallback. |
| POST | /refresh | Refreshes OAuth access token. |
| POST | /anonymous | Creates a new anonymous session. Returns full session data. |
| POST | /anonymous/recover | Recovers an anonymous session by recovery code. Returns full session data. |
Migration from 1.x
Version 2.0.0 contains breaking changes. If you are upgrading from 1.x, review the following:
Removed Exports
| Symbol | Action Required |
|--------|----------------|
| IAnonymousLogoutResponseDto | Removed. The /auth/anonymous/logout endpoint does not exist in the backend. No replacement needed — anonymous logout is now client-side only. |
| EAuthApiEndpoint.ANONYMOUS_STATUS | Removed. Use the unified EAuthApiEndpoint.STATUS (GET /auth/status) for all session status checks. |
| EAuthApiEndpoint.ANONYMOUS_LOGOUT | Removed. Anonymous logout does not call any backend endpoint. |
| AuthStatus.fromAnonymousStatusDto() | Removed. Use AuthStatus.fromDto() — it now detects anonymous sessions via the isAnonymous flag automatically. |
Changed Behavior
| Area | Change |
|------|--------|
| IAuthStatusResponseDto | Extended with isAnonymous?: boolean and recoveryCode?: string fields. The unified GET /auth/status endpoint now returns these fields for anonymous sessions. |
| IAnonymousSessionResponseDto | Expanded from a minimal shape to include authenticated, isAnonymous, userId, visitorId, recoveryCode, sessionId, and expiresAt. |
| ILogoutResponseDto | Extended with an optional anonymousSession field. When anonymousFallback: true, the backend embeds the new anonymous session in the logout response. |
| AuthStatus.fromDto() | Now detects anonymous sessions via the isAnonymous flag in the response and maps visitorId to userId automatically. No code changes needed if you were calling checkAuthStatus(). |
| createAnonymousSession() | Uses the POST /auth/anonymous response directly to build AuthStatus — no follow-up GET call. |
| recoverByCode() | Uses the POST /auth/anonymous/recover response directly — no follow-up GET call. |
| logout() (OAuth) | Reads anonymousSession from the logout response when anonymousFallback: true — no additional POST /auth/anonymous call in the happy path. |
| Angular peer dependency | Bumped from >=19.0.0 to >=20.0.0. |
Migration Example
Before (1.x):
// If you referenced removed enum values or DTOs directly:
import {
EAuthApiEndpoint,
IAnonymousLogoutResponseDto,
AuthStatus,
} from '@sftech/ng-auth';
// ANONYMOUS_STATUS and ANONYMOUS_LOGOUT no longer exist:
const anonStatusUrl = EAuthApiEndpoint.ANONYMOUS_STATUS; // ERROR in 2.0.0
const anonLogoutUrl = EAuthApiEndpoint.ANONYMOUS_LOGOUT; // ERROR in 2.0.0
// fromAnonymousStatusDto() no longer exists:
const status = AuthStatus.fromAnonymousStatusDto(dto); // ERROR in 2.0.0After (2.0.0):
import { EAuthApiEndpoint, AuthStatus } from '@sftech/ng-auth';
// Use unified STATUS for all session checks:
const statusUrl = EAuthApiEndpoint.STATUS; // '/status'
// fromDto() now handles anonymous sessions automatically:
const status = AuthStatus.fromDto(dto); // Works for OAuth and anonymous
// Or use fromAnonymousSessionDto() for POST /auth/anonymous responses:
const anonStatus = AuthStatus.fromAnonymousSessionDto(anonDto);Development
Build
npx nx build ng-authTest
npx nx test ng-authLint
npx nx lint ng-authChangelog
See CHANGELOG.md
License
MIT
