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

@hamak/auth

v0.5.1

Published

Auth - Complete authentication plugin with password, OAuth2, and Keycloak support

Readme

@hamak/auth

A complete authentication system for the Hamak App Framework, supporting password-based login, OAuth2, and Keycloak authentication.

Features

  • 🔐 Multiple Authentication Strategies - Password, OAuth2, Keycloak
  • 🔄 Automatic Token Refresh - Proactive refresh before expiry
  • 💾 Flexible Token Storage - localStorage, sessionStorage, or memory
  • 🛡️ PKCE Support - Secure OAuth2 for public clients
  • ⚛️ React Integration - Providers, hooks, and guard components
  • 🏪 Redux Integration - Auth state in your store
  • 🔌 Plugin Architecture - Integrates with microkernel plugin system

Packages

| Package | Description | |---------|-------------| | @hamak/auth-api | Interfaces, types, and DI tokens | | @hamak/auth-spi | Extension points for custom strategies | | @hamak/auth-impl | Core implementations and plugin factory | | @hamak/auth-templates | React providers, hooks, and components |

Installation

npm install @hamak/auth-api @hamak/auth-spi @hamak/auth-impl @hamak/auth-templates

Quick Start

1. Configure the Auth Plugin

import { createAuthPlugin } from '@hamak/auth-impl';

// Password-based authentication
const authPlugin = createAuthPlugin({
  strategy: 'password',
  password: {
    loginEndpoint: '/api/auth/login',
    refreshEndpoint: '/api/auth/refresh',
    logoutEndpoint: '/api/auth/logout',
    userInfoEndpoint: '/api/auth/me'
  },
  tokenStorage: 'localStorage',
  autoRefresh: {
    enabled: true,
    threshold: 60000 // Refresh 1 min before expiry
  }
});

// Or OAuth2
const oauthPlugin = createAuthPlugin({
  strategy: 'oauth2',
  oauth2: {
    clientId: 'my-app',
    authorizationUrl: 'https://auth.example.com/authorize',
    tokenUrl: 'https://auth.example.com/token',
    userInfoUrl: 'https://auth.example.com/userinfo',
    redirectUri: 'http://localhost:3000/callback',
    scope: ['openid', 'profile', 'email']
  }
});

// Or Keycloak
const keycloakPlugin = createAuthPlugin({
  strategy: 'keycloak',
  keycloak: {
    realm: 'my-realm',
    serverUrl: 'https://keycloak.example.com',
    clientId: 'my-app',
    redirectUri: 'http://localhost:3000/callback',
    enableDirectGrant: true // Allow password login
  }
});

2. Register the Plugin

import { Host } from '@hamak/microkernel-impl';

const host = new Host();
host.registerPlugin('auth', manifest, authPlugin);
await host.bootstrapAllAtRoot();

3. Setup React Provider

import { AuthProvider } from '@hamak/auth-templates';
import { AUTH_SERVICE_TOKEN } from '@hamak/auth-api';

function App() {
  const [authService, setAuthService] = useState(null);

  useEffect(() => {
    host.bootstrapAllAtRoot().then(() => {
      const ctx = host.rootActivationCtx;
      setAuthService(ctx.resolve(AUTH_SERVICE_TOKEN));
    });
  }, []);

  if (!authService) return <Loading />;

  return (
    <AuthProvider authService={authService}>
      <YourApp />
    </AuthProvider>
  );
}

4. Use Auth in Components

import { useAuth, useLogin, useLogout, RequireAuth, RequireRole } from '@hamak/auth-templates';

