New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

@tempoxyz/iid

Package Overview
Dependencies
Maintainers
7
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install
Package version was removed
This package version has been unpublished, mostly likely due to security reasons

@tempoxyz/iid

Cloudflare Access JWT verification and Tempo group authorization for Workers

unpublished
npmnpm
Version
0.5.0
Version published
Weekly downloads
0
Maintainers
7
Weekly downloads
 
Created
Source

@tempoxyz/iid

Server-side Cloudflare Access JWT verification and Tempo group authorization for Workers. Cloudflare Access must protect the application and perform the OIDC handshake with the internal identity broker. This package does not initiate login or manage sessions.

Install

pnpm add @tempoxyz/iid

Use

import { createInternalIdentity, getUser } from '@tempoxyz/iid';
import { Hono } from 'hono';

const identity = createInternalIdentity({
  publicPaths: ['/health'],
  // Required: the broker forwards groups but does not decide who may use
  // your app. `anyAuthenticatedUser: true` if the bar is any Tempo employee.
  requiredGroups: ['app-reader'],
});

export default new Hono()
  .use((c, next) => identity.middleware()(c, next))
  .get('/health', (c) => c.text('ok'))
  .get('/', (c) => c.text(`Hello ${getUser(c).email}`));

No Access account configuration or package-install credentials are needed. The SDK's only built-in service URL is https://identity.tempoxyz.net/config. On a configuration cache miss, it sends the incoming Access assertion to that fixed HTTPS endpoint in an Authorization: Bearer header, without following redirects. The broker independently verifies both signatures and an exact issuer allowlist, then returns that issuer's Access JWKS URL and the broker JWKS URL. No session lookup or extra token claim is required.

The pinned HTTPS config service is the SDK's configuration trust root. The SDK checks the returned issuer against the assertion exactly, caches successful configuration for five minutes, and independently verifies both signatures on every request. Key caches remain isolated by issuer and algorithm. Discovery failure with no unexpired configuration denies access; it never falls back to trusting token-supplied URLs. No audience or shared-email configuration is needed.

For a plain Worker:

export default identity.requireUser(async (_request, user) => Response.json(user), {
  public: () => new Response('ok'),
});

Public paths go to the explicit public handler (404 if absent). They skip SDK checks only: they do not bypass Cloudflare's edge policy. Configure edge exceptions separately if a health check must be reachable without Access authentication.

Verification and authorization

The SDK reads only Cf-Access-Jwt-Assertion, verifies RS256, the allowlisted issuer’s signature/issuer, expiration and not-before time, required token metadata, and user application-token shape. Service tokens, broker tokens, arbitrary identity headers, and cookies are not accepted. Missing/invalid identity returns 401 without redirecting; valid identity missing a required group returns 403.

Real identity comes only from custom.tempo_user_id, custom.tempo_email, and custom.groups, authenticated by the required broker ES256 detached signature in custom.tempo_identity_signature. The broker JWKS location is supplied by authenticated configuration discovery independently of Access. Email must be an exact @tempo.xyz address and groups must be a string array; a missing group claim fails closed, even with anyAuthenticatedUser. The returned TempoUser has only sub, email, and groups. The shared Access email/sub are not the employee identity. Tokens are never included in this object.

All requiredGroups must match exactly. Construction requires a nonempty group list or explicit anyAuthenticatedUser: true. Per-route checks use identity.requireGroup(...groups) after middleware, or hasGroup(user, group). Whole-Worker wrappers also accept { groups: [...] }.

Trust deliberately spans all three Tempo Access accounts: the SDK does not check an application AUD or require a token from the account hosting the Worker. A valid Access JWT from any listed issuer can carry a valid broker-signed identity to any consumer. Access or another IdP cannot fabricate or alter the broker-signed identity, but can replay a complete existing proof. This does not prove admission through a particular policy or bind the proof to the current browser. Application group requirements remain mandatory and are enforced by each application.

The v1 proof deliberately has no timestamp, expiry, or session binding. Access JWT expiry still applies, but Access can carry an old proof in a fresh JWT while its broker signing key remains trusted.

Discovery admits holders of valid employee assertions, not specific calling Workers. Anyone holding such an assertion can retrieve and share the config. The config service receives reusable identity material: never log the Authorization header or JWT, including in custom fetch implementations or request tracing. Only configuration is cached, never a caller's identity or group decision.

identity.handle(request) returns { type: 'public' }, { type: 'response', response }, or { type: 'user', user }. Both framework wrappers use this classification. Wrappers mark authenticated responses private, no-store; direct callers of handle must do the same.

Advanced options: clockToleranceSec (default 0), jwks cache tuning, and a receiver-safe injectable fetch for configuration and JWKS retrieval. Settings apply independently to the three Access caches and the broker cache; unused issuers trigger no fetch. Keys are cached for five minutes, with a five-second refetch cooldown and timeout. Known cached keys remain usable during an outage; unknown keys fail closed. No request is accepted without signature verification.

Optional authentication diagnostics

The SDK is silent by default. To investigate authentication failures, opt in:

const identity = createInternalIdentity({
  anyAuthenticatedUser: true,
  onDiagnostic: (event) => console.info(JSON.stringify({ event: 'access_auth', ...event })),
});

Each protected handle() call reports only a fixed reason label. No tokens, cookies, claim values, request data, or exception messages are included. Public paths emit nothing. Logging failures do not change authentication. Client-facing errors remain unchanged.

Reasons distinguish missing/malformed assertions, expired/not-yet-valid tokens, issuer/signature failures, invalid token metadata, missing custom identity fields, invalid_identity_signature, and missing required groups. key_unavailable intentionally combines key-fetch, JWKS, unknown-key, and cache/cooldown failures; it is not proof of an outage. authenticated means the base SDK identity and configured requiredGroups passed, not that later route-specific authorization passed. See the exported AuthReason type for the complete list.

FAQs

Package last updated on 23 Sep 2026

Related posts