Socket
Book a DemoInstallSign in
Socket

@analog-tools/auth

Package Overview
Dependencies
Maintainers
1
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@analog-tools/auth

Authentication module for AnalogJS applications

0.0.9
latest
Source
npmnpm
Version published
Weekly downloads
21
-87.72%
Maintainers
1
Weekly downloads
 
Created
Source

@analog-tools/auth

⚠️ IMPORTANT: Early Development Stage ⚠️
This project is in its early development stage. Breaking changes may happen frequently as the APIs evolve. Use with caution in production environments.

A comprehensive authentication and authorization solution for AnalogJS applications, providing OAuth 2.0/OpenID Connect integration with session management.

npm version License: MIT

Features

  • 🔐 OAuth 2.0/OpenID Connect Support: Seamless integration with OAuth providers (Auth0, Keycloak, etc.)
  • 🚪 Route Protection: Easily protect routes requiring authentication
  • 🔄 Token Management: Automatic token refresh and expiration handling
  • 🍪 Session Management: Secure session handling with customizable storage options
  • 👤 User Management: Extensible user data handling and mapping
  • 🔒 Security Best Practices: CSRF protection, secure cookies, and proper token validation

Installation

npm install @analog-tools/auth

Quick Start

Add OAuth authentication to your AnalogJS application in just a few steps:

  • Configure middleware in your app:
// src/server/middleware/auth.ts
import { defineEventHandler, H3Event } from 'h3';
import { useAnalogAuth, AnalogAuthConfig } from '@analog-tools/auth';

const authConfig: AnalogAuthConfig = {
  issuer: process.env['AUTH_ISSUER'] || '',
  clientId: process.env['AUTH_CLIENT_ID'] || '',
  clientSecret: process.env['AUTH_CLIENT_SECRET'] || '',
  // Optional audience for providers like Auth0
  audience: process.env['AUTH_AUDIENCE'] || '',
  scope: process.env['AUTH_SCOPE'] || 'openid profile email',
  callbackUri: process.env['AUTH_CALLBACK_URL'] || 'http://localhost:3000/api/auth/callback',
  // Routes that don't require authentication
  unprotectedRoutes: ['/imprint', '/help'],
};

export default defineEventHandler(async (event: H3Event) => {
  return useAnalogAuth(authConfig, event);
});

Configuration Options

The useAnalogAuth function accepts a configuration object with the following options:

OptionTypeDescriptionRequired
issuerstringThe OAuth issuer URL (your Identity Provider)Yes
clientIdstringYour OAuth client IDYes
clientSecretstringYour OAuth client secretYes
audiencestringThe API audience (needed for certain providers like Auth0)No
scopestringOAuth scopes to request (defaults to 'openid profile email')No
callbackUristringThe callback URL registered with your OAuth providerYes
tokenRefreshApiKeystringAPI key for securing token refresh endpointsNo
unprotectedRoutesstring[]Array of routes that don't require authenticationNo
logoutUrlstringURL to redirect to after logoutNo
sessionStorageSessionStorageConfigSession storage configuration (see below)No
userHandlerUserHandlerCallbacks for user data processing (see below)No

Session Storage Options

By default, the auth package uses Redis for session storage. You can configure this with:

useAnalogAuth(
  {
    // ...other options
    sessionStorage: {
      type: 'redis',
      config: {
        host: 'localhost',
        port: 6379,
        password: 'your-password',
        db: 0,
        tls: false,
        keyPrefix: 'auth-session:',
        maxAge: 86400, // 24 hours in seconds
        sessionSecret: 'your-session-secret',
      },
    },
  },
  event
);

Alternative storage options:

Memory Storage (not recommended for production):

useAnalogAuth(
  {
    // ...other options
    sessionStorage: {
      type: 'memory',
      config: {
        sessionSecret: 'your-session-secret',
        maxAge: 86400  // 24 hours in seconds
      }
    }
  }, 
  event
);

⚠️ KNOWN ISSUE: The Memory storage option is currently broken and not functioning properly. Please use Redis or another storage backend until this issue is resolved.

User Data Handling

You can customize how user data is stored and retrieved with the userHandler option:

