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

agg-labs-sso-sdk

v1.0.5

Published

Officail AGG Labs SSO SDK for JavaScript/TypeScript

Readme

AGG SSO ID SDK

Official TypeScript/JavaScript SDK for integrating with AGG Single Sign-On (SSO) Identity services. This library provides a production-ready implementation of OAuth 2.0 Authorization Code Flow with PKCE (Proof Key for Code Exchange) and full OpenID Connect (OIDC) compliance.

Table of Contents

Features

Core Capabilities

  • Zero External Dependencies: Built exclusively using native Web Crypto and Fetch APIs, ensuring minimal bundle size and maximum compatibility.
  • PKCE Built-in: Automatic generation and management of code_verifier and code_challenge parameters for enhanced security.
  • Full OIDC Support: Implements OpenID Connect protocol with ID token validation, user info endpoints, and refresh token rotation.
  • Isomorphic Runtime: Seamlessly operates in multiple JavaScript environments including Node.js, browsers, Next.js, Cloudflare Workers, and other edge computing platforms.
  • Strong TypeScript Support: Complete type definitions with comprehensive JSDoc documentation for excellent IDE integration.
  • Client Secret Support: Optional server-side client secret configuration for confidential client applications.
  • Token Management: Built-in refresh token handling, token revocation, and secure token storage patterns.

Installation

Install the SDK using npm:

npm install agg-labs-sso-sdk

Or using yarn:

yarn add agg-labs-sso-sdk

Or using pnpm:

pnpm add agg-labs-sso-sdk

Configuration

Basic Configuration (Public Client)

For browser-based applications without a backend:

import { AggClient } from 'agg-labs-sso-sdk';

const auth = new AggClient({
  clientId: 'your-service-provider-id',
  redirectUri: 'https://your-app.com/api/auth/callback'
});

Advanced Configuration (Confidential Client)

For server-side applications with a client secret:

import { AggClient } from 'agg-labs-sso-sdk';

const auth = new AggClient({
  clientId: 'your-service-provider-id',
  clientSecret: process.env.AGG_CLIENT_SECRET, // Server-side only
  redirectUri: 'https://your-app.com/api/auth/callback',
  scopes: ['openid', 'profile', 'email', 'offline_access'],
  issuerUrl: 'https://sso.agg.com' // Pre-configured, override if needed
});

Configuration Parameters

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | clientId | string | Yes | Your application's unique identifier issued by AGG SSO | | clientSecret | string | No | Server-side secret for token exchange. Use only in backend environments | | redirectUri | string | Yes | The exact callback URL registered in your SSO application settings | | scopes | string[] | No | OIDC scopes to request. Defaults to ['openid', 'profile', 'email'] | | issuerUrl | string | No | AGG SSO issuer URL. Pre-configured to https://sso.agg.com |

Authentication Flow

OAuth 2.0 Authorization Code Flow with PKCE

The SDK implements the industry-standard OAuth 2.0 Authorization Code Flow optimized for OIDC:

┌──────────────┐                              ┌──────────────────┐
│              │                              │                  │
│  Your App    │                              │   AGG SSO        │
│  (Client)    │                              │   (Auth Server)  │
│              │                              │                  │
└──────────────┘                              └──────────────────┘
       │                                               │
       │  1. createAuthorizeUrl()                      │
       │     (Generate code_verifier, state)           │
       │                                               │
       │  2. Redirect to Authorization URL             │
       ├──────────────────────────────────────────────>│
       │                                               │
       │                                    User logs in
       │                                               │
       │  3. Redirect to Callback URL                  │
       │     (with authorization code, state)          │
       │<──────────────────────────────────────────────┤
       │                                               │
       │  4. exchangeToken()                           │
       │     (Exchange code + code_verifier)           │
       ├──────────────────────────────────────────────>│
       │                                               │
       │  5. Return access_token, id_token             │
       │<──────────────────────────────────────────────┤
       │                                               │
       │  6. getUserInfo()                             │
       │     (Optional user profile fetch)             │
       ├──────────────────────────────────────────────>│
       │                                               │
       │  7. User Profile Data                         │
       │<──────────────────────────────────────────────┤
       │                                               │

