
Product
Socket for Jira Is Now Available
Socket for Jira lets teams turn alerts into Jira tickets with manual creation, automated ticketing rules, and two-way sync.
@teamhanko/hanko-frontend-sdk
Advanced tools
A package for simplifying UI integration with the Hanko API. It is meant for use in browsers only.
This package utilizes the Hanko API to provide functionality that allows an easier UI integration. It is meant for use in browsers only.
# npm
npm install @teamhanko/hanko-frontend-sdk
# yarn
yarn add @teamhanko/hanko-frontend-sdk
# pnpm
pnpm install @teamhanko/hanko-frontend-sdk
Import as a module:
import { Hanko } from "@teamhanko/hanko-frontend-sdk"
const hanko = new Hanko("http://localhost:3000")
With a script tag via CDN:
<script src="https://cdn.jsdelivr.net/npm/@teamhanko/hanko-frontend-sdk/dist/sdk.umd.js"></script>
<script>
const hanko = new hankoFrontendSdk.Hanko("http://localhost:3000")
...
</script>
You can pass certain options, when creating a new Hanko instance:
const defaultOptions = {
timeout: 13000, // The timeout (in ms) for the HTTP requests.
cookieName: "hanko", // The cookie name under which the session token is set.
localStorageKey: "hanko", // The prefix / name of the localStorage keys.
sessionCheckInterval: 30000, // Interval (in ms) for session validity checks. Must be greater than 3000 (3s).
sessionCheckChannelName: "hanko-session-check" // The broadcast channel name for inter-tab communication
};
const hanko = new Hanko("http://localhost:3000", defaultOptions);
You can bind callback functions to different custom events. The callback function will be called when the event happens and an object will be passed in, containing event details. The event binding works as follows:
// Controls the optional `once` parameter. When set to `true` the callback function will be called only once.
const once = false;
const removeEventListener = hanko.onSessionCreated((eventDetail) => {
// Your code...
}, once);
The following events are available:
hanko.onSessionCreated((sessionDetail) => {
// A new JWT has been issued.
console.info("Session created", sessionDetail.claims);
})
hanko.onSessionExpired(() => {
// You can redirect the user to a login page or show the `<hanko-auth>` element, or to prompt the user to log in again.
console.info("Session expired");
})
hanko.onUserLoggedOut(() => {
// You can redirect the user to a login page or show the `<hanko-auth>` element.
console.info("User logged out");
})
hanko.onUserDeleted(() => {
// You can redirect the user to a login page or show the `<hanko-auth>` element.
console.info("User has been deleted");
})
Please Take a look into the docs for more details.
The SDK provides methods to manage user sessions and retrieve user information.
Fetches the current user's profile information.
User object containing the user’s profile details. The object includes:
user_id: A unique string identifier for the user.passkeys: An optional array of WebAuthn credentials (passkey-based authentication).security_keys: An optional array of WebAuthn credentials (security key-based authentication).mfa_config: An optional configuration object for multi-factor authentication settings.emails: An optional array of email objects (e.g., { address: string, is_primary: boolean, is_verified: boolean }).username: An optional username object (e.g., { id: string, username: string }).created_at: A string timestamp (ISO 8601) of when the user was created.updated_at: A string timestamp (ISO 8601) of when the user was last updated.UnauthorizedError (invalid or expired session), TechnicalError (server or network issues).try {
const user = await hanko.getUser();
console.log("User profile:", user);
// Example output:
// {
// user_id: "123e4567-e89b-12d3-a456-426614174000",
// emails: [{ address: "user@example.com", is_primary: true, is_verified: true }],
// username: { id: "f2882293-3c39-451d-a7cb-4cf3375e0c66", username: "johndoe" },
// created_at: "2025-01-01T10:00:00Z",
// updated_at: "2025-04-01T12:00:00Z"
// }
} catch (error) {
console.error("Failed to fetch user profile:", error);
// Handle UnauthorizedError or TechnicalError
}
Checks the validity of the current session.
is_valid: A boolean indicating whether the session is valid.claims: An optional object with session details, including:
subject: The user ID or session identifier.session_id: The unique session identifier.expiration: A string timestamp (ISO 8601) when the session expires.email: An optional object with email details (e.g., { address: string, is_primary: boolean, is_verified: boolean }).username: An optional string with the user’s username.issued_at, audience, issuer: Optional metadata about the session token.try {
const sessionStatus = await hanko.validateSession();
console.log("Session status:", sessionStatus);
// Example output:
// {
// is_valid: true,
// claims: {
// subject: "123e4567-e89b-12d3-a456-426614174000",
// session_id: "789abc",
// expiration: "2025-04-25T12:00:00Z",
// email: { address: "user@example.com", is_primary: true, is_verified: true },
// custom_field: "value"
// }
// }
} catch (error) {
console.error("Failed to validate session:", error);
// Handle TechnicalError
}
Retrieves the current session token from the authentication cookie.
null if no session exists.null to handle missing sessions.const token = hanko.getSessionToken();
console.log("Session token:", token);
// Example output: "eyJhbGciOiJIUzI1NiIs..."
Logs out the current user by invalidating the session.
TechnicalError (server or network issues).try {
await hanko.logout();
console.log("User logged out");
} catch (error) {
console.error("Failed to fetch user logout:", error);
// Handle TechnicalError
}
If you use the main Hanko client provided by the Frontend SDK, you can use the lang parameter in the options when
instantiating the client to configure the language that is used to convey to the Hanko API the
language to use for outgoing emails. If you have disabled email delivery through Hanko and configured a webhook for the
email.send event, the value for the lang parameter is reflected in the JWT payload of the token contained in the
webhook request in the "Language" claim.
The Hanko backend allows you to define custom claims that are added to issued session JWTs (see here for more info).
To allow for IDE autocompletion and to maintain type safety for your custom claims:
*.d.ts) in your project (alternatively, modify an existing one).Claims type from the frontend SDK.Claims type.import type { Claims } from "@teamhanko/hanko-frontend-sdk" // 2.
// import type { Claims } from "@teamhanko/elements" // alternatively, if you use Hanko Elements, which
// re-exports most SDK types
type CustomClaims = Claims<{ // 3.
custom_claim?: string // 4.
}>;
import type { CustomClaims } from "..."; // path to your type declaration file
hanko.onSessionCreated((sessionDetail) => {
const claims = sessionDetail.claims as CustomClaims;
console.info("My custom claim:", claims.custom_claim);
});
import type { CustomClaims } from "..."; // path to your type declaration file
async function session() {
const session = await hanko.validateSession();
const claims = session.claims as CustomClaims;
console.info("My custom claim:", claims.custom_claim);
};
The SDK offers a TypeScript-based interface for managing authentication and profile flows with Hanko, enabling the development of custom frontends with the Hanko FlowAPI. It handles state transitions, action execution, input validation, and event dispatching, while also providing built-in support for auto-stepping and passkey autofill. This guide explores its core functionality and usage patterns.
Start a new authentication or profile flow using the createState method on a Hanko instance. Options allow you to control
event dispatching and auto-step behavior.
const state = await hanko.createState("login", {
dispatchAfterStateChangeEvent: true,
excludeAutoSteps: [],
loadFromCache: true,
cacheKey: "hanko-flow-state",
});
boolean - Whether to dispatch the onAfterStateChange event after state changes (default: true).AutoStepExclusion - Array of state names or "all" to skip specific or all auto-steps (default: null).boolean - Whether to attempt loading a cached state from localStorage (default: true).string - The key used for localStorage caching (default: "hanko-flow-state").The state object represents the current step in the flow. It contains properties and methods to interact with the flow.
StateName - The current state’s name (e.g., "login_init", "login_password", "success").FlowName - The name of the flow (e.g., "login").Error | undefined - An error object if an action or request fails (e.g., invalid input, network error).Payloads[StateName] | undefined - State-specific data returned by the API.ActionMap<StateName> - An object mapping action names to Action instances.string - CSRF token for secure requests.number - HTTP status code of the last response.ActionInfo | undefined - Details of the last action run on this state, if any.ActionInfo | undefined - Details of the action that led to this state, if any.boolean - Whether the state was loaded from localStorage.string - The key used for localStorage caching.AutoStepExclusion - An array of StateNames excluded from auto-stepping.Actions can be enabled or disabled based on the backend configuration or the user's state and properties. You can check
whether a specific action is enabled by accessing its enabled property:
if (state.actions.example_action.enabled) {
await state.actions.example_action.run();
} else {
console.log("Action is disabled");
}
Each action in state.actions has an inputs property defining expected input fields.
console.log(state.actions.continue_with_login_identifier.inputs);
// Example output:
// {
// username: {
// required: true,
// type: "string",
// minLength: 3,
// maxLength: 20,
// description: "User’s login name"
// }
// }
Actions transition the flow to a new state. Use the run method on an action, passing input values and optional configuration.
if (state.name === "login_init") {
const newState = await state.actions.continue_with_login_identifier.run({
username: "user1",
});
// Triggers `onBeforeStateChange` and `onAfterStateChange` events
// `newState` is the next state in the flow (e.g., "login_password")
}
state.name to ensure the action exists and inputs are valid for that state.run triggers onBeforeStateChange before the action and onAfterStateChange after the new state is loaded.newState.error will be set to "invalid_form_data", and specific errors will be attached to the related input fields (see "Error Handling" below).The SDK dispatches events via the Hanko instance to track state changes.
onBeforeStateChangeFires before an action is executed, useful for showing loading states.
hanko.onBeforeStateChange(({ state }) => {
console.log("Action loading:", state.invokedAction);
});
onAfterStateChangeFires after a new state is loaded, ideal for rendering UI or handling state-specific logic.
hanko.onAfterStateChange(({ state }) => {
console.log("Action load finished:", state.invokedAction);
switch (state.name) {
case "login_init":
state.passkeyAutofillActivation(); // Special handler for passkey autofill; requires an <input> field on the page with `autocomplete="username webauthn"` (e.g., <input type="text" name="username" autocomplete="username webauthn" />) so the browser can suggest and autofill passkeys when the user interacts with it.
break;
case "login_password":
// Render password input UI
if (state.error) {
console.log("Error:", state.error); // e.g., "invalid_form_data"
}
break;
case "error":
// Handle network errors or 5xx responses
console.error("Flow error:", state.error);
break;
}
});
You can disable the automatic onAfterStateChange event and dispatch it manually after custom logic.
if (state.name === "login_init") {
const newState = await state.actions.continue_with_login_identifier.run(
{ username: "user1" },
{ dispatchAfterStateChangeEvent: false }, // Disable automatic dispatch
);
// Only `onBeforeStateChange` is triggered here
await doSomething(); // Your custom async logic
newState.dispatchAfterStateChangeEvent(); // Manually trigger the event
}
Auto-steps automatically advance the flow for certain states, reducing manual intervention.
preflightlogin_passkeyonboarding_verify_passkey_attestationwebauthn_credential_verificationthirdpartysuccessaccount_deletedPrevent auto-steps by specifying states in excludeAutoSteps:
const state = await hanko.createState("login", {
excludeAutoSteps: ["success"], // Skip auto-step for "success"
});
hanko.onAfterStateChange(({ state }) => {
if (state.name === "success") {
console.log("Flow completed");
await state.autoStep();
}
});
If an action fails due to invalid inputs:
if (state.name === "login_password" && state.error === "invalid_form_data") {
const passwordError = state.actions.password_login.inputs.password.error;
console.log("Password error:", passwordError);
}
For network issues or 5xx responses, the error state is entered with details in state.error.
if (state.name === "error") {
console.error("Flow error:", state.error);
}
Persist and recover flow state using localStorage.
Save the current flow state to localStorage using saveToLocalStorage().
state.saveToLocalStorage(); // Stores under `state.cacheKey` (default: "hanko-flow-state")
Please note that the localStorage entry will be removed automatically when an action is invoked on the saved state.
Recover a cached state or start a new flow:
const state = await hanko.createState("login", {
loadFromCache: true, // Attempts to load from `cacheKey`
cacheKey: "hanko-flow-state",
});
Remove the cached state:
state.removeFromLocalStorage(); // Deletes from `state.cacheKey`
For custom persistence:
import { State } from "@teamhanko/hanko-frontend-sdk";
const serialized = state.serialize(); // Returns a `SerializedState` object
// Store `serialized` in your storage system
// Later, deserialize it
const recoveredState = await State.deserialize(hanko, serialized, {
cacheKey: "custom-key",
});
This allows integration with other storage mechanisms.
Found a bug? Please report on our GitHub page.
To see the latest documentation, please click here.
The hanko-frontend-sdk project is licensed under the MIT License.
FAQs
A package for simplifying UI integration with the Hanko API. It is meant for use in browsers only.
We found that @teamhanko/hanko-frontend-sdk demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 3 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Product
Socket for Jira lets teams turn alerts into Jira tickets with manual creation, automated ticketing rules, and two-way sync.

Company News
Socket won two 2026 Reppy Awards from RepVue, ranking in the top 5% of all sales orgs. AE Alexandra Lister shares what it's like to grow a sales career here.

Security News
NIST will stop enriching most CVEs under a new risk-based model, narrowing the NVD's scope as vulnerability submissions continue to surge.