npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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.

npm version Angular TypeScript License: MIT


Table of Contents


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() — registers USER_PROVIDER, COMPANY_PROVIDER
  • provideAuthLayoutIntegration() — bridges auth to layout
  • ✅ Guard: appInitGuard for 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-shared

Quick 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 navigation

Company 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 app

Auth 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:

  1. Calls POST /auth/refresh
  2. Updates AuthStateService with new token
  3. 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 loops

appInitGuard 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