@flusys/ng-auth
v4.1.1
Published
Authentication module for FLUSYS Angular applications
Readme
@flusys/ng-auth
Complete authentication library for the FLUSYS Angular platform — JWT session management, HTTP-only cookie tokens, multi-company selection, and provider adapter registration.
Table of Contents
- Overview
- Features
- Compatibility
- Installation
- Quick Start
- Module Registration
- Session Restoration Flow
- Auth State Service
- Auth API Service
- HTTP Interceptors
- Guards
- Pages
- Provider Adapters
- API Endpoints
- Configuration Reference
- Troubleshooting
- License
Overview
@flusys/ng-auth provides end-to-end authentication for FLUSYS Angular applications. It handles the complete lifecycle: login, registration, token refresh, session restoration on page reload, email verification, profile management, password changes, and multi-company selection.
Tokens are stored in HTTP-only cookies (managed by the backend) — never in localStorage. The access token is held in memory only and automatically refreshed via a silent refresh interceptor.
Features
- ✅ JWT authentication with HTTP-only refresh token cookies
- ✅ Silent token refresh on 401 responses
- ✅ Session restoration on page reload (
AuthInitService) - ✅ Multi-company selection flow
- ✅ Email verification with OTP
- ✅ Profile management (name, password, avatar)
- ✅
AuthStateService— signal-based reactive auth state - ✅
provideAuthProviders()— registersUSER_PROVIDER,COMPANY_PROVIDER - ✅
provideAuthLayoutIntegration()— bridges auth to layout - ✅ Guard:
appInitGuardfor session restoration before navigation - ✅ Guard:
AuthenticatedGuard,GuestGuard,CompanySelectedGuard
Compatibility
| Package | Version | |---------|---------| | Angular | 21+ | | @flusys/ng-core | 4.x | | @flusys/ng-shared | 4.x |
Installation
npm install @flusys/ng-auth @flusys/ng-core @flusys/ng-sharedQuick Start
1. Register Auth Providers
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { APP_CONFIG } from '@flusys/ng-core';
import {
provideAuthProviders,
provideAuthLayoutIntegration,
authInterceptor,
tokenRefreshInterceptor,
} from '@flusys/ng-auth';
import { environment } from './environments/environment';
export const appConfig: ApplicationConfig = {
providers: [
{ provide: APP_CONFIG, useValue: environment },
provideHttpClient(
withInterceptors([
authInterceptor, // Attaches access token to requests
tokenRefreshInterceptor, // Handles 401 → refresh → retry
])
),
...provideAuthProviders(), // USER_PROVIDER, COMPANY_PROVIDER
...provideAuthLayoutIntegration(), // LAYOUT_AUTH_STATE, LAYOUT_AUTH_API
],
};2. Protect Routes with Guards
// app.routes.ts
import { Routes } from '@angular/router';
import { appInitGuard } from '@flusys/ng-auth';
import { AppLayoutComponent } from '@flusys/ng-layout';
export const routes: Routes = [
{
path: '',
component: AppLayoutComponent,
canActivate: [appInitGuard], // Restores session on page refresh
children: [
{ path: 'dashboard', loadComponent: () => import('./pages/dashboard.component') },
],
},
{
path: 'auth',
loadChildren: () => import('@flusys/ng-auth').then(m => m.AUTH_ROUTES),
},
];3. Enable Auth in App Config
// environments/environment.ts
export const environment = {
apiBaseUrl: 'http://localhost:2002',
services: {
auth: {
enabled: true,
features: {
signUp: true,
emailVerification: true,
companySelection: true,
},
},
},
};Module Registration
ng-auth uses function-based providers (not Angular modules):
provideAuthProviders()
Registers auth adapters against shared provider tokens:
...provideAuthProviders()
// Provides:
// USER_PROVIDER → AuthStateService (IUserProvider)
// COMPANY_PROVIDER → AuthStateService (ICompanyProvider)These tokens are consumed by ng-iam, ng-storage, ng-notification — no direct dependency on ng-auth.
provideAuthLayoutIntegration()
Bridges auth state and API to the layout:
...provideAuthLayoutIntegration()
// Provides:
// LAYOUT_AUTH_STATE → AuthLayoutStateAdapter (profile picture, company logo)
// LAYOUT_AUTH_API → AuthLayoutApiAdapter (logout, select-company actions)Session Restoration Flow
On every page refresh, auth state is cleared from memory. The appInitGuard restores the session transparently:
Page Refresh
│
▼
appInitGuard runs
│
├─ User in memory? ──YES──► Check company → Allow navigation
│
└─ NO ──► AuthInitService.initialize()
│
├─ POST /auth/refresh (HTTP-only cookie) → access token
├─ GET /auth/me → user data
└─ Session restored in memory
│
├─ Auth enabled? → Check company selection
├─ IAM enabled? → Load permissions
└─ Allow navigationCompany Selection Flow
User logs in
│
├─ requiresSelection: false ──► Navigate to app
│
└─ requiresSelection: true
│
├─ sessionId + companies[] returned
└─ Redirect to /auth/select-company
│
└─ User picks company/branch
│
└─ POST /auth/select { sessionId, companyId, branchId }
│
└─ Tokens + company data → Navigate to appAuth State Service
AuthStateService is the single source of truth for authentication state, powered by Angular signals.
import { AuthStateService } from '@flusys/ng-auth';
@Component({ ... })
export class MyComponent {
private authState = inject(AuthStateService);
// Signals (read-only)
user = this.authState.user; // Signal<ICurrentUser | null>
company = this.authState.company; // Signal<ICompany | null>
branch = this.authState.branch; // Signal<IBranch | null>
isAuthenticated = this.authState.isAuthenticated; // Signal<boolean>
accessToken = this.authState.accessToken; // Signal<string | null>
// Computed
userName = computed(() => this.authState.user()?.name ?? 'Guest');
}AuthStateService API:
| Member | Type | Description |
|--------|------|-------------|
| user | Signal<ICurrentUser \| null> | Current authenticated user |
| company | Signal<ICompany \| null> | Selected company |
| branch | Signal<IBranch \| null> | Selected branch |
| isAuthenticated | Signal<boolean> | True if user is logged in |
| accessToken | Signal<string \| null> | In-memory JWT access token |
| setUser(user) | void | Update user state |
| setTokens(tokens) | void | Update access token |
| setCompany(company, branch) | void | Update company/branch |
| clearAll() | void | Clear all auth state (logout) |
Auth API Service
AuthApiService handles all HTTP calls to the auth backend.
import { AuthApiService } from '@flusys/ng-auth';
@Component({ ... })
export class LoginPageComponent {
private authApi = inject(AuthApiService);
login(email: string, password: string): void {
this.authApi.login({ email, password }).subscribe(response => {
if (response.requiresSelection) {
// Redirect to company selection
}
// Navigate to app
});
}
}AuthApiService Methods:
| Method | Endpoint | Description |
|--------|----------|-------------|
| login(dto) | POST /auth/login | Authenticate user |
| register(dto) | POST /auth/register | Self-register |
| logout() | POST /auth/logout | Invalidate tokens |
| refresh() | POST /auth/refresh | Refresh access token |
| me() | GET /auth/me | Get current user info |
| selectCompany(dto) | POST /auth/select | Select company after login |
| verifyEmail(dto) | POST /auth/verify-email | Verify OTP |
| resendVerification() | POST /auth/resend-verification | Resend OTP email |
| changePassword(dto) | POST /auth/change-password | Update password |
| updateProfile(dto) | POST /auth/update-profile | Update name/avatar |
HTTP Interceptors
authInterceptor
Attaches the in-memory access token as Authorization: Bearer <token> header to every outgoing request (except public endpoints).
provideHttpClient(
withInterceptors([authInterceptor])
)tokenRefreshInterceptor
On 401 responses, automatically:
- Calls
POST /auth/refresh - Updates
AuthStateServicewith new token - Retries the original failed request
All concurrent 401s are queued — only one refresh call is made.
provideHttpClient(
withInterceptors([tokenRefreshInterceptor])
)Interceptor Order Matters:
withInterceptors([
authInterceptor, // Must be FIRST (attaches token)
tokenRefreshInterceptor, // Must be SECOND (handles 401)
])Guards
| Guard | Usage | Behavior |
|-------|-------|---------|
| appInitGuard | canActivate | Restores session from cookie on page refresh |
| AuthenticatedGuard | canActivate | Redirects to /auth/login if not authenticated |
| GuestGuard | canActivate | Redirects to / if already authenticated |
| CompanySelectedGuard | canActivate | Redirects to /auth/select-company if company not chosen |
// Route with all guards
{
path: '',
canActivate: [appInitGuard],
children: [
{
path: 'dashboard',
canActivate: [AuthenticatedGuard, CompanySelectedGuard],
loadComponent: () => import('./dashboard.component'),
},
],
}Pages
ng-auth exports pre-built page components routable via AUTH_ROUTES:
import { AUTH_ROUTES } from '@flusys/ng-auth';
// app.routes.ts
{ path: 'auth', loadChildren: () => AUTH_ROUTES }| Route | Component | Description |
|-------|-----------|-------------|
| /auth/login | LoginPageComponent | Email + password login form |
| /auth/register | RegisterPageComponent | Self-registration form |
| /auth/verify-email | VerifyEmailPageComponent | OTP verification |
| /auth/select-company | SelectCompanyPageComponent | Company/branch picker |
| /auth/profile | ProfilePageComponent | User profile editor |
All pages support full i18n via TranslatePipe.
Provider Adapters
AUTH_EMAIL_PROVIDER
ng-auth requires an email adapter for verification emails. Register this in your NestJS backend (not Angular). On the Angular side, this is handled automatically by the backend.
User Enricher
You can inject additional user data at login time:
// Custom enricher (rare — usually not needed)
import { USER_ENRICHER } from '@flusys/ng-auth';
providers: [
{
provide: USER_ENRICHER,
useClass: MyUserEnricher,
},
]API Endpoints
| Method | Endpoint | Auth | Description |
|--------|----------|------|-------------|
| POST | /auth/login | Public | Login with email/password |
| POST | /auth/register | Public | Self-registration |
| POST | /auth/logout | Bearer | Logout and clear cookie |
| POST | /auth/refresh | Cookie | Refresh access token |
| GET | /auth/me | Bearer | Get current user |
| POST | /auth/select | Session | Select company after login |
| POST | /auth/verify-email | Bearer | Verify email OTP |
| POST | /auth/resend-verification | Bearer | Resend OTP |
| POST | /auth/change-password | Bearer | Change password |
| POST | /auth/update-profile | Bearer | Update profile |
Configuration Reference
| Config Key | Type | Default | Description |
|------------|------|---------|-------------|
| services.auth.enabled | boolean | false | Enable auth module |
| services.auth.features.signUp | boolean | false | Allow self-registration |
| services.auth.features.emailVerification | boolean | false | Require email OTP |
| services.auth.features.companySelection | boolean | false | Enable multi-company |
Troubleshooting
Token refresh loop (endless 401 cycle)
This happens when the refresh endpoint itself returns 401. Ensure the refresh token cookie hasn't expired. Also verify that tokenRefreshInterceptor skips the /auth/refresh endpoint:
// The interceptor automatically skips /auth/refresh to prevent loopsappInitGuard redirects to login on every refresh
Ensure your backend sets the refresh token cookie with httpOnly: true, sameSite: 'lax', and the correct domain/path. The browser must send the cookie automatically with POST /auth/refresh.
Company selection page shown even after selecting
Verify that POST /auth/select returns both tokens AND the company/branch data. AuthStateService.setCompany() must be called with the response.
User profile picture doesn't load
Profile pictures are fetched via FileUrlService, not by constructing URLs. Ensure ng-storage providers are registered and services.storage.enabled: true.
Login form validation errors not translated
Provide TRANSLATE_ADAPTER via provideLocalization(). The fallback text (non-translated) is always shown if the adapter is absent.
License
MIT © FLUSYS