useAnalogAuth(
  {
    // ...other options
    userHandler: {
      // Called when a user authenticates - store user in your database
      createOrUpdateUser: async (userInfo) => {
        // Example: store or update user in your database
        const user = await db.users.upsert({
          where: { sub: userInfo.sub },
          update: {
            name: userInfo.name,
            email: userInfo.email,
            lastLogin: new Date(),
          },
          create: {
            sub: userInfo.sub,
            name: userInfo.name,
            email: userInfo.email,
          },
        });

        return user; // This becomes the user object in the session
      },

      // Map user data to what your application needs
      mapUserToLocal: (userInfo) => {
        // Return a simplified user object for your application
        return {
          id: userInfo.id,
          name: userInfo.name,
          email: userInfo.email,
          roles: userInfo.roles || [],
          isAdmin: userInfo.roles?.includes('admin') || false,
        };
      },
    },
  },
  event
);

Advanced Usage

Token Refresh Strategy

The package implements three token refresh strategies:

  • Lazy Refresh: Tokens are refreshed only when needed
  • Proactive Refresh: Tokens that are close to expiration are refreshed in the background
  • Scheduled Refresh: A scheduled task can refresh tokens before they expire

To implement scheduled refresh (recommended for production):

// src/server/routes/api/cron/refresh-tokens.ts
import { defineEventHandler } from 'h3';
import { inject } from '@analog-tools/inject';
import { OAuthAuthenticationService } from '@analog-tools/auth';

export default defineEventHandler(async () => {
  const authService = inject(OAuthAuthenticationService);
  const result = await authService.refreshExpiringTokens();

  return {
    message: `Token refresh complete. Refreshed: ${result.refreshed}, Failed: ${result.failed}, Total sessions: ${result.total}`,
  };
});

Then configure a CRON job to call this endpoint regularly (every 5 minutes is recommended).

CSRF Protection

The package includes CSRF protection by using the OAuth state parameter. Always verify this parameter in your callback handler as shown in the examples.

Securing API Routes

The middleware automatically protects all routes except those specified in unprotectedRoutes. For manual authentication checks in your API routes:

// src/server/routes/api/protected-data.ts
import { defineEventHandler, createError } from 'h3';
import { checkAuthentication } from '@analog-tools/auth';
import { inject } from '@analog-tools/inject';

export default defineEventHandler(async (event) => {
  // Manually check if user is authenticated
  if (!(await checkAuthentication(event))) {
    throw createError({
      statusCode: 401,
      message: 'Authentication required',
    });
  }
  
  // Access session data from event context
  const { session } = event.context;
  
  return {
    message: 'Protected data',
    user: session.user
  };
});

Client-Side Authentication

The package provides a complete Angular integration through the @analog-tools/auth/angular entry point. This integration includes:

  • An AuthService for managing authentication state
  • Route guards for protecting Angular routes
  • HTTP interceptors for handling 401 responses and authorization headers

TRPC Integration

The @analog-tools/auth/angular package provides seamless integration with tRPC for Angular applications, handling authentication automatically.

1. TRPC Client Setup

Use the createTrpcClientWithAuth function to wrap your TRPC client with authentication support:

// src/trpc-client.ts
import { AppRouter } from './server/trpc/routers';
import { createTrpcClient } from '@analogjs/trpc';
import { inject } from '@angular/core';
import { SuperJSON } from 'superjson';
import { createTrpcClientWithAuth } from '@analog-tools/auth-angular';
import { injectRequest } from '@analogjs/router/tokens';

// Create the TRPC client with AnalogJS
export const { provideTrpcClient, TrpcClient, TrpcHeaders } =
  createTrpcClient<AppRouter>({
    url: '/api/trpc',
    options: {
      transformer: SuperJSON,
    },
  });

// Create a function to inject the authenticated TRPC client
export function injectTrpcClient() {
  return createTrpcClientWithAuth(inject(TrpcClient), injectRequest(), TrpcHeaders);
}

2. TRPC Context Configuration

Set up your TRPC context to pass the H3 event:

// src/server/trpc/context.ts
import { inferAsyncReturnType } from '@trpc/server';
import type { H3Event } from 'h3';

