Skip to content

Auth & Authz

The @connectum/auth package provides authentication interceptors and two authorization models: proto-based (rules in .proto files) and code-based (programmatic rules in TypeScript). Proto-based is the recommended approach -- it keeps access control alongside your API contract and requires zero application code changes when rules evolve.

Quick Start

Define access rules directly in .proto files -- the interceptor reads them at runtime:

protobuf
import "connectum/auth/v1/options.proto";

service UserService {
  option (connectum.auth.v1.service_auth) = { default_policy: "deny" };

  rpc GetProfile(GetProfileRequest) returns (GetProfileResponse) {
    option (connectum.auth.v1.method_auth) = { public: true };
  }

  rpc DeleteUser(DeleteUserRequest) returns (DeleteUserResponse) {
    option (connectum.auth.v1.method_auth) = { requires: { roles: ["admin"] } };
  }
}
typescript
import { createServer } from '@connectum/core';
import { createDefaultInterceptors } from '@connectum/interceptors';
import {
  createJwtAuthInterceptor,
  createProtoAuthzInterceptor,
  getPublicMethods,
} from '@connectum/auth';
import { UserService } from '#gen/user_pb.js';

const publicMethods = getPublicMethods([UserService]);

const jwtAuth = createJwtAuthInterceptor({
  jwksUri: 'https://auth.example.com/.well-known/jwks.json',
  skipMethods: publicMethods, // synced from proto `public: true`
});

const authz = createProtoAuthzInterceptor({ defaultPolicy: 'deny' });

const server = createServer({
  services: [routes],
  interceptors: [...createDefaultInterceptors(), jwtAuth, authz],
});

await server.start();

Code-Based Authorization

For services without proto annotations, use programmatic rules:

typescript
import { createAuthzInterceptor } from '@connectum/auth';

const authz = createAuthzInterceptor({
  defaultPolicy: 'deny',
  rules: [
    { name: 'public', methods: ['public.v1.PublicService/*'], effect: 'allow' },
    { name: 'admin', methods: ['admin.v1.AdminService/*'], requires: { roles: ['admin'] }, effect: 'allow' },
  ],
});

Both approaches can be combined -- proto options take priority, programmatic rules act as fallback.

Key Concepts

Authorization: Proto vs Code

Proto-BasedCode-Based
Where rules live.proto filesTypeScript code
InterceptorcreateProtoAuthzInterceptor()createAuthzInterceptor()
Change access rulesEdit .proto, regenerateEdit code, redeploy
Single source of truthProto contract = access policySeparate from API contract
Best forMost services (recommended)Dynamic rules, legacy services

Authentication Strategies

FactoryCredential sourceUse case
createAuthInterceptorAny (pluggable callback)API keys, mTLS, opaque tokens
createJwtAuthInterceptorAuthorization: Bearer <JWT>Auth0, Keycloak, custom issuers
createGatewayAuthInterceptorGateway-injected headersKong, Envoy, Traefik pre-auth
createSessionAuthInterceptorSession token (cookie or header)better-auth, lucia, custom sessions

All factories produce a standard ConnectRPC Interceptor and store the authenticated identity in AuthContext via AsyncLocalStorage.

When Do You Need App-Level Auth?

ScenarioAuth approach
Services behind an API gateway that verifies tokensGateway auth (createGatewayAuthInterceptor)
Services exposed directly to clientsJWT auth (createJwtAuthInterceptor)
Services with session-based web clientsSession auth (createSessionAuthInterceptor)
Custom or exotic credential schemesGeneric auth (createAuthInterceptor)

Interceptor Chain Position

Auth interceptors must be placed after errorHandler and before resilience interceptors:

errorHandler -> AUTH -> AUTHZ -> timeout -> bulkhead -> circuitBreaker -> ...

Learn More