API Reference

createAuthorizeUrl(scopes?: string[]): Promise<AuthorizeUrlResponse>

Generates the authorization URL that the user should be redirected to for login.

Parameters:

  • scopes (optional): Array of OIDC scopes. Defaults to client's configured scopes.

Returns:

interface AuthorizeUrlResponse {
  url: string;           // Complete authorization URL
  state: string;         // CSRF protection token
  codeVerifier: string;  // PKCE code verifier (must be stored securely)
}

Example:

const { url, state, codeVerifier } = await auth.createAuthorizeUrl();

// Store in secure session/cookie
session.set('auth_state', state);
session.set('code_verifier', codeVerifier);

// Redirect user
window.location.href = url;

Security Note: Always store state and codeVerifier in secure, httpOnly cookies or server-side sessions.


exchangeToken(code: string, codeVerifier: string): Promise<TokenResponse>

Exchanges the authorization code for access and ID tokens.

Parameters:

  • code: Authorization code from the callback URL (query parameter code)
  • codeVerifier: The code verifier stored during createAuthorizeUrl()

Returns:

interface TokenResponse {
  access_token: string;      // JWT for API access
  id_token: string;          // JWT containing user identity claims
  refresh_token?: string;    // Token for obtaining new access tokens
  token_type: string;        // Always 'Bearer'
  expires_in: number;        // Access token expiration in seconds
}

Example:

try {
  const tokens = await auth.exchangeToken(
    req.query.code,
    session.get('code_verifier')
  );
  
  // Securely store tokens
  session.set('access_token', tokens.access_token);
  session.set('id_token', tokens.id_token);
  
  if (tokens.refresh_token) {
    session.set('refresh_token', tokens.refresh_token);
  }
} catch (error) {
  console.error('Token exchange failed:', error);
  // Redirect to login
}

refreshToken(refreshToken: string): Promise<TokenResponse>

Obtains a new access token using a valid refresh token. Use this when the access token expires.

Parameters:

  • refreshToken: The refresh token obtained from exchangeToken()

Returns: TokenResponse (same as exchangeToken)

Example:

try {
  const newTokens = await auth.refreshToken(session.get('refresh_token'));
  session.set('access_token', newTokens.access_token);
  session.set('expires_at', Date.now() + newTokens.expires_in * 1000);
} catch (error) {
  console.error('Token refresh failed:', error);
  // Redirect to login
}

getUserInfo(accessToken: string): Promise<UserInfo>

Retrieves user profile information from the UserInfo endpoint.

Parameters:

  • accessToken: Valid access token

Returns:

interface UserInfo {
  sub: string;              // Subject (user unique ID)
  email?: string;           // User email
  email_verified?: boolean; // Email verification status
  name?: string;            // Full name
  given_name?: string;      // First name
  family_name?: string;     // Last name
  picture?: string;         // Profile picture URL
  [key: string]: any;       // Additional custom claims
}

Example:

const userInfo = await auth.getUserInfo(tokens.access_token);
console.log(`Authenticated as: ${userInfo.email}`);
console.log(`User ID: ${userInfo.sub}`);

revokeToken(token: string): Promise<void>

Revokes a refresh or access token, invalidating it immediately.

Parameters:

  • token: The token to revoke

Example:

try {
  await auth.revokeToken(session.get('refresh_token'));
  console.log('Token revoked successfully');
} catch (error) {
  console.error('Token revocation failed:', error);
}

logout(cookieHeader?: string): Promise<void>

Initiates a global logout from the AGG SSO system.

Parameters:

  • cookieHeader (optional): Cookie header string for server-side logout requests

Example:

// Client-side logout
await auth.logout();
session.clear();
window.location.href = '/';

// Server-side logout
await auth.logout(req.headers.cookie);

Advanced Usage

Session Management and Token Refresh

Implement automatic token refresh for seamless user experience:

class AuthManager {
  private tokens: TokenResponse;
  private refreshInterval: NodeJS.Timeout;

  constructor(private auth: AggClient) {}

