Test authentication
Test your app’s login, token refresh, and error handling without calling Kraken. These recipes use Blueprint Auth’s testing helpers with Mock Service Worker (MSW) and Vitest. Complete the app setup first, and use these helpers only in tests or local development.
Create test credentials​
Create a signer and define your test endpoints. The following recipes reuse these values:
import {
createMockEmailAuth,
createMockJwksHandler,
createMockOAuth,
createMockOAuthSession,
createMockOrganizationAuth,
createMockTokenSigner,
startMockAuthorizationServer,
} from "@krakentech/blueprint-auth/testing";
let now = new Date();
const signer = await createMockTokenSigner({ now: () => now });
const graphqlAuthEndpoint = "https://api.kraken.test/graphql/";
const authEndpoint = "https://auth.kraken.test/";
Set graphqlAuthEndpoint in these examples to the URL your app uses for authentication. If neither krakenConfig.graphqlAuthEndpoint nor KRAKEN_GRAPHQL_AUTH_ENDPOINT is set, Blueprint Auth uses the app's graphqlEndpoint instead. Apps that only configure krakenConfig.graphqlEndpoint can use that URL without adding a separate auth endpoint.
Match authEndpoint to your test app's krakenConfig.authEndpoint. Add graphqlAuthEndpoint to validation.accessTokenIssuers, matching the issuer in the examples. The next recipe serves the signer's public verification keys as a JSON Web Key Set (JWKS).
To seed an access token without exercising login, sign it directly:
const accessToken = await signer.createAccessToken({
issuer: graphqlAuthEndpoint,
subject: "kraken|account-user:1",
authMethod: "email",
});
Test login and refresh​
Create separate handlers for password login and token refresh. Link them with the login session’s refresh credential:
const login = await createMockEmailAuth.loginHandler({
graphqlAuthEndpoint,
credentials: {
email: "alice@example.test",
password: "test_password",
},
signer,
session: {
issuer: graphqlAuthEndpoint,
subject: "kraken|account-user:1",
},
});
const refresh = await createMockEmailAuth.refreshHandler({
refreshToken: login.session.refreshToken,
graphqlAuthEndpoint,
signer,
session: {
issuer: graphqlAuthEndpoint,
subject: "kraken|account-user:1",
},
});
Register the handlers and verification keys with MSW. Register cleanup before starting interception:
import { http } from "msw";
import { setupServer } from "msw/node";
import { afterAll } from "vitest";
const keys = createMockJwksHandler({ signer, authEndpoint });
const mocks = setupServer(
http.get(keys.url, keys.handle),
http.post(login.url, login.handle),
http.post(refresh.url, refresh.handle),
);
afterAll(() => mocks.close());
mocks.listen({ onUnhandledRequest: "error" });
Submit alice@example.test and test_password through the login form, then check the stored tokens. Advance both clocks beyond expiry, then trigger a protected request. Check that your app stores a new access token and keeps the refresh credential.
Set the signer’s clock through now; tokens expire after one hour by default. Give the refresh handler its own session settings when you need different claims or dates.
Run MSW in the process that makes the external requests. For a separate Playwright process, use Next.js test mode to forward server-side requests; browser routing alone won’t intercept them.
For scoped or masquerade login, register the corresponding handler the same way.
Test rejected credentials​
Configure an error for the exact credentials your test submits. Omit signer and session:
import { krakenErrorTypeToCodeMap } from "@krakentech/blueprint-auth";
const rejected = await createMockEmailAuth.loginHandler({
graphqlAuthEndpoint,
credentials: { email: "alice@example.test", password: "wrong_password" },
errorCode: krakenErrorTypeToCodeMap.INCORRECT_CREDENTIALS,
});
mocks.use(http.post(rejected.url, rejected.handle));
Submit that password through your app and check its error message. Unconfigured credentials fall through to later handlers rather than automatically returning an authentication error. Use errorCode: "invalid_grant" for an OAuth rejection instead of a Kraken error code.
Test OAuth login​
Start a local authorization service to exercise your app’s OAuth redirects and callback:
const authorization = await startMockAuthorizationServer();
afterAll(() => authorization.close());
const oauth = await createMockOAuth.loginHandler({
authEndpoint: authorization.origin,
clientId: "test_client",
redirectUri: "http://localhost:3000/api/auth/oauth/callback",
signer,
session: {
issuer: graphqlAuthEndpoint,
subject: "kraken|account-user:1",
},
});
const oauthKeys = createMockJwksHandler({
signer,
authEndpoint: `${authorization.origin}/`,
});
Before starting your app, set its krakenConfig.authEndpoint to authorization.origin and krakenConfig.oauthClientId to test_client. Match redirectUri to your app’s OAuth callback route.
For successful login, use tokens that are still valid when the code is exchanged. The mock derives expires_in from the access token's remaining lifetime and returns 0 once it expires. Blueprint Auth's OAuth callback requires expires_in >= 1, so it rejects that response before verifying the JWT. An expired mock response can test rejection of an invalid provider response, but not JWT-expiry verification.
To test renewal, let the existing session expire and configure the refresh handler to issue fresh tokens, as shown below.
Allow requests to reach the local service, and intercept token exchange and key requests:
import { passthrough } from "msw";
const authorizeUrl = `${authorization.origin}/authorize/`;
const consumeUrl = `${authorization.origin}/authorization-codes/consume/`;
mocks.use(
http.get(authorizeUrl, () => passthrough()),
http.post(consumeUrl, () => passthrough()),
http.post(oauth.url, oauth.handle),
http.get(oauthKeys.url, oauthKeys.handle),
);
Start sign-in through your app, follow the redirects, and check that it stores oauth.session. To test renewal after login, register a refresh handler before triggering expiry:
const oauthRefresh = await createMockOAuth.refreshHandler({
authEndpoint: authorization.origin,
clientId: "test_client",
refreshToken: oauth.session.refreshToken,
signer,
session: {
issuer: graphqlAuthEndpoint,
subject: "kraken|account-user:1",
},
});
mocks.use(http.post(oauthRefresh.url, oauthRefresh.handle));
Use the same clock and session assertions as the email recipe. Register each handler’s own url. Check the OAuth reference for available options.
Seed sessions and organization credentials​
Seed credentials when your test needs an authenticated starting state rather than a login journey.
Seed an OAuth session​
Create tokens offline, then set your app’s auth cookies with the returned values:
const seeded = await createMockOAuthSession(signer, {
issuer: graphqlAuthEndpoint,
subject: "kraken|account-user:1",
audience: "test_client",
idTokenIssuer: new URL("token/", authEndpoint).href,
});
Set these cookies in your test’s browser context:
| Cookie | Value |
|---|---|
accessToken | seeded.accessToken |
oAuthIdToken | seeded.idToken |
refreshToken | seeded.refreshToken |
To test refresh alone, create an expired session with issuedAt and expiresAt. Register a refresh handler for the same credential; omit the login handler and authorization service.
Seed organization credentials​
Prepare an encrypted organization token and store it in an isolated memory cache:
import { createMemoryCacheAdapter } from "@krakentech/blueprint-auth/cache/memory";
const cacheAdapter = createMemoryCacheAdapter();
const organization = await createMockOrganizationAuth(signer, {
issuer: graphqlAuthEndpoint,
subject: "kraken|organization:1",
graphqlAuthEndpoint,
encryption: { key: "test_encryption_key" },
});
await cacheAdapter.set(
organization.cacheKey,
organization.encryptedAccessToken,
);
Pass that cache adapter and matching encryption settings to your app’s auth setup.
For apps using createGlobalConfigCacheAdapter, mock the Global Config store instead of seeding it. Use organization.cacheKey and organization.encryptedAccessToken from createMockOrganizationAuth.
Share signing keys across processes​
Export and serialize the keys in one process, then share that data with the other process. For example, write it to a file:
import { writeFile } from "node:fs/promises";
import { createMockTokenSigner } from "@krakentech/blueprint-auth/testing";
const signer = await createMockTokenSigner();
await writeFile(
"test-auth-keys.json",
JSON.stringify(await signer.exportKeys()),
);
In the receiving process, read and deserialize the data to restore a signer with the same keys:
import { readFile } from "node:fs/promises";
import { createMockTokenSigner } from "@krakentech/blueprint-auth/testing";
const signer = await createMockTokenSigner({
keys: JSON.parse(await readFile("test-auth-keys.json", "utf8")),
});
Use the restored signer to create tokens or test-only handlers in the receiving process.
With Playwright, use globalSetup to share serialized keys through an environment variable, then restore the signer in each worker. Use createMockJwksHandler with that signer to serve the public verification keys to the app. Sharing one key pair keeps workers’ tokens consistent with the app’s JWKS cache.
If you use a file, add it to .gitignore and delete it after both processes finish. Never commit or log private signing keys, and keep them out of browsers.