
Security News
Deno Goes the Way of the Dinosaur
Cloudflare is acquiring Deno’s team, which will provide another year of bug fixes and security updates before ending support for the runtime.
@tempoxyz/iid
Advanced tools
Cloudflare Access JWT verification and Tempo group authorization for Workers
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.
pnpm add @tempoxyz/iid
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.
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.
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
Unknown package
The npm package @tempoxyz/iid receives a total of 0 weekly downloads. As such, @tempoxyz/iid popularity was classified as not popular.
We found that @tempoxyz/iid demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 7 open source maintainers collaborating on the project.

Security News
Cloudflare is acquiring Deno’s team, which will provide another year of bug fixes and security updates before ending support for the runtime.

Security News
Anthropic’s free OSS Scanner uses Mythos and other Claude models to find open source vulnerabilities and send maintainers reports with proposed fixes.

Security News
On Front Lines’ Early Adopters: CISO podcast, Andrew Becherer shares lessons from Datadog, weighs the long-term costs of AI-built tools, and explains how founders can actually gain a CISO’s trust.