  async initialize(code: string, codeVerifier: string) {
    this.tokens = await this.auth.exchangeToken(code, codeVerifier);
    this.setupAutoRefresh();
  }

  private setupAutoRefresh() {
    const expiresIn = this.tokens.expires_in;
    // Refresh 1 minute before expiration
    const refreshTime = (expiresIn - 60) * 1000;

    this.refreshInterval = setInterval(async () => {
      try {
        this.tokens = await this.auth.refreshToken(this.tokens.refresh_token);
      } catch (error) {
        console.error('Auto-refresh failed:', error);
        this.logout();
      }
    }, refreshTime);
  }

  getAccessToken(): string {
    return this.tokens.access_token;
  }

  async logout() {
    clearInterval(this.refreshInterval);
    try {
      await this.auth.revokeToken(this.tokens.refresh_token);
    } catch (error) {
      console.error('Token revocation during logout failed:', error);
    }
  }
}

Using with Server-Side Client Secret

For applications with a secure backend:

// Backend initialization (Node.js/Express)
const auth = new AggClient({
  clientId: process.env.AGG_CLIENT_ID,
  clientSecret: process.env.AGG_CLIENT_SECRET, // Keep secret on backend only
  redirectUri: 'https://your-app.com/api/auth/callback',
  scopes: ['openid', 'profile', 'email', 'offline_access']
});

// In your auth callback route
app.get('/api/auth/callback', async (req, res) => {
  const { code, state } = req.query;

  // Verify state
  if (state !== req.session.auth_state) {
    return res.status(400).json({ error: 'Invalid state parameter' });
  }

  try {
    const tokens = await auth.exchangeToken(
      code as string,
      req.session.code_verifier
    );

    // Store tokens securely
    req.session.tokens = tokens;
    req.session.userId = jwt.decode(tokens.id_token).sub;

    res.redirect('/dashboard');
  } catch (error) {
    console.error('Authentication failed:', error);
    res.redirect('/login?error=auth_failed');
  }
});

Implementing Protected Routes

// Middleware to check authentication
async function authRequired(req, res, next) {
  if (!req.session.tokens?.access_token) {
    return res.redirect('/login');
  }

  try {
    // Check if token needs refresh
    const decoded = jwt.decode(req.session.tokens.access_token);
    const expiresAt = decoded.exp * 1000;

    if (Date.now() > expiresAt - 60000) { // Refresh if expiring soon
      req.session.tokens = await auth.refreshToken(
        req.session.tokens.refresh_token
      );
    }

    req.user = jwt.decode(req.session.tokens.id_token);
    next();
  } catch (error) {
    req.session.destroy();
    res.redirect('/login?error=session_expired');
  }
}

app.get('/api/user/profile', authRequired, (req, res) => {
  res.json(req.user);
});

Security Best Practices

1. Store Credentials Securely

Never expose clientSecret in client-side code:

// GOOD: Server-side only
const auth = new AggClient({
  clientId: process.env.AGG_CLIENT_ID,
  clientSecret: process.env.AGG_CLIENT_SECRET // Backend only
});

// BAD: Never do this
const auth = new AggClient({
  clientSecret: 'sk_live_...' // Client-side exposure
});

2. Use HTTPS Only

Always use HTTPS for production. Redirect URIs must use HTTPS:

// Production redirect URI
redirectUri: 'https://your-app.com/api/auth/callback'

// Development only
redirectUri: 'http://localhost:3000/api/auth/callback'

3. Validate State Parameter

Always verify the state parameter to prevent CSRF attacks:

const { url, state } = await auth.createAuthorizeUrl();
session.set('auth_state', state);

// In callback handler
if (req.query.state !== session.get('auth_state')) {
  throw new Error('State validation failed - possible CSRF attack');
}

4. Use HttpOnly Cookies

Store tokens in httpOnly cookies to protect against XSS:

// Express.js example
res.cookie('access_token', tokens.access_token, {
  httpOnly: true,
  secure: true,        // HTTPS only
  sameSite: 'strict',  // CSRF protection
  maxAge: tokens.expires_in * 1000
});

5. Implement Token Refresh

Keep access tokens short-lived and use refresh tokens:

// Set up automatic refresh before expiration
const expiresIn = tokens.expires_in;
setTimeout(() => {
  refreshToken(tokens.refresh_token);
}, (expiresIn - 300) * 1000); // Refresh 5 minutes before expiry

Examples

Next.js Integration

// pages/api/auth/login.ts
import { NextApiRequest, NextApiResponse } from 'next';
import { AggClient } from 'agg-labs-sso-sdk';

const auth = new AggClient({
  clientId: process.env.NEXT_PUBLIC_AGG_CLIENT_ID!,
  redirectUri: `${process.env.NEXT_PUBLIC_APP_URL}/api/auth/callback`
});

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const { url, state, codeVerifier } = await auth.createAuthorizeUrl();

  // Store in HTTP-only cookie
  res.setHeader('Set-Cookie', [
    `auth_state=${state}; HttpOnly; Secure; Path=/`,
    `code_verifier=${codeVerifier}; HttpOnly; Secure; Path=/`
  ]);

  res.redirect(url);
}

React with Context API

import React, { createContext, useState, useCallback } from 'react';
import { AggClient, TokenResponse } from 'agg-labs-sso-sdk';

const AuthContext = createContext<AuthContextType | undefined>(undefined);

const auth = new AggClient({
  clientId: process.env.REACT_APP_AGG_CLIENT_ID!,
  redirectUri: `${window.location.origin}/auth/callback`
});

export function AuthProvider({ children }) {
  const [tokens, setTokens] = useState<TokenResponse | null>(null);
  const [user, setUser] = useState(null);

  const login = useCallback(async () => {
    const { url, state, codeVerifier } = await auth.createAuthorizeUrl();
    
    localStorage.setItem('auth_state', state);
    localStorage.setItem('code_verifier', codeVerifier);
    
    window.location.href = url;
  }, []);

  const handleCallback = useCallback(async (code: string) => {
    const codeVerifier = localStorage.getItem('code_verifier');
    const newTokens = await auth.exchangeToken(code, codeVerifier!);
    
    const userInfo = await auth.getUserInfo(newTokens.access_token);
    
    setTokens(newTokens);
    setUser(userInfo);
  }, []);

  const logout = useCallback(async () => {
    if (tokens?.refresh_token) {
      await auth.revokeToken(tokens.refresh_token);
    }
    setTokens(null);
    setUser(null);
  }, [tokens]);

  return (
    <AuthContext.Provider value={{ tokens, user, login, logout, handleCallback }}>
      {children}
    </AuthContext.Provider>
  );
}

Troubleshooting

Common Issues

Issue: "Invalid redirect_uri"

Cause: The redirect URI in your code doesn't match what's registered in AGG SSO.

Solution: Verify the exact redirect URI registered in your SSO application settings:

const auth = new AggClient({
  clientId: 'your-client-id',
  redirectUri: 'https://your-app.com/api/auth/callback' // Must match exactly
});

Issue: "PKCE required" or "code_challenge missing"

Cause: The SDK wasn't able to generate PKCE parameters.

Solution: Ensure Web Crypto API is available. For older Node.js versions:

// Node.js < 15
import crypto from 'crypto';
global.crypto = crypto.webcrypto;

Issue: "Token expired" or 401 errors

Cause: The access token has expired.

Solution: Implement token refresh logic:

if (error.status === 401) {
  const newTokens = await auth.refreshToken(refreshToken);
  // Retry original request with new access token
}

Issue: CORS errors when calling getUserInfo

Cause: Cross-origin requests are blocked.

Solution: Call getUserInfo from the backend, not the browser:

// Backend
app.get('/api/user', authRequired, async (req, res) => {
  const userInfo = await auth.getUserInfo(req.session.access_token);
  res.json(userInfo);
});

// Frontend
const response = await fetch('/api/user');
const userInfo = await response.json();

Support and Contributing

For issues, questions, or contributions, please visit the GitHub repository.

License

MIT License - See LICENSE file for details

License

ISC © AGG Labs