Getting started: Pages Router
This guide walks you through the steps to enable Kraken authentication in your Next.js app. When you're done, you'll know how the different parts of the package work together in order to provide a seamless authentication experience for your users.
What You'll Build​
By the end of this guide, you'll have:
- Client providers and auth API routes for your chosen login flow
- Protected routes using Next.js middleware
- Session management with server-side authentication
- Authenticated GraphQL queries
This guide only covers the basic setup of the @krakentech/blueprint-auth package, check out our
authentication guides to implement additional features.
Functions mentioned in this guide have more options, please refer to the API reference for more information. Functions are also annotated with JSDoc, providing in-editor documentation.
Requirements​
The @krakentech/blueprint-auth package requires:
next@^14.2.25 || ^15.0.0 || ^16.0.0,react@^18.2.0 || ^19.1.0,react-dom@^18.2.0 || ^19.1.0
If your project uses unsupported versions, make sure you upgrade them before proceeding.
Installation​
Install the @krakentech/blueprint-auth package and its peer dependencies.
The @krakentech/blueprint-auth package relies on:
@tanstack/react-query@^5.29.2,graphql-request@^7.2.0,@vercel/global-config@^1.5.0(optional: required when you use the supplied shared cache adapter).
- pnpm
- npm
- yarn
- bun
pnpm add @krakentech/blueprint-auth @tanstack/react-query @vercel/global-config graphql-request
npm install @krakentech/blueprint-auth @tanstack/react-query @vercel/global-config graphql-request
yarn add @krakentech/blueprint-auth @tanstack/react-query @vercel/global-config graphql-request
bun add @krakentech/blueprint-auth @tanstack/react-query @vercel/global-config graphql-request
Packages of the @krakentech NPM organization are published privately to the NPM registry. To
install these packages, you need to configure your package manager to use a Kraken issued NPM access
token. Please get in touch if you need one.
Configuration​
Use the createAuthConfig factory to create a centralized configuration object that can be reused
throughout your application. Check out the
API Reference to learn more about the
available configuration options.
Environment variables​
Create a .env.local file in the root of your project and define the following environment
variables:
GLOBAL_CONFIG="Vercel Global Config connection string"
VERCEL_AUTH_TOKEN="Vercel API token with Global Config write access"
VERCEL_TEAM_ID="Vercel team ID"
KRAKEN_AUTH_ENDPOINT="https://auth.xxxx-kraken.systems/"
KRAKEN_ACCESS_TOKEN_ISSUERS="https://api.xxxx-kraken.systems/v1/graphql/,https://auth.xxxx-kraken.systems/token/,https://support.xxxx-kraken.systems"
KRAKEN_GRAPHQL_ENDPOINT="Kraken GraphQL endpoint URL"
KRAKEN_X_CLIENT_IP_SECRET_KEY="Client IP secret key"
The use of environment variables is strongly recommended for supported options. When deploying to Vercel, configure these environment variables in your project settings for preview and production deployments.
Configuration object​
Create a configuration object that defines the API routes and app routes for your authentication
flow. The paths in apiRoutes must match the actual API route files you'll create in the next
section.
import { createAuthConfig } from "@krakentech/blueprint-auth";
export const authConfig = createAuthConfig({
apiRoutes: {
login: "/api/auth/login",
logout: "/api/auth/logout",
graphql: { kraken: "/api/graphql/kraken" },
session: "/api/auth/session",
},
appRoutes: {
dashboard: { pathname: "/dashboard" },
home: { pathname: "/" },
login: { pathname: "/login" },
},
});
Trusted request origins​
The cookie-authenticated POST handlers require a list of trusted origins. This requirement applies
to createLoginHandler, createLogoutHandler, and createGraphQLHandler. Add every origin that
serves your app. An origin contains the scheme, host, and optional port. It does not contain a path,
query, or fragment.
Each request must include an Origin header or a Referer header. The handler rejects a request
that has neither header. The handler checks every source header that is present. If the request has
both headers, both origins must be in the list.
Set the comma-separated ALLOWED_REQUEST_ORIGINS environment variable:
ALLOWED_REQUEST_ORIGINS="https://www.example.com,http://localhost:3000"
You can set the list in the configuration instead:
validation: {
allowedRequestOrigins: ["https://www.example.com", "http://localhost:3000"],
},
Use the same scheme that your server uses. Do not add a trailing path. createSessionHandler
handles GET requests, so it does not use this check. See the
createAuthConfig validation options
for more information.
Token verification​
KRAKEN_ACCESS_TOKEN_ISSUERS is a comma-separated list. An issuer is the iss field in an access
token. Use the exact issuers for your Kraken environment. Matching is case-sensitive. A trailing
slash changes the issuer. For OAuth, add token/ to the exact Kraken auth endpoint. Replace the
example domains above with the values for your Kraken environment.
KRAKEN_AUTH_ENDPOINT must use HTTPS. For local development, HTTP is accepted only for localhost,
127.0.0.1, and [::1]. Blueprint Auth gets the public JSON Web Key Set (JWKS) from
.well-known/jwks.json at this endpoint. See
Session management for an
overview of the session lifecycle.
Server cache​
For organization auth, create one server-only cache adapter. This guide uses the supplied Vercel Global Config adapter.
For user and viewer auth, sessions, login, logout, and OAuth, skip this step. Omit the
cacheAdapter import below and use createServerSideAuth(authConfig). You can also omit
@vercel/global-config and the GLOBAL_CONFIG, VERCEL_AUTH_TOKEN, and VERCEL_TEAM_ID
variables. Calling the factory's getAuth.org without an adapter fails with
AuthMissingPropertiesError for cacheAdapter.
Pages Router does not support the server-only marker. Import this cache module only from
getServerSideProps, API Routes, and other server modules. Do not import it from a component or
client module.
import { createGlobalConfigCacheAdapter } from "@krakentech/blueprint-auth/cache/global-config";
export const cacheAdapter = createGlobalConfigCacheAdapter();
The adapter stores organization tokens. It checks that its required environment values are present. It also parses the store ID and creates the Global Config client when this module loads. The cache provider checks the Vercel settings and permissions during a read or write.
Middleware​
Create a Next.js
middleware (version
≤15) or proxy (version ≥16)
using the createAuthMiddleware
factory.
The examples use the recommended catch-all matcher.
- Next.js ≤15
- Next.js ≥16
import { createAuthMiddleware } from "@krakentech/blueprint-auth/middleware";
import { authConfig } from "@/lib/auth/config";
export const middleware = createAuthMiddleware(authConfig);
export const config = {
matcher: ["/", "/((?!api|_next|_vercel|.*\\..*).*)"],
};
import { createAuthMiddleware } from "@krakentech/blueprint-auth/middleware";
import { authConfig } from "@/lib/auth/config";
export const proxy = createAuthMiddleware(authConfig);
export const config = {
matcher: ["/", "/((?!api|_next|_vercel|.*\\..*).*)"],
};
:::tip[Post-auth middleware mutations] If you modify headers after authMiddleware has run, wrap
the final response with forwardHeaders
to ensure they are visible in getServerSideProps via context.req.headers. :::
API routes​
You need to create 4 different API routes to handle the authentication flow. We recommend using the following structure:
pages/api/auth/login.tspages/api/auth/logout.tspages/api/auth/session.tspages/api/graphql/kraken.ts
Login API handler​
For email-and-password login, create the handler described in
Email & password: login API handler.
The route must match apiRoutes.login in your configuration. OAuth uses its own callback flow
instead.
Logout API handler​
Create the logout API handler using
createLogoutHandler.
import { createLogoutHandler } from "@krakentech/blueprint-auth/server";
import { authConfig } from "@/lib/auth/config";
export default createLogoutHandler(authConfig);
Session API handler​
Create the session API handler using
createSessionHandler.
import { createSessionHandler } from "@krakentech/blueprint-auth/server";
import { authConfig } from "@/lib/auth/config";
export default createSessionHandler(authConfig);
GraphQL API handler​
Create the GraphQL API handler using
createGraphQLHandler.
import { createGraphQLHandler } from "@krakentech/blueprint-auth/server";
import { authConfig } from "@/lib/auth/config";
export default createGraphQLHandler(authConfig);
Client​
The package provides React hooks to handle the authentication flow, as well as a context provider in order to make configuration available across your app.
Context providers​
The React hooks created using
createClientSideAuth rely on the
AuthProvider for configuration, and
QueryClientProvider from
@tanstack/react-query for data fetching.
Wrap your entire application with
AuthProvider and
QueryClientProvider.
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import type { AppProps } from "next/app";
import { useState } from "react";
import { AuthProvider } from "@/lib/auth/client";
export default function App({ Component, pageProps }: AppProps) {
const [queryClient] = useState(() => new QueryClient());
return (
<QueryClientProvider client={queryClient}>
<AuthProvider>
<Component {...pageProps} />
</AuthProvider>
</QueryClientProvider>
);
}
Client Functions​
Create the client-side functions using
createClientSideAuth.
import { createClientSideAuth } from "@krakentech/blueprint-auth/client";
import { authConfig } from "./config";
export const {
AuthProvider,
useGraphQLClient,
useKrakenAuthErrorHandler,
useLogin,
useLogout,
useSession,
} = createClientSideAuth(authConfig, {
defaultTarget: "kraken",
router: "pages-router",
});
Server Side Rendering​
For pages that depend on authenticated GraphQL data, use the
createServerSideAuth
factory to bind configuration and an optional cache adapter. Keep the request context explicit.
import { createServerSideAuth } from "@krakentech/blueprint-auth/server";
import { cacheAdapter } from "./cache";
import { authConfig } from "./config";
export const { getAuth, getGraphQLClient, getSession, prefetchSession, redirectToLogin } =
createServerSideAuth(authConfig, { cacheAdapter });
Recipes​
This section provides examples of how to use the authentication hooks in your application.
Login form​
Choose the authentication method your Kraken environment supports:
- Kraken OAuth is recommended for new integrations.
- Email & password: Pages Router covers the login API handler, form, redirects, and error handling.
Logout button​
Create a logout button using the useLogout
hook.
import { useLogout } from "@/lib/auth/client";
export function LogoutButton() {
const logout = useLogout();
return (
<button onClick={() => logout.mutate()} disabled={logout.isPending}>
Logout
</button>
);
}
Supporting Internationalization (i18n)​
If your application supports multiple languages with localized URLs, configure the auth package to use your i18n library's pathname translation.
The Pages Router has native i18n support for locale detection and prefixing (e.g., /fr/dashboard).
However, the auth package's i18n config is needed when you use translated route segments (e.g.,
/fr/tableau-de-bord instead of /fr/dashboard).
Configuration with next-intl​
import { createAuthConfig } from "@krakentech/blueprint-auth";
import { getPathname } from "@/i18n/navigation";
import { hasLocale } from "next-intl";
import { routing } from "@/i18n/routing";
export const authConfig = createAuthConfig({
appRoutes: {/* ... */},
i18n: {
localeCookie: "NEXT_LOCALE",
getLocalizedPathname({ locale, url, ...href }) {
const requestedLocale = locale ?? url.pathname.split("/")[1];
return getPathname({
href,
locale: hasLocale(routing.locales, requestedLocale)
? requestedLocale
: routing.defaultLocale,
});
},
},
// ... other config
});
See the next-intl documentation for setup instructions.
How It Works​
When configured:
- Middleware: Matches routes using localized pathnames
- Redirects: Sends users to localized destinations after login/logout
- Error Handling: Redirects to localized login pages on auth errors
For detailed examples including URL-based locale detection, simple path prefix patterns, and how to use with Next.js native i18n, see the i18n guide.
Using session data​
Create a component rendering different UI based on the user's authentication status using the
useSession hook.
import { useSession } from "@/lib/auth/client";
import { Logo } from "@/components/Logo";
import { LogoutButton } from "@/components/LogoutButton";
import { MasqueradeIcon } from "@/icons/MasqueradeIcon";
import { NavigationMenu } from "@/components/NavigationMenu";
import { UserMenu } from "@/components/UserMenu";
export function Header() {
const { data: session, isError, isFetched } = useSession();
return (
<header>
<NavigationMenu />
<Logo />
{!isFetched ? (
<p role="status">Loading session...</p>
) : isError ? (
<p role="alert">Unable to load the session.</p>
) : (
<>
{session.authMethod === "masquerade" && <MasqueradeIcon />}
{session.isAuthenticated ? <LogoutButton /> : <UserMenu />}
</>
)}
</header>
);
}
Prefetching session data​
You can prefetch the session data on the server by using the prefetchSession function. This will
ensure session data is available on the client-side as soon as the page loads.
This is only relevant in pages using both the useSession hook, and server-side rendering with
getServerSideProps.
import type { AppProps } from "next/app";
import { useState } from "react";
import {
type DehydratedState,
HydrationBoundary,
QueryClient,
QueryClientProvider,
} from "@tanstack/react-query";
import { AuthProvider } from "@/lib/auth/client";
export default function App({
Component,
pageProps,
}: AppProps<{ dehydratedState?: DehydratedState }>) {
const [queryClient] = useState(() => {
return new QueryClient({
defaultOptions: {
queries: { staleTime: 60 * 1000 },
},
});
});
return (
<QueryClientProvider client={queryClient}>
<HydrationBoundary state={pageProps.dehydratedState}>
<AuthProvider>
<Component {...pageProps} />
</AuthProvider>
</HydrationBoundary>
</QueryClientProvider>
);
}
import type { GetServerSidePropsContext } from "next";
import { QueryClient, dehydrate } from "@tanstack/react-query";
import { prefetchSession } from "@/lib/auth/server";
import { useSession } from "@/lib/auth/client";
export default function SubscribePage() {
const { data: session, isError, isFetched } = useSession();
if (!isFetched) {
return <p role="status">Loading session...</p>;
}
if (isError) {
return <p role="alert">Unable to load the session.</p>;
}
if (!session.isAuthenticated) {
return <NewUserSubscribeForm />;
}
return <ExistingUserSubscribeForm />;
}
export async function getServerSideProps(context: GetServerSidePropsContext) {
const queryClient = new QueryClient();
await prefetchSession({
context,
queryClient,
});
return {
props: {
dehydratedState: dehydrate(queryClient),
},
};
}
GraphQL queries in getServerSideProps​
import { BlueprintAuthErrorCode } from "@krakentech/blueprint-auth";
import type { GetServerSidePropsContext, InferGetServerSidePropsType } from "next";
import * as z from "zod";
import { PropertyCard } from "@/components/PropertyCard";
import { getAuth, getGraphQLClient, redirectToLogin } from "@/lib/auth/server";
import { graphql } from "@/lib/graphql";
export default function AccountPage({
account,
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
return (
<div>
<h1>Account #{account.number}</h1>
<h2>Properties</h2>
{account.properties.map((property) => (
<PropertyCard key={property.id} property={property} />
))}
</div>
);
}
export const AccountPageQuery = graphql(`
query AccountPageQuery($accountNumber: String!) {
account(accountNumber: $accountNumber) {
number
properties {
id
address
}
}
}
`);
const accountSchema = z.object({
number: z.string().min(1),
properties: z.array(z.object({ address: z.string().min(1), id: z.string().min(1) })),
});
export async function getServerSideProps(
context: GetServerSidePropsContext<{ accountNumber: string }>,
) {
const accountNumber = context.params?.accountNumber;
if (!accountNumber) {
return { notFound: true };
}
const auth = await getAuth.user({ context });
if (!auth) {
return redirectToLogin({
context,
errorCode: BlueprintAuthErrorCode.AuthenticationRequired,
});
}
const graphqlClient = getGraphQLClient.user({ auth });
const { account } = await graphqlClient.request(AccountPageQuery, {
accountNumber,
});
const result = accountSchema.safeParse(account);
if (!result.success) {
return { notFound: true };
}
return {
props: {
account: result.data,
},
};
}
GraphQL query fields are often nullable. Validate the response before you pass it to the page. This example returns a not found response when the account data is invalid.
The example does not catch request errors. Handle expected errors in getServerSideProps. Use an
error boundary in _app.tsx for unexpected render errors.
GraphQL queries with @tanstack/react-query​
This example uses gql.tada for type-safe GraphQL queries. The graphql
function is exported from your project's GraphQL setup.
import type { BlueprintGraphQLClient } from "@krakentech/blueprint-auth";
import { queryOptions, skipToken, useQuery } from "@tanstack/react-query";
import * as z from "zod";
import { graphql } from "@/lib/graphql";
import { useGraphQLClient } from "@/lib/auth/client";
export const AccountQuery = graphql(`
query AccountPageQuery($accountNumber: String!) {
account(accountNumber: $accountNumber) {
number
properties {
id
address
}
}
}
`);
const accountSchema = z.object({
number: z.string().min(1),
properties: z.array(z.object({ address: z.string().min(1), id: z.string().min(1) })),
});
export function accountQueryOptions(graphQLClient: BlueprintGraphQLClient, accountNumber?: string) {
return queryOptions({
queryKey: ["account", accountNumber],
queryFn: accountNumber
? async () => {
const { account } = await graphQLClient.request(AccountQuery, {
accountNumber,
});
return accountSchema.parse(account);
}
: skipToken,
});
}
export function useAccount(accountNumber?: string) {
const graphQLClient = useGraphQLClient();
return useQuery(accountQueryOptions(graphQLClient, accountNumber));
}
import { useRouter } from "next/router";
import { ErrorMessage } from "@/components/ErrorMessage";
import { PropertyCard } from "@/components/PropertyCard";
import { useAccount } from "@/queries/account";
export default function AccountPage() {
const router = useRouter();
const queryValue = router.query.accountNumber;
const accountNumber = router.isReady
? Array.isArray(queryValue)
? queryValue[0]
: queryValue
: undefined;
const { data: account, error, isError, isPending } = useAccount(accountNumber);
if (!router.isReady) {
return <p role="status">Loading account...</p>;
}
if (!accountNumber) {
return <p role="alert">The account number is missing.</p>;
}
if (isPending) {
return <p role="status">Loading account...</p>;
}
if (isError) {
return <ErrorMessage error={error} />;
}
if (!account) {
return <p role="alert">Account not found.</p>;
}
return (
<div>
<h1>Account #{account.number}</h1>
<h2>Properties</h2>
{account.properties.map((property) => (
<PropertyCard key={property.id} property={property} />
))}
</div>
);
}
Queries made with @tanstack/react-query can be prefetched on the server. Check out our
Prefetching session data recipe, or
@tanstack/react-query's documentation
to learn how to enable prefetching.
Learn more
Building on top of the example above, you can prefetch the account data by defining a
getServerSideProps function like this:
import { BlueprintAuthErrorCode } from "@krakentech/blueprint-auth";
import { dehydrate, QueryClient } from "@tanstack/react-query";
import type { GetServerSidePropsContext } from "next";
import { getAuth, getGraphQLClient, prefetchSession, redirectToLogin } from "@/lib/auth/server";
import { accountQueryOptions } from "@/queries/account";
export default function AccountPage() {
// ...
}
export async function getServerSideProps(
context: GetServerSidePropsContext<{ accountNumber: string }>,
) {
const accountNumber = context.params?.accountNumber;
if (!accountNumber) {
return { notFound: true };
}
const queryClient = new QueryClient();
const auth = await getAuth.user({ context });
if (!auth) {
return redirectToLogin({
context,
errorCode: BlueprintAuthErrorCode.AuthenticationRequired,
});
}
const graphqlClient = getGraphQLClient.user({ auth });
await Promise.all([
queryClient.prefetchQuery(accountQueryOptions(graphqlClient, accountNumber)),
prefetchSession({
context,
queryClient,
}),
]);
return {
props: {
dehydratedState: dehydrate(queryClient),
},
};
}
Next Steps​
Explore these features after you complete the basic setup:
- Organization-scoped authentication: restrict authentication to an organization.
- Masquerade: let support staff impersonate users.
- Anonymous authentication: provide access with scoped tokens.
- Kraken OAuth: enable OAuth authentication.
For detailed information about all available functions, types, and configuration options, see the API Reference.