@nodii/grpc-auth
v0.1.1
Published
gRPC service-to-service auth for Nodii microservices: server + client interceptors, JWKS fetch from S3, JWT verifier, token manager.
Maintainers
Readme
@nodii/grpc-auth
Service-to-service gRPC auth for the Nodii microservice stack.
Provides:
createServerAuthInterceptor— verifies an incomingBearertoken from theauthorizationmetadata header and attaches anS2SAuthContextto the call.createClientAuthInterceptor— acquires a token via anS2STokenManagerand attaches it to outgoing calls.S2STokenManager— caches/refreshes tokens from a SigV4-signed API Gateway endpoint.verifyS2SToken— JWT verifier with JWKS resolution.createSigV4S3JWKSet— JWKS resolver that pulls a private.well-known/jwks.jsonfrom S3 using SigV4 (not anonymous HTTPS).withAuthUnary+requireScope— handler-level wrapper for unary RPC scope enforcement.
Install
bun add @nodii/grpc-auth
# peer deps
bun add @grpc/grpc-js joseServer: verify incoming tokens
import * as grpc from "@grpc/grpc-js";
import {
createServerAuthInterceptor,
createSigV4S3JWKSet,
setJwksResolver,
} from "@nodii/grpc-auth";
// Configure JWKS source once, at boot.
setJwksResolver(
createSigV4S3JWKSet({
region: "ap-south-1",
bucket: "nucleus-cloud-s2s-auth",
key: ".well-known/jwks.json",
cacheMaxAgeMs: 60_000,
cooldownDurationMs: 5_000,
}),
);
const server = new grpc.Server({
interceptors: [
createServerAuthInterceptor({
expectedIssuer: "nucleus-s2s-auth",
expectedAudience: "nucleus-internal",
}),
],
});The interceptor extracts Bearer <token>, calls verifyS2SToken, and attaches the auth context to the call as (call as any).auth. Handlers read it via the same path:
function handler(call: ServerUnaryCall<Req, Res>, cb: sendUnaryData<Res>) {
const auth = (call as any).auth as S2SAuthContext | undefined;
// auth.serviceName, auth.scopes, ...
}Note.
(call as any).authis the production-proven pattern from provisioning. A typedgetCallContext(call)helper is planned for v0.3.0 (lifted from inventory). It will live alongside the legacy reader, not replace it.
Server: per-handler scope enforcement
import { withAuthUnary } from "@nodii/grpc-auth";
const REQUIRED_SCOPES_BY_METHOD: Record<string, string> = {
createCustomer: "provisioning:customers:write",
getCurrentIpAssignmentsForIp: "provisioning:ip_assignments:read",
};
const wrap = withAuthUnary({
expectedIssuer: "nucleus-s2s-auth",
expectedAudience: "nucleus-internal",
requiredScopesByMethod: REQUIRED_SCOPES_BY_METHOD,
// Optional: skip auth in local dev. Defaults to false.
skipAuth: () => process.env.LOCAL === "true",
});
server.addService(MyServiceDefinition, {
createCustomer: wrap("createCustomer", impl.createCustomer),
// ...
});Client: acquire and attach a token
import {
S2STokenManager,
createClientAuthInterceptor,
} from "@nodii/grpc-auth";
const tokenManager = new S2STokenManager({
apiBaseUrl: process.env.S2S_AUTH_API_BASE!,
region: process.env.AWS_REGION ?? "ap-south-1",
refreshSkewSeconds: 30,
});
const client = new MyGrpcServiceClient(target, credentials, {
interceptors: [
createClientAuthInterceptor({
tokenManager,
audience: "nucleus-internal",
scopes: ["flows:write"],
instanceId: process.env.INSTANCE_ID,
}),
],
});Versioning notes
v0.1.0 is a 1:1 lift of provisioning's S2S auth code (production-proven, also running in traffic-logs). Two narrow changes vs the source:
- The JWKS source is configured via
setJwksResolver(...)instead of a module-level constant. Provisioning's hardcodedregion: "ap-south-1",bucket: "nucleus-cloud-s2s-auth",key: ".well-known/jwks.json"move into the consumer's bootstrap. withAuthUnaryacceptsrequiredScopesByMethodandskipAuthas parameters instead of importing them from a service-specific env.
These are dependency-injection refactors, not behavior changes. Verifier semantics (algorithms, clock skew, claim shape, scope parsing), token cache logic, and JWKS refresh+cooldown are unchanged.
Planned follow-ups:
v0.2.0— pluggable test JWKS source (TEST_S2S_JWKS_PATHpattern from traffic-logs).v0.3.0— typedCallContext/getCallContext/mutateCallContextAPI alongside the legacy(call as any).authreads (pattern from inventory).v0.4.0— pluggable logger (replacesconsole.logcalls; quiet by default).