export const createContext = (event: H3Event) => {
  // Pass the H3 event to tRPC context so we can access session data
  return { event };
};

export type Context = inferAsyncReturnType<typeof createContext>;

3. Authentication Middleware

Create an authentication middleware for protected routes:

// src/server/trpc/trpc.ts
import { initTRPC, TRPCError } from '@trpc/server';
import { Context } from './context';
import { SuperJSON } from 'superjson';
import { checkAuthentication } from '@analog-tools/auth';

const t = initTRPC.context<Context>().create({
  transformer: SuperJSON,
});

// Middleware to check if user is authenticated
const isAuthenticated = t.middleware(async ({ ctx, next }) => {
  if (!(await checkAuthentication(ctx.event))) {
    throw new TRPCError({
      code: 'UNAUTHORIZED',
      message: 'User is not authenticated',
    });
  }

  return next({
    ctx: {
      ...ctx,
      // You could add user info here if needed
    },
  });
});

// Unprotected procedure - can be accessed without authentication
export const publicProcedure = t.procedure;

// Protected procedure - requires authentication
export const protectedProcedure = t.procedure.use(isAuthenticated);

export const router = t.router;
export const middleware = t.middleware;

Using Protected TRPC Routes

Define your TRPC router with protected routes:

// src/server/trpc/routers/my-router.ts
import { protectedProcedure, publicProcedure, router } from '../trpc';

export const myRouter = router({
  // Public route - no authentication required
  public: publicProcedure.query(() => {
    return { message: 'This is public data' };
  }),
  
  // Protected route - requires authentication
  protected: protectedProcedure.query(() => {
    return { message: 'This is protected data' };
  }),
});

Error Handling

The auth-angular package automatically handles authentication errors from TRPC calls. The wrapTrpcClientWithErrorHandling function adds error handling for auth-related errors:

// In your component
import { Component } from '@angular/core';
import { injectTrpcClient } from '../trpc-client';

@Component({
  selector: 'app-my-component',
  template: `
    <button (click)="fetchProtectedData()">Fetch Protected Data</button>
    <div *ngIf="data">{{ data | json }}</div>
  `,
})
export class MyComponent {
  private trpc = injectTrpcClient();
  data: any;

  fetchProtectedData() {
    // Will automatically handle auth errors
    this.trpc.my.protected.query().subscribe({
      next: (result) => {
        this.data = result;
      },
      error: (err) => {
        console.error('Error fetching data:', err);
      },
    });
  }
}

Setup Angular Integration

First, add the auth providers to your app.config.ts:

// src/app/app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { provideFileRouter } from '@analogjs/router';
import { provideAuthClient, authInterceptor } from '@analog-tools/auth/angular';

export const appConfig: ApplicationConfig = {
  providers: [
    // AnalogJS providers
    provideFileRouter(),
    
    // HTTP client with auth interceptor
    provideHttpClient(
      withInterceptors([authInterceptor])
    ),
    
    // Auth client provider
    provideAuthClient(),
  ],
};

Using the Auth Service

Inject the provided AuthService in your components:

// src/app/pages/profile.page.ts
import { Component, inject, effect } from '@angular/core';
import { Router } from '@angular/router';
import { AuthService } from '@analog-tools/auth/angular';

@Component({
  standalone: true,
  template: `
    @if (auth.isLoading()) {
      <div>Loading...</div>
    } @else if (auth.user(); as user) {
      <div class="profile">
        <h1>Welcome, {{ user.name }}</h1>
        <p>Email: {{ user.email }}</p>
        <button (click)="auth.logout()">Logout</button>
      </div>
    } @else {
      <div>
        <h1>Please log in</h1>
        <button (click)="auth.login()">Login</button>
      </div>
    }
  `,
})
export default class ProfilePage {
  auth = inject(AuthService);
}

Using Route Guards

Protect your routes with the built-in auth guards:

// src/app/pages/admin.page.ts
import { Component } from '@angular/core';
import { authGuard, roleGuard } from '@analog-tools/auth/angular';

export const routeMeta = {
  title: 'Admin Page',
  canActivate: [authGuard], // Requires authentication
};

@Component({
  template: `<h1>Admin Page</h1>`,
})
export default class AdminPage {}

