admGitea
  • Joined on 2026-01-03

hessutech-auth-consumer (1.2.1)

Published 2026-01-04 15:36:16 +00:00 by admGitea

Installation

registry=
npm install hessutech-auth-consumer@1.2.1
"hessutech-auth-consumer": "1.2.1"

About this package

hessutech-auth-consumer

Reusable JWT authentication middleware for Hessutech services

Purpose

Standardizes authentication across all Hessutech services (Service B, Service C, etc.) by providing a turnkey Fastify plugin that validates Hessu-JWT tokens issued by The Sentinel. Uses RS256 asymmetric cryptography for secure, scalable token verification without shared secrets.

Features

  • RS256 JWT Verification: Asymmetric public key cryptography (no shared secrets)
  • Fastify Plugin: Drop-in authentication with automatic request.user decoration
  • App Claim Validation: Prevents cross-service token reuse
  • TypeScript-First: Full type safety with strict typing
  • Flexible Key Management: Static public key or dynamic fetching from Sentinel
  • Zero Configuration: Works out of the box with sensible defaults

Installation

1. Configure NPM Registry

Create or edit .npmrc in your project root:

@hessutech:registry=https://reg.hessutech.fi/api/packages/admGitea/npm/
//reg.hessutech.fi/api/packages/admGitea/npm/:_authToken=${GITEA_NPM_TOKEN}

Set your authentication token:

export GITEA_NPM_TOKEN="your-gitea-access-token"

2. Install Package

npm install hessutech-auth-consumer

Configuration

Environment Variables

Create a .env file in your service:

# Required: Sentinel URL for dynamic key fetching (recommended)
SENTINEL_URL=https://sentinel.hessutech.fi

# Optional: Static public key (alternative to dynamic fetching)
HESSU_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----"

# Required: Your application identifier
SERVICE_APP_ID=service-b

# Optional: Server port
PORT=3001

Configuration Options

Variable Required Description
SENTINEL_URL Yes (if no static key) Base URL of The Sentinel service
HESSU_PUBLIC_KEY Yes (if no Sentinel URL) PEM-encoded RS256 public key
SERVICE_APP_ID Yes Application slug (e.g., service-b, service-c)

Recommended: Use SENTINEL_URL for automatic key rotation support. Static keys require manual updates when rotated.

Usage

Basic Setup

import Fastify from 'fastify';
import { hessuAuthPlugin } from 'hessutech-auth-consumer';

const server = Fastify({ logger: true });

// Public routes (no authentication)
server.get('/health', async () => {
  return { status: 'ok' };
});

// Protected routes (authentication required)
await server.register(async (protectedRoutes) => {
  await protectedRoutes.register(hessuAuthPlugin, {
    sentinelUrl: process.env.SENTINEL_URL,
    expectedApp: process.env.SERVICE_APP_ID,
  });

  protectedRoutes.get('/api/me', async (request) => {
    // request.user is automatically populated
    return {
      userId: request.user.sub,
      app: request.user.app,
      roles: request.user.roles,
    };
  });
});

await server.listen({ port: 3001, host: '0.0.0.0' });

Role-Based Access Control

protectedRoutes.get('/api/admin/stats', async (request, reply) => {
  // Check user role
  if (!request.user.roles.includes('admin')) {
    return reply.code(403).send({
      error: 'FORBIDDEN',
      message: 'Admin role required',
    });
  }

  return { stats: { totalUsers: 1234 } };
});

Multiple Protected Scopes

// Service B routes
await server.register(async (serviceBRoutes) => {
  await serviceBRoutes.register(hessuAuthPlugin, {
    sentinelUrl: process.env.SENTINEL_URL,
    expectedApp: 'service-b',
  });

  serviceBRoutes.get('/api/service-b/data', async (request) => {
    return { data: 'Service B data', user: request.user.sub };
  });
});

// Service C routes (different app ID)
await server.register(async (serviceCRoutes) => {
  await serviceCRoutes.register(hessuAuthPlugin, {
    sentinelUrl: process.env.SENTINEL_URL,
    expectedApp: 'service-c',
  });

  serviceCRoutes.get('/api/service-c/data', async (request) => {
    return { data: 'Service C data', user: request.user.sub };
  });
});

Static Public Key (Alternative)

await protectedRoutes.register(hessuAuthPlugin, {
  publicKey: process.env.HESSU_PUBLIC_KEY,
  expectedApp: process.env.SERVICE_APP_ID,
});

