Email & password
Use this flow when your Kraken environment still supports signing in with an email address and password. For new integrations, prefer Kraken OAuth: Kraken's authorization server handles the login screen instead of your application collecting passwords.
Email & Password authentication through the ObtainKrakenToken mutation is
deprecated and will be removed in the near future. We'll be replacing it with
the Kraken Auth Server soon.
Blueprint Auth sends the credentials to Kraken, verifies the returned access token, and writes the authentication cookies. Your application supplies the form and decides how to present failures; it should not manage tokens itself.
Before you begin​
Complete the setup guide for your router:
- App Router setup: export
loginfrom your server-side auth module. The example below uses Next.js 15+ and React 19. - Pages Router setup: export
useLoginfrom your client-side auth module and installAuthProviderinsideQueryClientProvider.
Both paths require the Kraken endpoints, client IP secret, and exact trusted access-token issuers described in those guides. Keep credentials and server-only configuration out of client modules.
| Router | Form submission | Server entry point |
|---|---|---|
| App Router | useActionState submits a Server Action | login from createAppRouterAuth |
| Pages Router | useLogin sends a JSON POST to apiRoutes.login | createLoginHandler |
The Server Action approach does not need a login API route.
Implementation​
- App Router
- Pages Router
Create the login action​
Validate the submitted fields on the server before calling login. Pass the
page's search parameters so Blueprint Auth can honor a nextPage return path.
"use server";
import type { AppRouterLoginParams } from "@krakentech/blueprint-auth/server";
import { unstable_rethrow } from "next/navigation";
import { login } from "@/lib/auth/server";
export type LoginState = { error?: string };
export async function loginAction(
searchParams: AppRouterLoginParams.Redirect["searchParams"],
_previousState: LoginState,
formData: FormData,
): Promise<LoginState> {
const email = formData.get("email");
const password = formData.get("password");
if (
typeof email !== "string" || !email.trim() ||
typeof password !== "string" || !password
) {
return { error: "Enter your email address and password." };
}
try {
return await login({
input: { email: email.trim(), password },
searchParams,
});
} catch (error) {
unstable_rethrow(error);
return { error: "Unable to sign in. Check your details and try again." };
}
}
On success, login normally redirects by throwing Next.js's internal redirect
error. Call unstable_rethrow before handling application errors; otherwise a
successful login can be mistaken for a failure. Do not return credentials or raw
error objects in the action state.
Connect the form​
useActionState supplies the action result and pending state. Disable submission
while pending and display a generic error without exposing Kraken's response.
"use client";
import { useActionState } from "react";
import type { LoginState } from "./actions";
type LoginFormProps = {
action: (state: LoginState, formData: FormData) => Promise<LoginState>;
};
export function LoginForm({ action }: LoginFormProps) {
const [state, formAction, pending] = useActionState(action, {});
return (
<form action={formAction}>
<label htmlFor="email">Email</label>
<input id="email" name="email" type="email" autoComplete="username" required />
<label htmlFor="password">Password</label>
<input
id="password"
name="password"
type="password"
autoComplete="current-password"
required
/>
{state.error && <p role="alert">{state.error}</p>}
<button type="submit" disabled={pending}>
{pending ? "Signing in..." : "Sign in"}
</button>
</form>
);
}
Resolve the page's asynchronous searchParams and bind them to the action:
import { loginAction } from "./actions";
import { LoginForm } from "./LoginForm";
type LoginPageProps = {
searchParams: Promise<Record<string, string | string[] | undefined>>;
};
export default async function LoginPage({ searchParams }: LoginPageProps) {
const action = loginAction.bind(null, await searchParams);
return <LoginForm action={action} />;
}
Login API handler​
Create the handler at the path configured by apiRoutes.login, typically
/api/auth/login:
import { createLoginHandler } from "@krakentech/blueprint-auth/server";
import { authConfig } from "@/lib/auth/config";
export default createLoginHandler(authConfig);
Set ALLOWED_REQUEST_ORIGINS or validation.allowedRequestOrigins before
creating the handler. See Security requirements.
Create the login page​
useLogin sends credentials to the configured handler. It invalidates the client
session query after a successful login and navigates to the selected destination.
On failure, its default handler adds an error code to the current page's URL.
The form below handles both mutation errors and that URL state.
import { useRouter } from "next/router";
import { useLogin } from "@/lib/auth/client";
export default function LoginPage() {
const router = useRouter();
const login = useLogin();
const hasError = login.isError || typeof router.query.error === "string";
return (
<form
method="post"
onSubmit={(event) => {
event.preventDefault();
const formData = new FormData(event.currentTarget);
const email = formData.get("email");
const password = formData.get("password");
if (typeof email === "string" && typeof password === "string") {
login.mutate({ email: email.trim(), password });
}
}}
>
<label htmlFor="email">Email</label>
<input id="email" name="email" type="email" autoComplete="username" required />
<label htmlFor="password">Password</label>
<input
id="password"
name="password"
type="password"
autoComplete="current-password"
required
/>
{hasError && <p role="alert">Unable to sign in. Check your details and try again.</p>}
<button type="submit" disabled={login.isPending}>
{login.isPending ? "Signing in..." : "Sign in"}
</button>
</form>
);
}
This form requires JavaScript to submit through useLogin. Keep method="post"
so a submission before hydration does not put credentials in the URL. A native
HTML form submission is not a replacement for the hook: the API handler requires
a JSON request, not a form-encoded body.
To handle errors without the default URL change, supply onLoginError on your
existing AuthProvider. Omitting the callback's defaultRedirect() call keeps
the user on the form; login.isError still exposes the failure. See
AuthProvider.
Redirects and errors​
| Behavior | App Router | Pages Router |
|---|---|---|
| Default success destination | nextPage from the supplied search parameters, otherwise the configured dashboard | nextPage from the URL, otherwise the configured dashboard |
| Override the destination | Pass nextPage to login | Pass nextPage to useLogin |
| Stay on the page after success | Pass nextPage: null to login and return your own success state | Call useLogin({ nextPage: null }) and render a success state |
| Handle failure | Catch application errors in the action, preserving Next.js control-flow errors | Read the mutation error and URL error code, or override AuthProvider.onLoginError |
Blueprint Auth validates redirect destinations against the request origin. Use its redirect handling rather than redirecting directly to an unchecked query parameter. An invalid destination does not result in an external redirect.
A failed attempt can mean invalid credentials, a rejected token, a service
failure, or a configuration error. Show a safe message to the user and use
auth logging to investigate; do not assume
every failure means the password is wrong. If Kraken requires CAPTCHA, both
login and useLogin accept captchaResponse alongside the credentials.
Security requirements​
- Use HTTPS in deployed environments. Never put credentials in URLs, logs, browser storage, or returned action state.
- Validate input on the server. Browser validation and disabled buttons are UI
aids, not security boundaries.
createLoginHandlervalidates its request body; a custom Server Action must validate its own input. - For the API-handler path, configure a non-empty list of trusted origins.
Requests must use POST with
Content-Type: application/jsonand include a trustedOriginorReferer. If both headers are present, both must be trusted.useLoginsupplies the JSON content type; the browser supplies source headers. Follow the trusted request origins setup. - The Server Action example calls
logindirectly, notcreateLoginHandler. Next.js handles the Server Action transport and its origin checks; the API handler'sallowedRequestOriginssetting does not configure those checks. - Login establishes a session; it does not protect every subsequent operation.
At a protected page, handler, or Server Action, call
getAuth.userand deny access if it returnsnull. Only then creategetGraphQLClient.user({ auth }), and enforce resource-specific authorization on the server.
Related guides​
- Session management: session state, verification, and refresh behavior.
- Logout: ending a session.
- API reference: configuration and
complete
login,useLogin, and handler options.