Skip to main content

Anonymous Authentication

Introduction

Anonymous authentication enables temporary, scoped access to specific resources using pre-signed keys generated by Kraken. Unlike standard login which provides full account access with refreshable tokens, anonymous auth creates time-limited scopedToken cookies with restricted scope, granting access only to specific resources. This makes it ideal for scenarios where you need to grant temporary access without requiring user registration.

Key Characteristics
  • Pre-signed keys: Generated server-side via Kraken API and can be reused multiple times
  • Time-limited access: Scoped tokens expire after their set duration (typically around 1 hour)
  • Resource-specific: Each token is scoped to specific resources or accounts
  • No automatic refresh: The auth package does not automatically refresh expired tokens (users can re-authenticate using the same pre-signed key)

Getting started

Before you begin

Configuration

Basic Configuration (single anon path)

lib/auth/config.ts
import { createAuthConfig } from "@krakentech/blueprint-auth";

export const authConfig = createAuthConfig({
appRoutes: {
anon: {
// Extract pre-signed key from URL path
getAnonParams({ url }) {
if (url.pathname.startsWith("/anon")) {
const [_anon, preSignedKey] = url.pathname.split("/");
if (preSignedKey) {
return { preSignedKey };
}
}
},
// Single pathname
pathname: "/anon",
},
dashboard: { pathname: "/dashboard" },
login: { pathname: "/login" },
},
});

Advanced Configuration (multiple paths with custom redirects)

lib/auth/config.ts
import { createAuthConfig } from "@krakentech/blueprint-auth";
import { NextResponse } from "next/server";

export const authConfig = createAuthConfig({
appRoutes: {
anon: {
getAnonParams({ url }) {
// Support /anon/{key}/... paths
if (url.pathname.startsWith("/anon")) {
const [_anon, preSignedKey] = url.pathname.split("/");
if (preSignedKey) {
return { preSignedKey };
}
}

// Support dashboard with ?key={key} search param
if (url.pathname.startsWith("/feedback")) {
return { preSignedKey: url.searchParams.get("key") };
}
},

// Multiple pathnames
pathname: ["/anon", "/feedback"],

// Optional: Custom redirect on success
customSuccessResponse({ url }, { redirect }) {
// Example: redirect to clean URL without /anon prefix
const cleanPath = url.pathname.replace("/anon", "/feedback");
return redirect(new URL(cleanPath, url.origin));
},

// Optional: Custom error handling
customErrorResponse({ url, errorCode }, { redirect }) {
return redirect(new URL(`/error?code=${errorCode}`, url.origin));
},
},
dashboard: { pathname: "/dashboard" },
login: { pathname: "/login" },
},
});

Configuration Options

OptionTypeRequiredDescription
pathnamestring | string[]Routes where anonymous auth is enabled
getAnonParams(options: { url: NextURL }) => { preSignedKey: string | null | undefined } | undefinedExtracts preSignedKey from URL
customSuccessResponse(options: { url: NextURL }, helpers: { redirect, rewrite }) => NextResponse | undefinedOverride redirect after successful authentication
customErrorResponse(options: { url: NextURL; errorCode: ErrorCode }, helpers: { redirect, rewrite }) => NextResponse | undefinedOverride redirect on authentication failure
Child route access

Providing a parent route in pathname automatically grants access to all child routes. For example:

  • pathname: "/anon" grants access to /anon/{key}, /anon/{key}/account/123, /anon/{key}/account/123/feedback, etc.
  • This is useful for multi-step forms where the scoped token persists across multiple pages (e.g., /anon/{key}/feedback/step-1, /anon/{key}/feedback/step-2, /anon/{key}/feedback/step-3)
Public subroutes

Use allowList with glob patterns to make specific anon subroutes publicly accessible:

anon: {
pathname: "/anon",
getAnonParams({ url }) { /* ... */ },
allowList: ["/anon/public/**"], // No pre-signed key required
}

See the picomatch docs for supported glob patterns.

Middleware

middleware.ts
import { createAuthMiddleware } from "@krakentech/blueprint-auth/middleware";
import { authConfig } from "@/lib/auth/config";

export const middleware = createAuthMiddleware(authConfig);

export const config = {
matcher: ["/anon/:path*", "/dashboard/:path*", "/login"],
};
Matcher must include anon paths

The middleware matcher array must include your appRoutes.anon.pathname value(s), otherwise the anonymous auth flow won't trigger.

Anonymous route

Create a page that accepts a pre-signed key and account number in the URL path. The middleware automatically sets the scopedToken cookie when users navigate to the URL, allowing you to use server functions like getUserScopedGraphQLClient to fetch data.

pages/anon/[preSignedKey]/[accountNumber]/feedback.tsx
import type {
GetServerSidePropsContext,
InferGetServerSidePropsType,
} from "next";
import { FeedbackForm } from "@/components/FeedbackForm";
import { graphql } from "@/lib/graphql";
import { getUserScopedGraphQLClient } from "@/lib/auth/server";

