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

@zshn-dev/auth-angular

v2.0.0

Published

Angular signals library for GitHub OAuth — guards, interceptor, and components

Readme

@zshn-dev/auth-angular

Signal-based GitHub OAuth authentication library for Angular 21+.

@zshn-dev/auth-angular provides a complete, opinionated GitHub OAuth flow for Angular applications. It ships with signal-based state, a functional HTTP interceptor, route guards, and ready-to-use login/callback components — all wired up through a single provideAuth() call.


Requirements

| Dependency | Version | | ---------- | -------- | | Angular | ≥ 21 | | TypeScript | ≥ 5.9 | | Node.js | ≥ 20 |


Installation

npm install @zshn-dev/auth-angular

Setup

1. Register the providers — app.config.ts

Call provideAuth() inside ApplicationConfig. This registers AuthService and the authInterceptor for the entire application.

// src/app/app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
import { provideAuth } from '@zshn-dev/auth-angular';
import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideHttpClient(withInterceptorsFromDi()),
    provideAuth({
      serverUrl: 'http://localhost:3000',
      afterLoginUrl: '/dashboard',
      afterLogoutUrl: '/login',
      storageKey: 'auth_token',
    }),
  ],
};

2. Configure routes — app.routes.ts

Mount AuthLoginComponent behind guestGuard (redirects away if already logged in) and AuthCallbackComponent at the OAuth callback URL. Protect private routes with authGuard.

// src/app/app.routes.ts
import { Routes } from '@angular/router';
import {
  AuthLoginComponent,
  AuthCallbackComponent,
  authGuard,
  guestGuard,
} from '@zshn-dev/auth-angular';

export const routes: Routes = [
  {
    path: 'login',
    component: AuthLoginComponent,
    canActivate: [guestGuard],
  },
  {
    // Must match the callback URL configured in your GitHub OAuth App
    path: 'auth/callback',
    component: AuthCallbackComponent,
  },
  {
    path: 'dashboard',
    loadComponent: () =>
      import('./dashboard/dashboard.component').then(m => m.DashboardComponent),
    canActivate: [authGuard],
  },
  { path: '', redirectTo: 'dashboard', pathMatch: 'full' },
];

3. Use AuthService in a component

Inject AuthService and read its signals directly in templates or component logic. Signals are synchronous — no async pipe or subscription needed.

// src/app/dashboard/dashboard.component.ts
import { Component, inject } from '@angular/core';
import { AuthService } from '@zshn-dev/auth-angular';

@Component({
  selector: 'app-dashboard',
  standalone: true,
  template: `
    <header>
      <p>Welcome, {{ auth.user()?.name ?? 'stranger' }}</p>
      <button (click)="auth.logout()">Sign out</button>
    </header>

    @if (auth.isAuthenticated()) {
      <main>Protected content</main>
    }
  `,
})
export class DashboardComponent {
  protected auth = inject(AuthService);
}

API Reference

provideAuth(config: AuthConfig): EnvironmentProviders

Registers AuthService and authInterceptor via makeEnvironmentProviders(). Call once in app.config.ts.

function provideAuth(config: AuthConfig): EnvironmentProviders;

AuthConfig

interface AuthConfig {
  /** Base URL of the authentication server. Required. */
  serverUrl: string;

  /** Angular route to navigate to after a successful login. Default: '/' */
  afterLoginUrl?: string;

  /** Angular route to navigate to after logout. Default: '/login' */
  afterLogoutUrl?: string;

  /** localStorage key used to persist the JWT. Default: 'auth_token' */
  storageKey?: string;
}

AuthService

Signal-based service. Available via inject(AuthService) anywhere after provideAuth() has been registered.

class AuthService {
  /** The currently authenticated user, or null when logged out. */
  readonly user: Signal<User | null>;

  /** True when a valid token is present in storage. */
  readonly isAuthenticated: Signal<boolean>;

  /** The raw JWT string, or null when logged out. */
  readonly token: Signal<string | null>;

  /** Redirects the browser to the GitHub OAuth authorization page. */
  login(): void;

  /** Clears the stored token and navigates to afterLogoutUrl. */
  logout(): void;

  /**
   * Stores the given token and updates all signals.
   * Called automatically by AuthCallbackComponent — you rarely need this directly.
   */
  setToken(token: string): void;
}

User

Shape of the decoded JWT payload exposed through AuthService.user.

interface User {
  sub: string;    // GitHub user ID
  name: string;   // GitHub display name
  email: string;  // GitHub email (if available)
  avatar: string; // GitHub avatar URL
  iat: number;    // Issued-at timestamp (seconds)
  exp: number;    // Expiry timestamp (seconds)
}

authGuard

Functional CanActivateFn. Redirects unauthenticated users to /login.

const authGuard: CanActivateFn;

Usage:

{ path: 'settings', component: SettingsComponent, canActivate: [authGuard] }

guestGuard

Functional CanActivateFn. Redirects already-authenticated users to /.

const guestGuard: CanActivateFn;

Usage:

{ path: 'login', component: AuthLoginComponent, canActivate: [guestGuard] }

authInterceptor

Functional HttpInterceptorFn. Auto-registered by provideAuth() — you do not need to add it manually. Attaches Authorization: Bearer <token> to every outbound HTTP request when a token is present.

const authInterceptor: HttpInterceptorFn;

AuthLoginComponent

Renders a "Sign in with GitHub" button that calls AuthService.login().

@Component({ selector: 'auth-login', ... })
class AuthLoginComponent {}

Use as a routed page or embed inline:

<!-- as a routed page (configured in routes) -->

<!-- inline -->
<auth-login />

AuthCallbackComponent

Reads the ?token= query parameter, calls AuthService.setToken(), then navigates to afterLoginUrl. Mount at the route matching your GitHub OAuth App callback URL.

@Component({ selector: 'auth-callback', ... })
class AuthCallbackComponent {}
// In app.routes.ts
{ path: 'auth/callback', component: AuthCallbackComponent }

AUTH_CONFIG

InjectionToken<AuthConfig> — the token under which provideAuth() registers the configuration. Useful for reading config values in custom extensions.

const AUTH_CONFIG: InjectionToken<AuthConfig>;
// Reading config in a custom service
readonly config = inject(AUTH_CONFIG);

Design Notes

Signals instead of RxJS

AuthService exposes Signal<T> values rather than Observable<T>. Signals are synchronous and do not require a subscription or the async pipe — template reads like auth.user()?.name just work. They also integrate natively with Angular's effect() and computed() APIs introduced in Angular 17+.

makeEnvironmentProviders() instead of forRoot()

forRoot() is a legacy pattern from NgModule-based apps. makeEnvironmentProviders() is the modern equivalent for standalone Angular: it scopes providers to the environment injector, prevents accidental use in component or route injectors, and composes naturally with bootstrapApplication.

No providedIn: 'root'

AuthService is not decorated with providedIn: 'root'. This is intentional: the service depends on AUTH_CONFIG, which is only available after provideAuth() has been called. Making it tree-shakable via providedIn: 'root' would allow it to be injected without the required configuration, causing a runtime error. By requiring explicit registration through provideAuth(), the dependency is always satisfied and the misconfiguration is impossible.


License

MIT