For role-based access control, use the roleGuard with route data:

// src/app/pages/super-admin.page.ts
import { Component } from '@angular/core';
import { roleGuard } from '@analog-tools/auth/angular';

export const routeMeta = {
  title: 'Super Admin Page',
  canActivate: [roleGuard],
  data: {
    roles: ['admin', 'super-admin'], // Requires any of these roles
  },
};

@Component({
  template: `<h1>Super Admin Panel</h1>`,
})
export default class SuperAdminPage {}

User Authentication Management

The package automatically handles user data transformation from various OAuth providers into a standardized format through the AuthService:

// src/app/services/user.service.ts
import { Injectable, inject } from '@angular/core';
import { AuthService, AuthUser } from '@analog-tools/auth/angular';

@Injectable({
  providedIn: 'root'
})
export class UserService {
  private authService = inject(AuthService);
  
  // The AuthService automatically handles user data transformation
  getCurrentUser(): AuthUser | null {
    return this.authService.user();
  }
  
  hasAdminAccess(): boolean {
    return this.authService.hasRoles(['admin']);
  }
}
Supported Identity Providers

The package automatically handles user data from various OAuth providers:

  • Auth0: Properly handles Auth0 user profile data and roles
  • Keycloak: Correctly maps realm and client roles
  • Generic OIDC: Supports standard OpenID Connect claims

The AuthService internally handles all the transformation logic, so you don't need to worry about the specifics of each provider.


#### Auth Service API

The `AuthService` provides several key methods and properties:

```typescript
// Core user state
user: Signal<AuthUser | null>;       // User data
isAuthenticated: Signal<boolean>;    // Is user authenticated
isLoading: Signal<boolean>;          // Auth state loading indicator

// Methods
login(targetUrl?: string): void;     // Redirect to login, with optional return URL
logout(): void;                      // Logout and redirect
checkAuthentication(): Promise<boolean>; // Force auth status check
hasRoles(roles: string[]): boolean;  // Check if user has specified roles

Security Considerations

Authentication Best Practices

  • Environment Variables: Store sensitive values like clientSecret in environment variables
  • HTTPS Required: Always use HTTPS in production environments
  • Secure Cookies: The package configures secure cookies in production automatically
  • Token Storage: Tokens are only stored server-side, never exposed to the client
  • Token Validation: All tokens are properly validated before use
  • CSRF Protection: State parameter validation prevents cross-site request forgery

Production Setup Checklist

  • Set NODE_ENV=production to enable secure defaults
  • Configure a strong random SESSION_SECRET for cookie signing
  • Use Redis or another persistent store for sessions (in-memory is not suitable for production)
  • Set up token refresh mechanism (preferably scheduled refresh)
  • Configure proper CORS settings if your API is on a different domain
  • Implement rate limiting for auth endpoints to prevent brute force attacks

Vite Configuration

When using @analog-tools/auth with AnalogJS, you need to configure Vite to properly handle the package during server-side rendering. Add the package to the noExternal array in your vite.config.ts:

// vite.config.ts
import analog from '@analogjs/platform';
import { defineConfig } from 'vite';

export default defineConfig({
  // ...other config
  ssr: {
    noExternal: ['@analogjs/trpc', '@trpc/server', '@analog-tools/auth'],
  },
  // ...other config
});

Troubleshooting

Common Issues

Error: Failed to fetch OpenID configuration

  • Check your internet connection
  • Verify the issuer URL is correct
  • Ensure the OAuth provider is online

Error: Failed to exchange authorization code

  • Check that clientId and clientSecret are correct
  • Verify that callbackUri matches what's registered with your provider

Error: Failed to refresh token

  • Token might be expired or revoked
  • Verify that the refresh token is valid
  • Check if your OAuth provider limits refresh token use

Debugging

Enable detailed logging by setting the environment variable:

DEBUG=analog-auth:*

Environment Setup

For local development, create a .env file with the following variables:

AUTH_ISSUER=https://your-issuer.com
AUTH_CLIENT_ID=your-client-id
AUTH_CLIENT_SECRET=your-client-secret
AUTH_AUDIENCE=your-audience
AUTH_SCOPE=openid profile email
AUTH_CALLBACK_URL=http://localhost:3000/api/auth/callback
SESSION_SECRET=your-session-secret
REDIS_URL=redis://localhost:6379
AUTH_LOGOUT_URL=http://localhost:3000

Examples

Complete Integration Example

This example shows how to set up a comprehensive authentication solution with the merged package:

Server-side Setup

// src/server/middleware/auth.ts
import { defineEventHandler, H3Event } from 'h3';
import { useAnalogAuth, AnalogAuthConfig } from '@analog-tools/auth';

// Define auth configuration
const authConfig: AnalogAuthConfig = {
  issuer: 'https://keycloak.your-domain.com/realms/your-realm',
  clientId: process.env['AUTH_CLIENT_ID'] || '',
  clientSecret: process.env['AUTH_CLIENT_SECRET'] || '',
  scope: 'openid profile email',
  callbackUri: 'http://localhost:3000/api/auth/callback',
  unprotectedRoutes: ['/api/public', '/api/auth/login', '/api/auth/callback'],
  // Configure Redis session storage for production
  sessionStorage: {
    type: 'redis',
    config: {
      url: process.env['REDIS_URL'] || 'redis://localhost:6379',
      ttl: 86400, // 24 hours
      sessionSecret: process.env['SESSION_SECRET'] || 'your-session-secret',
    },
  },
  // Custom user data handling
  userHandler: {
    mapUserToLocal: (userInfo) => ({
      id: userInfo.sub,
      name: userInfo.name,
      email: userInfo.email,
      roles: userInfo.realm_access?.roles || [],
    }),
    createOrUpdateUser: async (user) => {
      // Store or update user in your database
      console.log('User authenticated:', user);
      return user;
    },
  },
};

export default defineEventHandler(async (event: H3Event) => {
  return useAnalogAuth(authConfig, event);
});

Angular Client Setup

// src/app/app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { provideFileRouter, requestContextInterceptor } from '@analogjs/router';
import { provideClientHydration } from '@angular/platform-browser';
import { 
  provideAuthClient, 
  authInterceptor 
} from '@analog-tools/auth/angular';
import { provideTrpcClient } from '../trpc-client';

export const appConfig: ApplicationConfig = {
  providers: [
    provideFileRouter(),
    provideClientHydration(),
    
    // Auth configuration
    provideAuthClient(),
    
    // HTTP configuration with auth interceptor
    provideHttpClient(
      withInterceptors([
        requestContextInterceptor, 
        authInterceptor
      ])
    ),
    
    // TRPC client with auth integration
    provideTrpcClient(),
  ],
};

Protected Route Example

// src/app/pages/protected.page.ts
import { Component } from '@angular/core';
import { authGuard } from '@analog-tools/auth/angular';

export const routeMeta = {
  title: 'Protected Page',
  canActivate: [authGuard],
};

@Component({
  template: `
    <div class="p-4">
      <h1 class="text-2xl font-bold mb-4">Protected Content</h1>
      <p>This page is only visible to authenticated users.</p>
    </div>
  `,
})
export default class ProtectedPage {}

Package Architecture

The @analog-tools/auth package is structured as a multi-entry point package that provides a comprehensive authentication solution:

  • Main Entry Point: @analog-tools/auth

    • Server-side OAuth implementation with H3 middleware
    • Session management integration
    • API route handlers
  • Angular Entry Point: @analog-tools/auth/angular

    • Angular-specific authentication services
    • Route guards and HTTP interceptors
    • Reactive state management with Angular signals
    • User transformation utilities for different providers (Auth0, Keycloak, etc.)
    • tRPC middleware for protected procedures
    • Authentication utilities for tRPC routes
    • Error handling for authentication failures

Relationship with Other Packages

This package relies on other @analog-tools packages:

  • @analog-tools/session: For secure session management
  • @analog-tools/inject: For dependency injection
  • @analog-tools/logger: For structured logging

Contributing

Contributions are welcome! Please check out our contribution guidelines.

License

MIT

Keywords

analogjs

FAQs

Package last updated on 21 Aug 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

About

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.

  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc

U.S. Patent No. 12,346,443 & 12,314,394. Other pending.