export default function FeedbackPage({
account,
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
return (
<>
<h1>Customer Feedback</h1>
<FeedbackForm account={account} />
</>
);
}

const FeedbackFormQuery = graphql(`
query FeedbackForm($accountNumber: String!) {
account(accountNumber: $accountNumber) {
id
number
balance
}
}
`);

export async function getServerSideProps(
context: GetServerSidePropsContext<{
preSignedKey: string;
accountNumber: string;
}>,
) {
const { accountNumber } = context.params;

// The scopedToken cookie is already set by middleware
// Use server functions normally (they'll use the scoped token)
const graphQLClient = getUserScopedGraphQLClient({ context });

// Fetch data for the page
const { account } = await graphQLClient.request(FeedbackFormQuery, {
accountNumber,
});

if (!account) {
return { notFound: true };
}

return { props: { account } };
}

Session state

Middleware handles security

The middleware ensures users have valid tokens before reaching your page components. You don't need to check session state for security purposes.

Checking session state is useful when you need to differentiate behavior between anonymous and fully authenticated users. Use getSession or useSession to conditionally render UI elements or execute different application logic based on authentication type.

Session state fields

FieldValueDescription
authMethod"scoped"Indicates user is authenticated via anonymous auth
isAuthenticatedtrueUser has valid authentication (any supported auth cookie)

Common patterns

When to use this

Client components need to conditionally render features based on whether the user authenticated via pre-signed key or full login.

Example

A client-side feedback form that conditionally shows "Save Draft" and submission history links only to fully authenticated users, while displaying a one-time submission warning for anonymous users.

Show implementation
components/FeedbackForm.tsx
"use client";

import Link from "next/link";
import { useSession } from "@/lib/auth/client";
import { useState } from "react";

export function FeedbackForm({ accountId }: { accountId: string }) {
const { data: session } = useSession();
const [feedback, setFeedback] = useState("");

return (
<form>
<textarea
value={feedback}
onChange={(e) => setFeedback(e.target.value)}
placeholder="Tell us about your experience..."
/>

{session.authMethod !== "scoped" && (
<>
<button type="button">Save Draft</button>
<Link href="/feedback/history">View Previous Feedback</Link>
</>
)}

{session.authMethod === "scoped" && (
<p className="warning">
This is a one-time submission. You won't be able to edit it later.
</p>
)}

<button type="submit">
{session.authMethod === "scoped"
? "Submit (One-time)"
: "Submit Feedback"}
</button>
</form>
);
}

Security Considerations

Security Notes
  1. Pre-signed keys are sensitive: Treat them like passwords
  2. Use HTTPS only: Never send keys over unencrypted connections
  3. Time-limit tokens: Set appropriate expiration times when generating keys
  4. Validate scope server-side: Always verify the token scope matches the accessed resource
  5. No automatic refresh: The auth package does not automatically refresh expired tokens (users can re-authenticate using the same pre-signed key)

FAQ

How does anonymous authentication work?
  1. User receives URL with embedded pre-signed key (e.g., /anon/{key}/account/123/feedback)
  2. User navigates to the URL
  3. Next.js middleware intercepts the request
  4. getAnonParams function extracts the pre-signed key from the URL
  5. Middleware calls obtainKrakenToken GraphQL mutation with the key
  6. Kraken validates the key and returns a scoped JWT token
  7. Middleware sets scopedToken cookie with expiration from JWT payload
  8. User gains access to the protected resource
  9. Token expires after the set duration

:::warning[No automatic refresh] The auth package does not automatically refresh scoped tokens. When a scoped token expires, users need to re-authenticate by navigating to a URL with their pre-signed key. Pre-signed keys can be reused multiple times within their validity period, which is typically longer than the scoped token lifetime. :::

How long do scoped tokens last?

The expiration time is determined when the pre-signed key is generated via the Kraken API. The scoped token's exp claim contains the Unix timestamp when it expires. This is typically set to around 1 hour, though it can vary depending on the use case. Note that pre-signed keys themselves have separate, usually longer, expiration times and can be reused to obtain new scoped tokens.

What happens if a user already has an accessToken cookie?

The auth middleware follows a priority system where higher-priority authentication methods take precedence. If a user already has a valid accessToken (full authenticated session), the scoped token will not override it. The middleware will skip processing the pre-signed key and continue with the existing authenticated session.

Priority order (highest to lowest):

  1. masqueradeToken - Staff impersonation
  2. MWAuthToken - Mobile app integration
  3. accessToken - Full authenticated sessions (email/password, OAuth)
  4. scopedToken - Anonymous key access

This prevents accidental session replacement when an authenticated user clicks an anonymous session link.

Next Steps

Now that you have anonymous authentication configured, explore these related guides:

API Reference

Quick reference to relevant functions: