Skip to content

Authentication

@granit/authentication defines framework-agnostic Keycloak/OIDC types — user claims, login/logout options, lifecycle events, and a base auth context interface. @granit/react-authentication provides the useKeycloakInit hook that handles PKCE initialization, silent SSO, automatic token refresh, and back-channel logout. It also exports createAuthContext and createMockProvider factories so each consuming app can define its own typed auth context.

@granit/authentication-api-keys and @granit/react-authentication-api-keys add API key management — types and React Query hooks for creating, revoking, rotating, and scoping keys.

Peer dependencies: keycloak-js, react ^19, axios, @tanstack/react-query ^5

  • Directory@granit/authentication/ Keycloak/OIDC types (framework-agnostic)
    • @granit/react-authentication Keycloak init hook, auth context factory, mock provider
  • Directory@granit/authentication-api-keys/ API key management types
    • @granit/react-authentication-api-keys React Query hooks for API key CRUD
PackageRoleDepends on
@granit/authenticationKeycloak types, login/logout options, lifecycle eventskeycloak-js
@granit/react-authenticationuseKeycloakInit, createAuthContext, createMockProvider@granit/authentication, @granit/api-client, react, keycloak-js
@granit/authentication-api-keysAPI key DTOs (ApiKeyResponse, ApiKeyCreateRequest, etc.)
@granit/react-authentication-api-keysuseApiKeys, useCreateApiKey, useRevokeApiKey, useRotateApiKey@granit/authentication-api-keys, @tanstack/react-query, axios
graph TD
    authn["@granit/authentication"]
    reactAuthn["@granit/react-authentication"]
    apiKeys["@granit/authentication-api-keys"]
    reactApiKeys["@granit/react-authentication-api-keys"]
    apiClient["@granit/api-client"]
    keycloak["keycloak-js"]

    reactAuthn --> authn
    reactAuthn --> apiClient
    reactAuthn --> keycloak
    authn --> keycloak
    reactApiKeys --> apiKeys

    style keycloak fill:#e8f5e9,stroke:#43a047,color:#1b5e20
src/auth/auth-context.ts
import type { BaseAuthContextType, KeycloakUserInfo } from '@granit/authentication';
import { createAuthContext } from '@granit/react-authentication';
interface AuthContextType extends BaseAuthContextType {
register: (options?: { redirectUri?: string }) => void;
}
export const { AuthContext, useAuth } = createAuthContext<AuthContextType>();
src/auth/AuthProvider.tsx
import { useKeycloakInit } from '@granit/react-authentication';
import { AuthContext } from './auth-context';
export function AuthProvider({ children }: { children: React.ReactNode }) {
const auth = useKeycloakInit({
url: import.meta.env.VITE_KEYCLOAK_URL,
realm: import.meta.env.VITE_KEYCLOAK_REALM,
clientId: import.meta.env.VITE_KEYCLOAK_CLIENT_ID,
});
return <AuthContext.Provider value={auth}>{children}</AuthContext.Provider>;
}

Standard OIDC user claims from the Keycloak /userinfo endpoint.

interface KeycloakUserInfo {
sub: string;
email?: string;
email_verified?: boolean;
name?: string;
preferred_username?: string;
given_name?: string;
family_name?: string;
picture?: string;
}

Keycloak lifecycle event names.

type KeycloakEvent =
| 'onReady'
| 'onAuthSuccess'
| 'onAuthError'
| 'onAuthRefreshSuccess'
| 'onAuthRefreshError'
| 'onAuthLogout'
| 'onTokenExpired';
interface LoginOptions {
redirectUri?: string;
idpHint?: string; // bypass Keycloak login page (e.g. "google")
loginHint?: string; // pre-fill username
locale?: string; // force login UI locale
action?: string; // "register" or a required action name
prompt?: 'login' | 'consent' | 'none';
scope?: string; // additional OAuth scopes
maxAge?: number; // max seconds since last authentication
}
interface LogoutOptions {
redirectUri?: string;
}

Shared auth context interface. Each consuming app extends it with application-specific fields.

interface BaseAuthContextType {
keycloak: Keycloak | null;
authenticated: boolean;
loading: boolean;
user: KeycloakUserInfo | null;
login: () => void;
logout: () => void;
}

Configuration object for useKeycloakInit.

interface KeycloakCoreConfig {
url: string;
realm: string;
clientId: string;
silentCheckSso?: boolean; // default: true
silentCheckSsoFallback?: boolean; // default: true (Safari workaround)
useTokenClaims?: boolean; // default: false (use /userinfo endpoint)
onTokenExpired?: () => void;
onAuthRefreshError?: () => void;
onAuthLogout?: () => void;
onEvent?: (event: KeycloakEvent) => void;
}
type ApiKeyType = 'Secret' | 'Publishable' | 'Webhook' | 'Ephemeral';
type CacheBehavior = 'Normal' | 'NoCache';
interface ApiKeyResponse {
readonly id: string;
readonly name: string;
readonly type: ApiKeyType;
readonly environment: string;
readonly prefix: string;
readonly lastFourChars: string;
readonly permissions: readonly string[];
readonly allowedCidrs: readonly string[];
readonly expiresAt: string | null;
readonly lastUsedAt: string | null;
readonly revokedAt: string | null;
readonly cacheBehavior: CacheBehavior;
readonly createdAt: string;
}
interface ApiKeyCreateRequest {
readonly name: string;
readonly type: ApiKeyType;
readonly environment: string;
readonly permissions?: readonly string[];
readonly allowedCidrs?: readonly string[];
readonly expiresAt?: string;
readonly cacheBehavior?: CacheBehavior;
}
interface ApiKeyCreateResponse {
readonly id: string;
readonly rawSecret: string; // only returned once — must be stored by client
readonly prefix: string;
readonly lastFourChars: string;
readonly name: string;
readonly type: ApiKeyType;
readonly environment: string;
readonly expiresAt: string | null;
}
interface ApiKeyRotateResponse {
readonly newKeyId: string;
readonly rawSecret: string; // only returned once
readonly prefix: string;
readonly lastFourChars: string;
readonly oldKeyId: string;
}
interface ApiKeyUpdateScopesRequest {
readonly permissions: readonly string[];
readonly allowedCidrs: readonly string[];
}

Initializes Keycloak with PKCE (S256), performs check-sso, and manages the full session lifecycle.

function useKeycloakInit(config: KeycloakCoreConfig): KeycloakCoreResult;

Returned fields (extends BaseAuthContextType):

FieldTypeDescription
keycloakRefRefObject<Keycloak | null>Direct Keycloak instance reference
login(options?)(LoginOptions?) => voidRedirect to Keycloak login
logout(options?)(LogoutOptions?) => voidRedirect to Keycloak logout
register(options?)(Omit<LoginOptions, 'action'>?) => voidShortcut for login({ action: 'register' })
hasRealmRole(role)(string) => booleanCheck realm-level role
hasResourceRole(role, resource?)(string, string?) => booleanCheck resource-level role
isTokenExpired(minValidity?)(number?) => booleanCheck if token expires within n seconds
tokenParsedRecord<string, unknown> | undefinedDecoded JWT payload

Automatic side effects:

  • Registers Bearer token getter via setTokenGetter() on @granit/api-client
  • Registers 401 handler via setOnUnauthorized() — triggers keycloak.logout()
  • Refreshes token every 60 seconds via setInterval
  • Loads user info from /userinfo endpoint (or from tokenParsed when useTokenClaims: true)
EventEffect
Token expiredAuto-refresh handles it (60s interval)
Refresh fails (onAuthRefreshError)authenticated → false, forces logout
Session revoked (onAuthLogout)authenticated → false, user → null
HTTP 401 receivedonUnauthorized callback triggers keycloak.logout()

Back-channel logout (admin revokes session in Keycloak) is detected via the dual mechanism: the 60-second token refresh fails, or the next API call returns 401.

Factory that creates a typed React context and useAuth hook. Each app calls this once with its own extended context type.

function createAuthContext<T extends BaseAuthContextType>(): {
AuthContext: React.Context<T | undefined>;
useAuth: () => T;
};

useAuth() throws if called outside AuthContext.Provider.

Creates a mock auth provider for Storybook and unit tests.

function createMockProvider<T extends BaseAuthContextType>(
AuthContext: React.Context<T | undefined>,
value: T
): React.FC<{ children: React.ReactNode }>;
const MockAuthProvider = createMockProvider(AuthContext, {
keycloak: null,
authenticated: true,
loading: false,
user: { sub: 'mock-001', name: 'Test User', email: '[email protected]' },
login: () => {},
logout: () => {},
});

All hooks accept an ApiKeyHookOptions configuration:

interface ApiKeyHookOptions {
client: AxiosInstance;
basePath?: string; // default: '/api/v1/api-keys'
}
const apiKeyKeys = {
all: ['api-keys'] as const,
lists: () => [...apiKeyKeys.all, 'list'] as const,
list: (params) => [...apiKeyKeys.lists(), params] as const,
details: () => [...apiKeyKeys.all, 'detail'] as const,
detail: (id) => [...apiKeyKeys.details(), id] as const,
};

Fetches a paginated, filterable list of API keys.

interface UseApiKeysParams {
search?: string;
type?: string[];
environment?: string;
includeRevoked?: boolean;
page?: number;
pageSize?: number;
}
function useApiKeys(
params?: UseApiKeysParams,
options: ApiKeyHookOptions
): UseQueryResult<ApiKeyResponse[]>;

Fetches a single API key by ID. Query is enabled only when id is truthy.

function useApiKey(id: string, options: ApiKeyHookOptions): UseQueryResult<ApiKeyResponse>;

Creates a new API key. Returns ApiKeyCreateResponse with rawSecretonly available once.

function useCreateApiKey(
options: ApiKeyHookOptions
): UseMutationResult<ApiKeyCreateResponse, Error, ApiKeyCreateRequest>;

Revokes an API key. Invalidates list and detail caches on success.

function useRevokeApiKey(
options: ApiKeyHookOptions
): UseMutationResult<void, Error, string>;

Rotates an API key — creates a new secret and invalidates the old one.

function useRotateApiKey(
options: ApiKeyHookOptions
): UseMutationResult<ApiKeyRotateResponse, Error, string>;

Updates permissions and allowed CIDRs on an existing key.

interface UpdateApiKeyScopesVariables {
id: string;
request: ApiKeyUpdateScopesRequest;
}
function useUpdateApiKeyScopes(
options: ApiKeyHookOptions
): UseMutationResult<void, Error, UpdateApiKeyScopesVariables>;
CategoryKey exportsPackage
OIDC typesKeycloakUserInfo, KeycloakEvent, LoginOptions, LogoutOptions@granit/authentication
Auth contextBaseAuthContextType, KeycloakCoreConfig@granit/authentication
Keycloak hookuseKeycloakInit(), KeycloakCoreResult@granit/react-authentication
Context factorycreateAuthContext(), createMockProvider()@granit/react-authentication
API key typesApiKeyResponse, ApiKeyCreateRequest, ApiKeyCreateResponse, ApiKeyRotateResponse@granit/authentication-api-keys
API key hooksuseApiKeys(), useApiKey(), useCreateApiKey(), useRevokeApiKey(), useRotateApiKey()@granit/react-authentication-api-keys
Query keysapiKeyKeys@granit/react-authentication-api-keys