Migrating authentication to v40
This guide describes the changes introduced in Blueprint Auth v40. Its examples target that version, not the current API. If you are upgrading to v45, also follow Migrating authentication to v45.
This guide will help you migrate @krakentech/blueprint-auth to version 40, which introduces App
Router support and a unified configuration system.
Overview​
Version 40 of @krakentech/blueprint-auth eliminates configuration duplication by introducing a
single source of truth for authentication. Instead of repeating the same settings across middleware,
API routes, and client code, you now define your authentication configuration once in a central
authConfig object and import it wherever needed.
This becomes possible because all user-facing functions now expect a subset of a single unified interface. Previous versions came close to this design, but some functions had slightly different configuration requirements that prevented using a single shared config object. Now that these interfaces have been aligned, you can pass the same config everywhere.
This architectural shift means you maintain a single source of truth instead of duplicating configuration across multiple files. When you need to update authentication behavior, you make the change in one place rather than hunting through your codebase to ensure all instances stay in sync.
The new implementation brings full App Router support through automatic context detection, meaning the same utilities work seamlessly whether you're using the Pages Router or the App Router. All handlers have been converted to factory functions that accept your config as a parameter, creating a consistent API surface across the entire package. You'll also get improved error handling with structured error codes and messages that guide you toward solutions, as well as better TypeScript inference.
For complete details on all functions, hooks, and configuration options mentioned in this guide, see the API Reference.
If you identify a breaking change we've missed, please reach out so we can add it to this page. If you run into any issues during migration, don't hesitate to reach out to the team for support.
Migration Steps​
We'll work from the inside out: first create the centralized config, then wire it up to your runtime components (middleware, API handlers, client), update any custom code that references old APIs, and finally verify everything works.
Step 1: Update packages​
The @krakentech/blueprint-auth and @krakentech/blueprint-api packages must be upgraded together.
- pnpm
- npm
- yarn
- bun
pnpm update @krakentech/blueprint-auth@^40 @krakentech/blueprint-api@^4
If your project relies on one of the other packages listing @krakentech/blueprint-api as a
peer-dependency, you must update them as well:
| Package | Command |
|---|---|
@krakentech/blueprint-utils | pnpm update @krakentech/blueprint-utils@^55 |
npm update @krakentech/blueprint-auth@^40 @krakentech/blueprint-api@^4
If your project relies on one of the other packages listing @krakentech/blueprint-api as a
peer-dependency, you must update them as well:
| Package | Command |
|---|---|
@krakentech/blueprint-utils | npm update @krakentech/blueprint-utils@^55 |
yarn upgrade @krakentech/blueprint-auth@^40 @krakentech/blueprint-api@^4
If your project relies on one of the other packages listing @krakentech/blueprint-api as a
peer-dependency, you must update them as well:
| Package | Command |
|---|---|
@krakentech/blueprint-utils | yarn upgrade @krakentech/blueprint-utils@^55 |
bun update @krakentech/blueprint-auth@^40 @krakentech/blueprint-api@^4
If your project relies on one of the other packages listing @krakentech/blueprint-api as a
peer-dependency, you must update them as well:
| Package | Command |
|---|---|
@krakentech/blueprint-utils | bun update @krakentech/blueprint-utils@^55 |
If you try to upgrade to @krakentech/blueprint-api@4.0.0 or @krakentech/blueprint-auth@40.0.0 or
@krakentech/blueprint-utils@55.0.0 (without prefixing the version with ^ or ~) you will get a
warning about a faulty release. Please skip these versions and upgrade directly to the latest
version instead.
Step 2: Configuration​
Create a centralized config that you'll reuse everywhere. See the
createAuthConfig API reference for
all available configuration options.
import { createAuthConfig } from "@krakentech/blueprint-auth";
export const authConfig = createAuthConfig({
apiRoutes: {
graphql: { kraken: "/api/graphql" },
login: "/api/auth/login",
logout: "/api/auth/logout",
session: "/api/auth/session",
},
appRoutes: {
dashboard: { pathname: "/dashboard" },
login: { pathname: "/login" },
},
krakenConfig: {
graphqlEndpoint: process.env.KRAKEN_GRAPHQL_ENDPOINT,
},
// Optional: customize behavior
customization: {
getCookieOptions: (cookieName) => {
if (cookieName === "accessToken") {
return { maxAge: 3600 };
}
return undefined; // use defaults
},
},
});
The router option is required if you're using client-side authentication. However, it is
not part of authConfig. Instead, it's passed as the second argument to createClientSideAuth
(see Step 5 below).
The router option must be set to either "pages-router" or "app-router" depending on your
Next.js setup. This tells the auth package which routing strategy to use for client-side redirects.
If you're only using server-side functions (middleware, API handlers, getServerSideProps,
etc.), you don't need to specify a router.
Instead of repeating your authentication configuration across multiple files, you now maintain
everything in one place. When you need to update your auth setup, you change it here and the update
automatically applies everywhere you've imported authConfig.
If you're migrating from a previous version, review the config-related breaking changes below before
creating your authConfig. This ensures you start with a valid configuration.
| Change | What to do |
|---|---|
| Customization options |
|
| Validation options |
|
| Route extractors |
|
| Custom headers signature |
|
| Cookie options return |
|
See the Breaking Changes Reference section for detailed examples of each migration.
Now that you have your centralized config, the next step is to use it in your middleware where authentication checks happen first.
Step 3: Middleware​
Replace the middleware with the new
createAuthMiddleware factory
function.
// Before
import { authMiddleware } from "@krakentech/blueprint-auth/middleware";
import type { NextRequest, NextResponse } from "next/server";
export async function middleware(req: NextRequest) {
const res = NextResponse.next();
return await authMiddleware({
appRoutes: {
dashboard: { pathname: "/dashboard" },
login: { pathname: "/login" },
},
krakenConfig: {
graphqlEndpoint: process.env.KRAKEN_GRAPHQL_ENDPOINT,
},
req,
res,
});
}
// After
import { createAuthMiddleware } from "@krakentech/blueprint-auth/middleware";
import { authConfig } from "@/lib/auth/config";
export const middleware = createAuthMiddleware(authConfig);
Use the recommended
catch-all matcher
in config.matcher.
With middleware protecting your routes, let's update the API handlers that actually perform authentication operations.
Step 4: API handlers​
Replace each handler with a one-liner factory call. See the API reference for details:
createLoginHandler,
createLogoutHandler,
createSessionHandler,
createGraphQLHandler,
createKrakenOAuthHandler,
createUpdateOrgTokenHandler.
- Login
- Logout
- Session
- GraphQL
- Kraken OAuth
- Update Org Token
// Before
import { loginHandler } from "@krakentech/blueprint-auth/server";
import type { NextApiRequest, NextApiResponse } from "next";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
await loginHandler({
krakenConfig: {/* ... */},
req,
res,
// ... more config
});
}
// After
import { createLoginHandler } from "@krakentech/blueprint-auth/server";
import { authConfig } from "@/lib/auth/config";
export default createLoginHandler(authConfig);
// Before
import { logoutHandler } from "@krakentech/blueprint-auth/server";
import type { NextApiRequest, NextApiResponse } from "next";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
await logoutHandler({ req, res });
}
// After
import { createLogoutHandler } from "@krakentech/blueprint-auth/server";
export default createLogoutHandler();
// Before
import { sessionHandler } from "@krakentech/blueprint-auth/server";
import type { NextApiRequest, NextApiResponse } from "next";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
await sessionHandler({ req, res });
}
// After
import { createSessionHandler } from "@krakentech/blueprint-auth/server";
export default createSessionHandler();
// Before
import { graphqlHandler } from "@krakentech/blueprint-auth/server";
import type { NextApiRequest, NextApiResponse } from "next";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
await graphqlHandler({
krakenConfig: {/* ... */},
req,
res,
// ... more config
});
}
// After
import { createGraphQLHandler } from "@krakentech/blueprint-auth/server";
import { authConfig } from "@/lib/auth/config";
export default createGraphQLHandler(authConfig);
// Before
import { krakenOAuthHandler } from "@krakentech/blueprint-auth/server";
import type { NextApiRequest, NextApiResponse } from "next";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
await krakenOAuthHandler({
appRoutes: {/* ... */},
apiRoutes: {/* ... */},
krakenConfig: {/* ... */},
req,
res,
// ... more config
});
}
// After
import { createKrakenOAuthHandler } from "@krakentech/blueprint-auth/server";
import { authConfig } from "@/lib/auth/config";
export default createKrakenOAuthHandler(authConfig);
// Before
import { updateOrgTokenHandler } from "@krakentech/blueprint-auth/server";
import type { NextApiRequest, NextApiResponse } from "next";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
await updateOrgTokenHandler({
krakenConfig: {/* ... */},
req,
res,
// ... more config
});
}
// After
import { createUpdateOrgTokenHandler } from "@krakentech/blueprint-auth/server";
import { authConfig } from "@/lib/auth/config";
export default createUpdateOrgTokenHandler(authConfig);
Your server-side auth is now set up. Time to wire up the client-side code that your components will use.
Step 5: Client-side auth​
The client-side auth setup has several important changes. Your authConfig now includes appRoutes
field, the AuthProvider no longer takes appRoutes as a prop, and the auth hooks have updated
signatures.
createClientSideAuth​
The function now takes your centralized authConfig as the first parameter, and a client-only
config object as the second parameter. The client-only config includes defaultTarget and router.
Your centralized authConfig must include appRoutes (dashboard and login pathnames, previously
passed to AuthProvider). See the
createClientSideAuth API reference
for details.
// Before
import { createClientSideAuth } from "@krakentech/blueprint-auth/client";
export const {
AuthProvider,
useAuth,
useLogin,
useLogout,
useSession,
useGraphQLClient,
useKrakenAuthErrorHandler,
} = createClientSideAuth({
apiRoutes: {
graphql: { kraken: "/api/graphql" },
login: "/api/auth/login",
logout: "/api/auth/logout",
session: "/api/auth/session",
},
defaultTarget: "kraken",
});
// After
import { createClientSideAuth } from "@krakentech/blueprint-auth/client";
import { authConfig } from "@/lib/auth/config";
export const {
AuthProvider,
useAuth,
useLogin,
useLogout,
useSession,
useGraphQLClient,
useKrakenAuthErrorHandler,
} = createClientSideAuth(authConfig, {
defaultTarget: "kraken",
router: "pages-router",
});
AuthProvider​
The appRoutes prop is removed since routes are now in your authConfig. Handlers are now passed
as individual props (onLoginSuccess, onLoginError, onLogoutSuccess, onLogoutError) instead
of a single handlers object. Handler signatures have changed: all handlers now receive a
defaultRedirect callback function, onLoginError receives an ErrorCode string instead of a
KrakenError[] array, and onLogout splits into separate onLogoutSuccess and onLogoutError
handlers for better error handling. See the
AuthProvider API reference for details.
// Before
<AuthProvider
appRoutes={{
dashboard: { pathname: "/dashboard" },
login: { pathname: "/login" },
}}
handlers={{
onLoginSuccess: () => {
// custom success logic
},
onLoginError: (errors) => {
// custom error logic
},
onLogout: () => {
// custom logout logic
},
}}
>
{children}
</AuthProvider>
// After
<AuthProvider
onLoginSuccess={(defaultRedirect) => {
// custom success logic, optionally call defaultRedirect()
}}
onLoginError={(errorCode, defaultRedirect) => {
// custom error logic, optionally call defaultRedirect()
}}
onLogoutSuccess={(defaultRedirect) => {
// custom logout success logic
}}
onLogoutError={(errorCode, defaultRedirect) => {
// custom logout error logic
}}
>
{children}
</AuthProvider>
Hooks​
If you're using auth hooks with options, their signatures have changed.
useLogin​
The shouldRedirectToNextPage boolean flag is replaced by nextPage. Set it to null to prevent
redirect, or provide a path to redirect to a specific page. See the
useLogin API reference for details.
// Before
const login = useLogin({
shouldRedirectToNextPage: false,
});
// After
const login = useLogin({
nextPage: null,
});
useSession​
The return type of the useSession hook has changed. The SessionState type now includes an
authMethod field and removes several boolean flags. See the
useSession API reference for details.
// Before
const { data: session } = useSession();
if (session.isMasquerading) {
return <MasqueradeBanner />;
}
if (session.isScopedSession) {
return <PaymentDetailsCard />;
}
if (session.isMobileWebView) {
return <UpdateAppLink />;
}
// After
const { data: session } = useSession();
if (session.authMethod === "masquerade") {
return <MasqueradeBanner />;
}
if (session.authMethod === "scoped") {
return <PaymentDetailsCard />;
}
if (session.authMethod === "mobile-web-view") {
return <UpdateAppLink />;
}
useLogout​
The hook now accepts an options object. Use nextPage to control the redirect destination, or set
it to null to prevent redirect. See the
useLogout API reference for details.
// Before
const logout = useLogout();
// After
const logout = useLogout({
nextPage: "/goodbye",
});
useKrakenAuthErrorHandler​
The hook now takes an options object instead of a single function. The onError callback receives a
defaultRedirect function as its second parameter, and you can specify redirectType ("push" or
"replace"). The getAuthErrorCode function is no longer returned from the hook; import
getErrorCode from
@krakentech/blueprint-auth/utils instead. See the
useKrakenAuthErrorHandler API reference
for details.
// Before
const { getAuthErrorCode, handleKrakenErrors } = useKrakenAuthErrorHandler((error) => {
// custom error handling
});
const errorCode = getAuthErrorCode(error);
// After
import { getErrorCode } from "@krakentech/blueprint-auth/utils";
const { handleKrakenErrors } = useKrakenAuthErrorHandler({
onError: (error, defaultRedirect) => {
// custom error handling
},
redirectType: "replace",
});
const errorCode = getErrorCode(error);
The core migration is complete. Now let's update any remaining server-side code.
Step 6: Server-side auth​
Server-side authentication has several important changes. All relate to making the API more consistent and enabling App Router support.
Context parameter change​
Server-side functions now accept a context parameter as part of their options object instead of
separate req and res parameters. This makes the API more versatile by allowing server functions
to adapt to different execution environments, from traditional API routes to newer patterns like
Server Components and Server Actions.
Not all server functions work in every Next.js execution environment. For example, some utilities require request/response objects and won't work in Server Components. See the Execution Environment guide in the API reference for details on which functions work where.
- SSR
- API Routes
- Middleware
// Before
import { getUserScopedGraphQLClient } from "@krakentech/blueprint-auth/server";
import type { GetServerSidePropsContext } from "next";
export async function getServerSideProps({ req, res }: GetServerSidePropsContext) {
const client = getUserScopedGraphQLClient({
krakenConfig: {/* ... */},
req,
res,
});
}
// After
import { getUserScopedGraphQLClient } from "@krakentech/blueprint-auth/server";
import { authConfig } from "@/lib/auth/config";
import type { GetServerSidePropsContext } from "next";
export async function getServerSideProps(context: GetServerSidePropsContext) {
const client = getUserScopedGraphQLClient(authConfig, { context });
}
// Before
import { getUserScopedGraphQLClient } from "@krakentech/blueprint-auth/server";
import type { NextApiRequest, NextApiResponse } from "next";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const client = getUserScopedGraphQLClient({
krakenConfig: {/* ... */},
req,
res,
});
}
// After
import { getUserScopedGraphQLClient } from "@krakentech/blueprint-auth/server";
import { authConfig } from "@/lib/auth/config";
import type { NextApiRequest, NextApiResponse } from "next";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const userClient = getUserScopedGraphQLClient(authConfig, {
context: { req, res },
});
}
// Before
import { getUserScopedGraphQLClient } from "@krakentech/blueprint-auth/server";
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
export async function middleware(req: NextRequest) {
const res = NextResponse.next();
const client = getUserScopedGraphQLClient({
krakenConfig: {/* ... */},
req,
res,
});
}
// After
import { getUserScopedGraphQLClient } from "@krakentech/blueprint-auth/server";
import { authConfig } from "@/lib/auth/config";
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
export async function middleware(req: NextRequest) {
const res = NextResponse.next();
const userClient = getUserScopedGraphQLClient(authConfig, {
context: { req, res },
});
}
This applies to all server-side utilities including getUserScopedGraphQLClient,
getOrganizationScopedGraphQLClient, getSession, login, logout, and more.
Function renaming​
Several server-side utilities have been renamed for clarity.
- getSessionData
- getServerSideGraphQLClients
- createKrakenOAuthURI
Renamed to getSession.
// Before
import { getSessionData } from "@krakentech/blueprint-auth/server";
import type { GetServerSidePropsContext } from "next";
export async function getServerSideProps({ req, res }: GetServerSidePropsContext) {
const session = await getSessionData({
krakenConfig: {/* ... */},
req,
res,
});
}
// After
import { getSession } from "@krakentech/blueprint-auth/server";
import type { GetServerSidePropsContext } from "next";
export async function getServerSideProps(context: GetServerSidePropsContext) {
const session = await getSession({ context });
}
Split into two separate functions: getUserScopedGraphQLClient and
getOrganizationScopedGraphQLClient. Import only what you need.
// Before
import { getServerSideGraphQLClients } from "@krakentech/blueprint-auth/server";
import type { GetServerSidePropsContext } from "next";
export async function getServerSideProps({ req, res }: GetServerSidePropsContext) {
const { userScopedClient, organizationScopedClient } = await getServerSideGraphQLClients({
krakenConfig: {/* ... */},
req,
res,
});
}
// After
import {
getUserScopedGraphQLClient,
getOrganizationScopedGraphQLClient,
} from "@krakentech/blueprint-auth/server";
import { authConfig } from "@/lib/auth/config";
import type { GetServerSidePropsContext } from "next";
export async function getServerSideProps(context: GetServerSidePropsContext) {
const userClient = getUserScopedGraphQLClient(authConfig, { context });
const orgClient = getOrganizationScopedGraphQLClient(authConfig, { context });
}
Renamed to generateKrakenOAuthURI for consistency with similar utilities.
// Before
import { createKrakenOAuthURI } from "@krakentech/blueprint-auth/server";
import type { GetServerSidePropsContext } from "next";
export async function getServerSideProps({ req, res }: GetServerSidePropsContext) {
const oauthUri = await createKrakenOAuthURI({
krakenConfig: {/* ... */},
apiRoutes: {/* ... */},
req,
res,
});
}
// After
import { generateKrakenOAuthURI } from "@krakentech/blueprint-auth/server";
import { authConfig } from "@/lib/auth/config";
import type { GetServerSidePropsContext } from "next";
export async function getServerSideProps(context: GetServerSidePropsContext) {
const oauthUri = await generateKrakenOAuthURI(authConfig, { context });
}
getSession​
The return type of the getSession server function has changed. The SessionState type now
includes an authMethod field and removes several boolean flags. See the
getSession API reference for details.
// Before
import { getSession } from "@krakentech/blueprint-auth/server";
import type { GetServerSidePropsContext } from "next";
export async function getServerSideProps(context: GetServerSidePropsContext) {
const session = await getSession({ context });
if (session.isMasquerading) {
// Handle masquerade session
}
if (session.isScopedSession) {
// Handle scoped session
}
if (session.isMobileWebView) {
// Handle mobile web view session
}
}
// After
import { getSession } from "@krakentech/blueprint-auth/server";
import type { GetServerSidePropsContext } from "next";
export async function getServerSideProps(context: GetServerSidePropsContext) {
const session = await getSession({ context });
if (session.authMethod === "masquerade") {
// Handle masquerade session
}
if (session.authMethod === "scoped") {
// Handle scoped session
}
if (session.authMethod === "mobile-web-view") {
// Handle mobile web view session
}
}
For a cleaner API with less boilerplate, consider using
createServerSideAuth. This
factory function returns pre-configured versions of all server utilities that only require runtime
parameters.
Show example
// Instead of repeating config across calls
import { getSession, getUserScopedGraphQLClient } from "@krakentech/blueprint-auth/server";
import type { GetServerSidePropsContext } from "next";
export async function getServerSideProps(context: GetServerSidePropsContext) {
const session = await getSession({ context });
const client = getUserScopedGraphQLClient({
context,
krakenConfig: {/* ... */},
});
}
// You can use the factory
import { createServerSideAuth } from "@krakentech/blueprint-auth/server";
import { authConfig } from "@/lib/auth/config";
import type { GetServerSidePropsContext } from "next";
const { getSession, getUserScopedGraphQLClient } = createServerSideAuth(authConfig);
export async function getServerSideProps(context: GetServerSidePropsContext) {
const session = await getSession({ context });
const client = getUserScopedGraphQLClient({ context });
}
See the API reference for details.
Step 7: Cookies​
The cookie constants have been renamed for clarity.
// Before
import { TOKEN_COOKIE_KEYS } from "@krakentech/blueprint-auth";
const token = cookies[TOKEN_COOKIE_KEYS.access];
// After
import { AUTH_COOKIE } from "@krakentech/blueprint-auth";
const token = cookies[AUTH_COOKIE.accessToken];
Also make sure to pdate cookie names if you're manipulating auth cookies manually or in tests.
// Before
await setCookies({
cookies: [
{ name: "access", value: accessToken },
{ name: "refresh", value: refreshToken },
],
});
// After
await setCookies({
cookies: [
{ name: "accessToken", value: accessToken },
{ name: "refreshToken", value: refreshToken },
],
});
Before testing, verify all required environment variables are configured.
If you're using server-side GraphQL clients in getServerSideProps, getStaticProps, API routes, or middleware, they also need updating.
Step 8: Internationalization (i18n)​
Version 40 introduces a new i18n configuration to support localized routing and pathname translation. If your app uses Next.js built-in i18n features and supports multiple locales, you need to configure the auth package accordingly.
For comprehensive documentation, configuration examples, and integration patterns, see the i18n guide.
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({
i18n: {
localeCookie: "NEXT_LOCALE",
getLocalizedPathname({ locale, pathname }) {
return getPathname({
href: { pathname },
locale: hasLocale(routing.locales, locale) ? locale : routing.defaultLocale,
});
},
},
// ... other config
});
See the i18n guide for detailed setup instructions, common patterns, and FAQ.
Step 9: Environment variables​
Your environment variables remain the same, with one addition: krakenConfig.xClientIpSecretKey is
now a required option. Following best practices for handling secrets, we recommend setting the
KRAKEN_X_CLIENT_IP_SECRET_KEY environment variable to provide this value.
With everything in place, thoroughly test your authentication flows.
Step 10: Test everything​
You know your application best. Run your type checker, build your project, and verify your authentication flows work as expected. Consider testing login, logout, token refresh, and protected routes. If you use OAuth, masquerade, organization-scoped auth, or anonymous auth features, give those a check too.
Breaking Changes Reference​
Configuration Changes​
Config gets organized
Options are now grouped logically into customization and validation objects. See the
createAuthConfig API reference for
all configuration options.
// Before
{
getCookieOptions: (cookieName) => ({ /* ... */ }),
setCustomHeaders: (headers) => { /* ... */ },
shouldValidateUrl: !process.env.IS_TEST,
}
// After
{
customization: {
getCookieOptions: (cookieName) => ({ /* ... */ }),
setCustomHeaders: (headers, authScope) => { /* ... */ },
},
validation: {
validateGraphQLEndpointUrl: !process.env.IS_TEST,
},
}
Custom headers get more context
The setCustomHeaders callback now receives authScope for user vs organization requests. This
unifies the API across the package; write your header logic once and reuse it everywhere.
// Before
setCustomHeaders: (headers: Headers) => void
// After
customization: {
setCustomHeaders: (headers: Headers, authScope: "user" | "organization") => void | Promise<void>
}
Cookie options allow selective customization
// returned full context object
getCookieOptions: (cookieName: CookieName) => Omit<HttpContext, "req" | "res">;
// nested in customization, returns just cookie options
customization: {
getCookieOptions: (cookieName: CookieName) => SerializeOptions | undefined;
}
Route config gets consistent names
Route parameter extractors now use consistent naming and accept an options object for easier extensibility.
// Before
appRoutes: {
anon: {
getPreSignedKey: (url) => ({ preSignedKey }),
},
masquerade: {
getUserIdAndMasqueradeToken: (url) => ({ userId, masqueradeToken }),
},
}
// After
appRoutes: {
anon: {
getAnonParams({ url }) {
return { preSignedKey };
},
},
masquerade: {
getMasqueradeParams({ url }) {
return { userId, masqueradeToken };
},
},
}
Middleware​
Middleware gets factory pattern
The middleware now uses the factory pattern with
createAuthMiddleware.
// Before
import { authMiddleware } from "@krakentech/blueprint-auth/middleware";
import type { NextRequest, NextResponse } from "next/server";
export async function middleware(req: NextRequest) {
const res = NextResponse.next();
return await authMiddleware({
appRoutes: {/* ... */},
krakenConfig: {/* ... */},
req,
res,
// ... more config
});
}
// After
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|.*\\..*).*)"],
};
Redirect callbacks support fallback
Before:
customSuccessResponse: ({ url }) => NextResponse.redirect(url);
After:
customSuccessResponse({ url }, { redirect }) {
if (someCondition) {
return redirect(url);
}
// return undefined to use default
}
The customSuccessResponse and customErrorResponse callbacks now receive helper functions
(redirect, rewrite) as a second parameter. This dependency injection pattern eliminates the need
to import NextResponse in your config file, making it framework-agnostic and browser-safe.
API Handlers​
Handlers now use factories
Handlers are now factories that work with the unified config. You can export them directly from your
API routes, reducing boilerplate code significantly. See the API reference for:
createLoginHandler,
createLogoutHandler,
createSessionHandler,
createGraphQLHandler.
// Before
import { loginHandler } from "@krakentech/blueprint-auth/server";
import type { NextApiRequest, NextApiResponse } from "next";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
await loginHandler({
krakenConfig: { graphqlEndpoint: process.env.KRAKEN_GRAPHQL_ENDPOINT },
req,
res,
edgeConfig: {/* ... */},
encryption: {/* ... */},
});
}
// After
import { createLoginHandler } from "@krakentech/blueprint-auth/server";
import { authConfig } from "@/lib/auth/config";
export default createLoginHandler(authConfig);
GraphQL client functions split for clarity
The single getServerSideGraphQLClients function is replaced by two separate functions:
getUserScopedGraphQLClient
and
getOrganizationScopedGraphQLClient.
Import only what you need.
// Before
import { getServerSideGraphQLClients } from "@krakentech/blueprint-auth/server";
const { userScopedClient, organizationScopedClient } = await getServerSideGraphQLClients({
krakenConfig: {/* ... */},
req,
res,
});
// After
import {
getUserScopedGraphQLClient,
getOrganizationScopedGraphQLClient,
} from "@krakentech/blueprint-auth/server";
import { authConfig } from "@/lib/auth/config";
const userClient = getUserScopedGraphQLClient(authConfig, {
context: { req, res },
});
const orgClient = getOrganizationScopedGraphQLClient(authConfig, {
context: { req, res },
});
Server Functions​
Server functions now use context parameter
Server-side functions now accept a context parameter as part of their options object instead of
separate req and res parameters.
// Before
import { getServerSideGraphQLClients } from "@krakentech/blueprint-auth/server";
const { userScopedClient, organizationScopedClient } = await getServerSideGraphQLClients({
krakenConfig: {/* ... */},
req,
res,
});
// After
import {
getUserScopedGraphQLClient,
getOrganizationScopedGraphQLClient,
} from "@krakentech/blueprint-auth/server";
import { authConfig } from "@/lib/auth/config";
const userClient = getUserScopedGraphQLClient(authConfig, {
context: { req, res },
});
const orgClient = getOrganizationScopedGraphQLClient(authConfig, {
context: { req, res },
});
This pattern applies to all server-side utilities including getSession,
getUserScopedGraphQLClient, getOrganizationScopedGraphQLClient, and others.
Client-Side Auth​
Client auth uses unified config
The createClientSideAuth function signature changed from one parameter to two. The defaultTarget
option moved to the second parameter along with the new router option. See the
createClientSideAuth API reference
for details.
// Before
import { createClientSideAuth } from "@krakentech/blueprint-auth/client";
export const { AuthProvider, useLogin, useLogout, useSession } = createClientSideAuth({
apiRoutes: {
graphql: { kraken: "/api/graphql" },
login: "/api/auth/login",
},
defaultTarget: "kraken",
});
// After
import { createClientSideAuth } from "@krakentech/blueprint-auth/client";
import { authConfig } from "@/lib/auth/config";
export const { AuthProvider, useLogin, useLogout, useSession } = createClientSideAuth(authConfig, {
defaultTarget: "kraken",
router: "pages-router",
});
AuthProvider API changes
The AuthProvider component API has been simplified and improved:
- The
appRoutesprop has been removed. Route configuration now lives in your centralizedauthConfig. - Handler callbacks are now passed as individual props directly to
AuthProviderinstead of being nested in ahandlersobject. - All handlers now receive a
defaultRedirectcallback function, giving you control over whether to use default redirect behavior. - The
onLoginErrorhandler receives a normalizedErrorCodestring instead of aKrakenError[]array. - The
onLogouthandler splits into separateonLogoutSuccessandonLogoutErrorhandlers for granular error handling. - All handlers can now be async.
See the AuthProvider API reference for
details.
// Before
<AuthProvider
appRoutes={{
dashboard: "/dashboard",
login: "/login",
}}
onLoginSuccess={() => {
// Handle login success
}}
onLoginError={(errorCode) => {
// Handle login error
}}
onLogoutSuccess={() => {
// Handle logout success
}}
onLogoutError={(errorCode) => {
// Handle logout error
}}
>
// After
<AuthProvider
onLoginSuccess={(defaultRedirect) => {
// Handle login success
defaultRedirect(); // Optional: use default redirect behavior
}}
onLoginError={(errorCode, defaultRedirect) => {
// Handle login error with normalized error code
defaultRedirect(); // Optional: use default redirect behavior
}}
onLogoutSuccess={(defaultRedirect) => {
// Handle logout success
defaultRedirect(); // Optional: use default redirect behavior
}}
onLogoutError={(errorCode, defaultRedirect) => {
// Handle logout error with normalized error code
defaultRedirect(); // Optional: use default redirect behavior
}}
>
useLogin accepts nextPage option
The shouldRedirectToNextPage boolean flag is replaced by nextPage for more granular redirect
control. Set it to null to prevent redirect, or provide a path to redirect to a specific page. The
server now returns a redirectUrl in the response. See the
useLogin API reference for details.
// Before
const login = useLogin({
shouldRedirectToNextPage: false,
});
// After
const login = useLogin({
nextPage: null,
});
useLogout accepts options
The hook now accepts an options object to control redirect behavior. Use nextPage to specify the
redirect destination or set it to null to prevent redirect. See the
useLogout API reference for details.
// Before
const logout = useLogout();
// After
const logout = useLogout({
nextPage: "/goodbye",
});
useKrakenAuthErrorHandler uses options object
The hook now accepts an options object instead of a single callback function. The onError callback
receives a defaultRedirect function as its second parameter, and you can specify redirectType
("push" or "replace"). The getAuthErrorCode function is no longer returned from the hook; import
getErrorCode from @krakentech/blueprint-auth/utils instead. See the
useKrakenAuthErrorHandler API reference
for details.
// Before
const { getAuthErrorCode, handleKrakenErrors } = useKrakenAuthErrorHandler((error) => {
// custom error handling
});
// After
import { getErrorCode } from "@krakentech/blueprint-auth/utils";
const { handleKrakenErrors } = useKrakenAuthErrorHandler({
onError: (error, defaultRedirect) => {
// custom error handling
},
redirectType: "replace",
});
const errorCode = getErrorCode(error);
useSession no longer refetches on window focus or reconnect
The useSession hook no longer automatically refetches session data when the user refocuses the
window or reconnects to the network. This prevents UI tearing when sessions expire.
Previous behavior: The hook would refetch on refetchOnWindowFocus and refetchOnReconnect
events. If the session had expired during one of these refetches, the hook would return an
unauthenticated state while the user remained on a protected page (e.g., dashboard). This caused UI
tearing where conditionally rendered authenticated UI elements (user menus, dashboard navigation,
etc.) would suddenly disappear while the user was still viewing the page.
New behavior: The session state remains stable during the user's current page view. Users are
redirected to the login page during their next page navigation or GraphQL request (when
useKrakenAuthErrorHandler
is used), ensuring a consistent UI experience without jarring visual changes.
See the useSession API reference for
details.
Import & Export Changes​
Cookie constants renamed
// TOKEN_COOKIE_KEYS with shortened property names
import { TOKEN_COOKIE_KEYS } from "@krakentech/blueprint-auth";
TOKEN_COOKIE_KEYS.access; // actually "accessToken"
TOKEN_COOKIE_KEYS.refresh; // actually "refreshToken"
// AUTH_COOKIE with full property names
import { AUTH_COOKIE } from "@krakentech/blueprint-auth";
AUTH_COOKIE.accessToken; // "accessToken"
AUTH_COOKIE.refreshToken; // "refreshToken"
Import and export names updated
The API has been redesigned for consistency. Handlers use the create* factory pattern, and
utilities have clearer names.
// Utility functions renamed:
createKrakenOAuthURI → generateKrakenOAuthURI
getSessionData → getSession
// Constants renamed:
TOKEN_COOKIE_KEYS → AUTH_COOKIE
// New error handling exports:
AuthError // structured error class with helpful messages
BlueprintAuthErrorCode // error code enum
getErrorCode // extract error codes from errors
isBlueprintAuthErrorCode // type guard for error codes
See the migration steps above for detailed handler and middleware changes.
getErrorCode now standalone export
The getErrorCode function is no longer returned from useKrakenAuthErrorHandler. Import it from
@krakentech/blueprint-auth/utils. See the
getErrorCode API reference for details.
// Before
const { getAuthErrorCode, handleKrakenErrors } = useKrakenAuthErrorHandler(/* ... */);
const errorCode = getAuthErrorCode(error);
// After
import { getErrorCode } from "@krakentech/blueprint-auth/utils";
const { handleKrakenErrors } = useKrakenAuthErrorHandler(/* ... */);
const errorCode = getErrorCode(error);
isBlueprintAuthErrorCode type guard
The isBlueprintAuthErrorCode function is a TypeScript type guard that checks if an error code is a
valid Blueprint auth error code.
Usage:
import { BlueprintAuthErrorCode } from "@krakentech/blueprint-auth";
import { getErrorCode, isBlueprintAuthErrorCode } from "@krakentech/blueprint-auth/utils";
const errorCode: string | null = getErrorCode(error);
// In v40, the predicate accepts string, so check for null first.
if (errorCode !== null && isBlueprintAuthErrorCode(errorCode)) {
// errorCode is now typed as BlueprintAuthErrorCode (not string | null)
// You get autocomplete for valid Blueprint auth error codes
switch (errorCode) {
case BlueprintAuthErrorCode.TokenAccessInvalid:
// handle specific error
break;
// ...
}
}
This is useful when working with error codes from external sources or when you need strict type checking.
New SessionState
New field:
authMethod: Identifies which authentication method is currently active ("email","oauth","scoped","masquerade","mobile-web-view", ornull)
Removed fields:
isMasqueradingisMobileWebViewisScopedSession
const { data: session } = useSession();
if (session.isAuthenticated) {
return <LogoutButton />
} else {
return <LoginButton />
}
if (session.authMethod === "masquerade") {
return <MasqueradeBanner />
}
if (session.authMethod === "scoped") {
return <PaymentDetailsCard />
} else {
return <PaymentDetailsForm />
}
if (session.authMethod === "mobile-web-view") {
return <UpdateAppLink />
} else {
return <DownloadAppLink />
}
See the SessionState type reference and
the AuthMethod type reference for full
documentation.
New Features (Non-Breaking)​
New Utility: getActiveAuthMethod​
A new utility function getActiveAuthMethod is available to determine the active authentication
method based on cookie priority without loading the full session state:
import { createAuthCookieUtils } from "@krakentech/blueprint-auth/server";
import { authConfig } from "@/lib/auth/config";
const { getActiveAuthMethod, getAuthCookies } = createAuthCookieUtils(authConfig, {
context,
});
const cookies = await getAuthCookies();
const authMethod = getActiveAuthMethod(cookies);
See the
createAuthCookieUtils API reference
for details on the available utility functions including getActiveAuthMethod.