// Login form
function LoginPage() {
  const { loginWithPassword, loading, error } = useLogin();

  const handleSubmit = async (e) => {
    e.preventDefault();
    const result = await loginWithPassword(username, password);
    if (result.success) {
      navigate('/dashboard');
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      {error && <Alert>{error.message}</Alert>}
      <input name="username" />
      <input name="password" type="password" />
      <button disabled={loading}>Login</button>
    </form>
  );
}

// Protected content
function Dashboard() {
  const { user } = useAuth();
  const { logout } = useLogout();

  return (
    <RequireAuth fallback={<Loading />}>
      <h1>Welcome, {user?.name}</h1>

      <RequireRole role="admin">
        <AdminPanel />
      </RequireRole>

      <button onClick={logout}>Logout</button>
    </RequireAuth>
  );
}

Available Hooks

| Hook | Description | |------|-------------| | useAuth() | Full auth context with state and operations | | useAuthState() | Auth state only (isAuthenticated, user, loading) | | useUser() | Current user or null | | useLogin() | Login operations with loading/error state | | useLogout() | Logout operation with loading state | | useRoles() | Role checking utilities | | usePermissions() | Permission checking utilities | | useOAuthCallback() | Handle OAuth redirect callbacks |

Guard Components

// Require authentication
<RequireAuth fallback={<Loading />}>
  <ProtectedContent />
</RequireAuth>

// Require specific role
<RequireRole role="admin" unauthorizedContent={<AccessDenied />}>
  <AdminPanel />
</RequireRole>

// Require any of multiple roles
<RequireRole roles={['admin', 'moderator']}>
  <ModeratorTools />
</RequireRole>

// Require permission
<RequirePermission permission="document:write">
  <EditButton />
</RequirePermission>

Higher-Order Components

import { withAuth, withRole, withPermission } from '@hamak/auth-templates';

// Protect a component
const ProtectedDashboard = withAuth(Dashboard, {
  fallback: <Loading />,
  onUnauthenticated: () => navigate('/login')
});

// Require role
const AdminPanel = withRole(Panel, {
  role: 'admin',
  unauthorizedComponent: AccessDenied
});

Plugin Commands

The auth plugin registers these commands:

ctx.commands.run('auth.login', credentials);
ctx.commands.run('auth.logout');
ctx.commands.run('auth.refresh');
ctx.commands.run('auth.isAuthenticated');
ctx.commands.run('auth.getCurrentUser');
ctx.commands.run('auth.hasRole', 'admin');
ctx.commands.run('auth.hasPermission', 'document:write');
ctx.commands.run('auth.initiateOAuth', 'keycloak');

Plugin Events

Subscribe to auth events via hooks:

ctx.hooks.on('auth:login-success', ({ user }) => {
  console.log('User logged in:', user);
});

ctx.hooks.on('auth:logout', () => {
  console.log('User logged out');
});

ctx.hooks.on('auth:session-expired', () => {
  console.log('Session expired');
});

ctx.hooks.on('auth:token-refresh', ({ user }) => {
  console.log('Token refreshed');
});

Configuration Options

interface AuthPluginConfig {
  // Primary strategy
  strategy: 'password' | 'oauth2' | 'keycloak';

  // Password strategy config
  password?: {
    loginEndpoint: string;
    refreshEndpoint: string;
    logoutEndpoint: string;
    userInfoEndpoint?: string;
  };

  // OAuth2 config
  oauth2?: {
    clientId: string;
    authorizationUrl: string;
    tokenUrl: string;
    userInfoUrl?: string;
    redirectUri: string;
    scope: string[];
    usePkce?: boolean; // default: true
  };

  // Keycloak config
  keycloak?: {
    realm: string;
    serverUrl: string;
    clientId: string;
    redirectUri: string;
    scope?: string[];
    enableDirectGrant?: boolean;
  };

  // Storage
  tokenStorage?: 'localStorage' | 'sessionStorage' | 'memory';
  storageKeyPrefix?: string;

  // Auto refresh
  autoRefresh?: {
    enabled: boolean;
    threshold?: number; // ms before expiry
    checkInterval?: number; // ms between checks
  };

  // Routes
  routes?: {
    afterLogin?: string;
    afterLogout?: string;
    login?: string;
    unauthorized?: string;
  };

  debug?: boolean;
}

Custom Strategy

Implement your own authentication strategy:

import type { IAuthStrategy } from '@hamak/auth-spi';

class CustomStrategy implements IAuthStrategy {
  readonly type = 'custom';
  readonly name = 'my-custom-auth';

  async authenticate(credentials) {
    // Your authentication logic
    return { success: true, user, accessToken, refreshToken };
  }

  async refreshToken(refreshToken) {
    // Your refresh logic
    return { success: true, accessToken };
  }

  async logout(accessToken) {
    // Your logout logic
  }
}

Security Considerations

  • PKCE: Enabled by default for OAuth2 flows
  • State Parameter: CSRF protection for OAuth callbacks
  • Token Storage: Use memory for highest security (tokens lost on refresh)
  • Auto Refresh: Prevents session expiry during active use
  • JWT Validation: Server-side validation required; client-side is for UX only

Demo

Run the demo app to see auth in action:

npm run demo:dev

Navigate to the Auth Demo tab to test:

  • Login with demo/demo123 (user + editor roles)
  • Login with admin/admin123 (all roles)
  • Login with guest/guest123 (guest role only)

API Reference

See DESIGN.md for detailed architecture documentation.

License

MIT