@hyper.authio/testing
v0.10.1
Published
An in-memory Authio realm for tests: discovery, JWKS, every grant, introspection, revocation, UserInfo, signed webhooks.
Readme
@hyper.authio/testing
An in-memory Authio realm served over HTTP on loopback, for tests of code built on @hyper.authio/client,
@hyper.authio/server or @hyper.authio/express.
npm install -D @hyper.authio/testingimport { FakeAuthio, ManualClock, createWebhookDelivery } from "@hyper.authio/testing";
const authio = await FakeAuthio.start({
realm: "acme",
clients: [{ clientId: "orders-api", clientSecret: "secret" }],
users: [{ sub: "u-1", username: "alice", password: "p", claims: { user_resource_access: { editor: { orders: ["read"] } } } }],
});
const token = await authio.issueAccessToken({
sub: "u-1",
audience: "https://orders.example.com",
scope: "orders:read",
claims: { groups: ["finance"], org_id: "o-1" },
});
// point the SDK at authio.issuer, then:
await authio.close();- Endpoints: discovery (every endpoint it advertises is served), JWKS, authorization, token (client credentials,
authorization code with ID tokens, refresh with rotation, token exchange including connected accounts, password,
device code, personal access token), introspection (answered according to who asks, like Authio), revocation,
UserInfo (JSON, or a signed JWT with
userInfoAsJwt), device authorization and end session. - Sign-in:
authorize(url)follows an authorization URL as a browser would and returns the callback URL (or, forresponse_mode=web_message, the posted parameters). It signs insignInAs, thelogin_hintor the first user; honours PKCE (S256, plain;code_challenge_methodis required),prompt(noneanswerslogin_requiredwithout a session,loginsigns in again),max_age,acr_values, and sends RFC 9207iss(omitAuthorizationIssuerleaves it out). ID tokens carrynonce,sid,auth_time,acr,amrandat_hash.createAuthorizationCodestill mints a code directly. - Sign-out: the end-session endpoint checks
post_logout_redirect_uri, echoesstate, and posts a logout token to thebackchannelLogoutUriof every client of the session (backchannelDeliveries);deliverBackchannelLogoutandcreateLogoutTokendo it by hand. Ending a session revokes its refresh tokens;revokeRefreshTokens({ sub | sid })andrevokeToken(token)revoke them by hand. - Refresh tokens carry the session's
sidthrough refreshes and are introspectable by the client they were issued to (token_typeRefresh), as a BFF's session check needs. - JWE: a client with
encryption: { key, algorithm }(RSA-OAEP or RSA-OAEP-256) gets its ID tokens, UserInfo, and optionally access and logout tokens encrypted;issueAccessToken({ encryptTo })mints one directly. - Personal access tokens:
personalAccessTokens: [{ token: "pat_…", sub, clientId? }]. - Clients are configured as in Authio's console, and the fake enforces it as Authio does, refusing with an error that
names the setting and accepting what the client does not require:
tokenEndpointAuthMethod:client_secret_basic(default withclientSecret),client_secret_post,private_key_jwt(default with onlypublicKey) ornone(default with neither), matched exactly (invalid_client); a secret sent by anoneclient is ignored.requirePkce(defaulttrue, as Authio creates clients) refuses an authorization request withoutcode_challenge: declarerequirePkce: falsefor a client with PKCE off.pkceMethods(default["S256"]) refuses another method even when PKCE is not required; PKCE sent to a client that does not require it is checked and accepted.signingAlgorithm:RS256(default) orHS256/HS384/HS512with the client secret, for the client's ID tokens, UserInfo and logout tokens;accessTokenSigningAlgorithmthe same for its access tokens, as Authio keeps two settings. The secret needs at least 32, 48 or 64 bytes (RFC 7518 §3.2), or the client is refused.issueIdTokenOnRefreshadds a new ID token to every refresh.accessTokenFormat:Jwt(default),Reference(an opaque handle of 43 characters, never encrypted) orLightweight(a JWT with only the identity and session claims, without*_resource_access, profile or email). Introspection answers with every claim of the complete token whatever the format, as Authio does.frontchannelLogoutUri(withfrontchannelLogoutSessionRequiredforissandsid) is loaded when a session of the client ends (frontchannelDeliveries);frontchannelLogoutUrlanddeliverFrontchannelLogoutdo it by hand.response_modeother thanqueryandweb_messageis refused.
clock(such as theManualClockthe library uses) is the time tokens are issued and checked with.issueAccessTokenoptions:expiresIn(negative for an expired token),type(IDfor a non-access token),headerType(the headertyp,at+jwtby default as RFC 9068 has it;nullleaves it out),issuer,untrusted(a signature that does not verify),format(Jwt,ReferenceorLightweight; the client'saccessTokenFormatby default).failures.set("token", [{ status: 503 }])answers the next calls to an endpoint with errors;deviceOutcomesscripts device polling;connectionResponderscripts connected-account exchanges;requestsrecords every call.ManualClockmoves only when told to: device polling and cache expiry run instantly.createWebhookDelivery(type, data, secret)builds a signed delivery (body,headers).
License
MIT