Request User Payload

After successful authentication, request.user contains:

interface HessuUserContext {
  sub: string;       // User ID (UUID from Supabase)
  app: string;       // Application slug (e.g., 'service-b')
  roles: string[];   // User roles for this app (e.g., ['admin', 'user'])
  exp: number;       // Token expiration (Unix timestamp)
  iat: number;       // Token issued at (Unix timestamp)
}

Example Payload

{
  sub: "550e8400-e29b-41d4-a716-446655440000",
  app: "service-b",
  roles: ["user", "admin"],
  exp: 1735689600,
  iat: 1735603200
}

How Authentication Works

  1. Client requests token: Frontend calls Sentinel's /api/auth/exchange with Supabase JWT
  2. Sentinel issues Hessu-JWT: Signed with RS256 private key, valid for 24 hours
  3. Client calls your service: Includes Authorization: Bearer <hessu-jwt> header
  4. Plugin validates token:
    • Verifies RS256 signature using public key
    • Checks expiration and issuer
    • Validates app claim matches expectedApp
    • Populates request.user with decoded payload
  5. Route handler executes: Access user context via request.user

Error Responses

401 Unauthorized

Token is missing, invalid, expired, or signature verification failed.

{
  "statusCode": 401,
  "error": "Unauthorized",
  "message": "Invalid or expired Hessu-JWT token"
}

403 Forbidden

Token is valid but app claim does not match expected app.

{
  "statusCode": 403,
  "error": "Forbidden",
  "message": "Token not valid for this service"
}

Advanced Configuration

Custom Key Cache Duration

await protectedRoutes.register(hessuAuthPlugin, {
  sentinelUrl: process.env.SENTINEL_URL,
  expectedApp: process.env.SERVICE_APP_ID,
  keyCacheDuration: 7200, // Cache for 2 hours (default: 3600)
});

Standalone JWT Verifier

Use the verifier directly without Fastify plugin:

import { createVerifier } from 'hessutech-auth-consumer';

const verifier = createVerifier({
  publicKey: process.env.HESSU_PUBLIC_KEY,
  expectedApp: 'service-b',
});

try {
  const payload = await verifier.verify(token);
  console.log('User ID:', payload.sub);
  console.log('Roles:', payload.roles);
} catch (error) {
  console.error('Token validation failed:', error.message);
}

TypeScript Support

Full TypeScript support with type inference:

import type { HessuUserContext } from 'hessutech-auth-consumer';

// Type-safe route handler
protectedRoutes.get('/api/profile', async (request) => {
  const user: HessuUserContext = request.user;
  return {
    id: user.sub,
    roles: user.roles,
  };
});

Security Considerations

  • Always use HTTPS in production: Tokens transmitted over HTTP can be intercepted
  • Validate app claim: Prevents tokens issued for Service B from being used on Service C
  • Short-lived tokens: Hessu-JWT tokens expire in 24 hours
  • Key rotation: Use SENTINEL_URL for automatic key rotation support
  • No shared secrets: RS256 means services only need the public key

Troubleshooting

"Invalid or expired token"

  • Verify token is not expired (check exp claim)
  • Ensure public key matches Sentinel's signing key
  • Check token was issued by Sentinel (issuer must be hessu-sentinel)

"Token not valid for this service"

  • Ensure expectedApp matches the app claim in the token
  • Verify token was requested with correct targetApp during exchange

"Failed to fetch public key"

  • Verify SENTINEL_URL is correct and accessible
  • Check network connectivity to Sentinel
  • Ensure Sentinel's /api/auth/keys endpoint is responding

License

MIT

Package Information

Support

For issues or questions, contact the Hessutech development team or file an issue in the Gitea repository.

Dependencies

Dependencies

ID Version
dotenv ^16.4.7
jose ^5.9.6

Development Dependencies

ID Version
@types/node ^22.10.5
fastify ^5.6.2
typescript ^5.9.3

Peer Dependencies

ID Version
fastify ^5.0.0

Keywords

jwt authentication fastify rs256 hessutech middleware
Details
npm
2026-01-04 15:36:16 +00:00
0
Hessutech
MIT
latest
14 KiB
Assets (1)
Versions (4) View all
1.2.1 2026-01-04
1.2.0 2026-01-04
1.1.0 2026-01-04
1.0.0 2026-01-04