Zero-dependency browser SDK for Hiyve Identity. Built on native fetch and localStorage.
localStorage (or in-memory for SSR)mcp:data:read, mcp:search:execute, etc.)tfaRequired events for custom UI flowsAuthError with a typed code and optional detailsnpm install @hiyve/identity-client
<script src="path/to/hiyve-identity-client.umd.cjs"></script>
<script>
const auth = new HiyveIdentityClient.HiyveAuth({
apiKey: 'pk_live_your_key',
});
</script>
import { HiyveAuth } from '@hiyve/identity-client';
const auth = new HiyveAuth({
apiKey: 'pk_live_your_api_key',
});
// Register a new user
await auth.register({
email: 'user@example.com',
password: 'securePassword123',
name: 'Jane Doe',
});
// Log in (may trigger TFA on unknown devices)
const result = await auth.login({
email: 'user@example.com',
password: 'securePassword123',
});
if (result.tfaRequired) {
// Unknown device -- OTP code sent to user's email
console.log('Check your email for a verification code');
// After user enters the code:
const { user } = await auth.verifyTfa({ code: '123456' });
console.log('Logged in as:', user.email);
} else {
console.log('Logged in as:', result.user.email);
}
console.log('Authenticated:', auth.isAuthenticated()); // true
// Listen for auth state changes
const unsubscribe = auth.onAuthStateChange(({ authenticated, user }) => {
console.log('Auth state changed:', authenticated, user);
});
// Log out
await auth.logout();
// Clean up when done (e.g. component unmount)
unsubscribe();
auth.destroy();
Pass a config object to the HiyveAuth constructor:
const auth = new HiyveAuth({
// Required
apiKey: 'pk_live_your_api_key', // Your public API key
// Optional (defaults shown)
environment: 'production', // 'production' or 'development'
basePath: '/identity/auth', // API route prefix
tokenStorage: 'localStorage', // 'localStorage' or 'memory'
autoRefresh: true, // Auto-refresh tokens before expiry
refreshBuffer: 300, // Seconds before expiry to trigger refresh
timeout: 30000, // Request timeout in milliseconds
});
| Option | Type | Default | Description |
|---|---|---|---|
apiKey |
string |
required* | Public API key (pk_live_* or pk_test_*) |
baseUrl |
string |
— | Full base URL for proxy mode (e.g. '/api/hiyve/identity'). When set, apiKey is not required |
environment |
string |
'production' |
'production' or 'development' |
basePath |
string |
'/identity/auth' |
API route prefix (ignored when baseUrl is set) |
tokenStorage |
string |
'localStorage' |
'localStorage' for persistent sessions, 'memory' for ephemeral. Production recommendation: use 'memory' to protect refresh tokens from XSS — see Security & Compliance |
autoRefresh |
boolean |
true |
Automatically refresh tokens before they expire |
refreshBuffer |
number |
300 |
How many seconds before expiry to trigger auto-refresh |
timeout |
number |
30000 |
HTTP request timeout in milliseconds |
*apiKey is required unless baseUrl is provided for proxy mode.
Create a new user account. Depending on the organization's emailVerificationRequired setting, the server may send a verification email or mark the user as verified immediately.
try {
const { user } = await auth.register({
email: 'user@example.com',
password: 'securePassword123',
name: 'Jane Doe', // optional
metadata: { role: 'viewer' }, // optional -- custom data
});
console.log('Registered:', user.id);
if (user.emailVerified) {
// Email verification not required -- user can log in immediately
console.log('Ready to log in');
} else {
// Email verification required -- tell user to check their inbox
console.log('Check your email for a verification link');
}
} catch (err) {
if (err.code === 'USER_ALREADY_EXISTS') {
console.log('Email already registered');
}
}
Note: Registration does not automatically log the user in. Call auth.login() after registration.
Authenticate with email and password. On success, tokens are stored automatically and auto-refresh is started.
try {
const { user, accessToken, refreshToken, expiresIn } = await auth.login({
email: 'user@example.com',
password: 'securePassword123',
});
console.log('Welcome,', user.name);
console.log('Token expires in', expiresIn, 'seconds');
} catch (err) {
if (err.code === 'INVALID_CREDENTIALS') {
console.log('Wrong email or password');
} else if (err.code === 'EMAIL_NOT_VERIFIED') {
console.log('Please verify your email first');
}
}
When a user logs in from an unrecognized device, the server returns a TFA challenge instead of tokens. The SDK stores the challenge internally and emits a tfaRequired event.
const result = await auth.login({
email: 'user@example.com',
password: 'securePassword123',
});
if (result.tfaRequired) {
// The response includes:
// - challengeToken: stored internally by the SDK
// - methods: ['email_otp'] -- available verification methods
// - expiresIn: seconds until the challenge expires (default: 600)
// - user: { id, email, name } -- the user being authenticated
console.log('TFA required, methods:', result.methods);
console.log('Code expires in', result.expiresIn, 'seconds');
// Prompt the user for the OTP code from their email, then:
const { user, accessToken, deviceId } = await auth.verifyTfa({ code: '123456' });
console.log('Logged in as:', user.email);
// The device is now trusted -- future logins from this device skip TFA
} else {
// Known device -- direct login (no TFA needed)
const { user, accessToken } = result;
console.log('Logged in as:', user.email);
}
Subsequent logins from the same device (within 90 days) will bypass TFA automatically.
Invalidates the session server-side and clears all local tokens. The server call is best-effort -- local state is always cleared even if the network request fails.
await auth.logout();
console.log(auth.isAuthenticated()); // false
Fetch the authenticated user's profile from the server.
const { user } = await auth.getUser();
console.log(user.email, user.name);
This requires a valid access token. If the token is expired and auto-refresh is enabled, the SDK will attempt to refresh it before the request.
Fetch or update the authenticated user's full profile, which includes additional fields beyond what getUser() returns (picture, phone, organization, metadata, roles, etc.).
const { profile } = await auth.getProfile();
console.log(profile.name, profile.email);
console.log(profile.organization, profile.phone);
console.log(profile.metadata); // custom key-value data
const { profile } = await auth.updateProfile({
name: 'Jane Smith',
phone: '+1234567890',
organization: 'Acme Corp',
metadata: { department: 'Engineering', theme: 'dark' },
});
console.log('Updated:', profile.name);
Updatable fields: name, picture, phone, organization, and metadata. Roles cannot be changed via this method.
Manually refresh access and refresh tokens. This is handled automatically when autoRefresh is enabled, but you can call it explicitly if needed.
const { accessToken, refreshToken, expiresIn } = await auth.refreshTokens();
If the refresh token is invalid or expired, this throws an AuthError with code TOKEN_REFRESH_FAILED and clears the local auth state.
await auth.requestPasswordReset({ email: 'user@example.com' });
// Server sends an email with a reset link containing a token
Extract the token from the reset link (typically a URL query parameter) and submit the new password:
await auth.resetPassword({
token: 'reset_token_from_email_link',
password: 'newSecurePassword456',
});
Extract the token from the verification link and submit it:
await auth.verifyEmail({ token: 'verification_token_from_email' });
await auth.resendVerification({ email: 'user@example.com' });
MCP tokens are long-lived API tokens for programmatic access (CI/CD pipelines, integrations, MCP server connections). All MCP token methods require the user to be logged in.
const { rawToken, token } = await auth.createMCPToken({
name: 'CI/CD Pipeline',
permissions: ['mcp:data:read', 'mcp:search:execute'],
expiresIn: '90d',
ipRestriction: ['203.0.113.0/24'],
});
// Store rawToken securely -- it cannot be retrieved again
console.log('Token:', rawToken);
console.log('Token ID:', token.id);
const { tokens } = await auth.listMCPTokens();
tokens.forEach((t) => {
console.log(t.id, t.metadata.name, t.expiresAt);
});
const { valid, token } = await auth.validateMCPToken(rawToken);
if (valid) {
console.log('Token is valid, permissions:', token.permissions);
}
await auth.revokeMCPToken(tokenId);
The SDK supports the full OAuth 2.1 Authorization Code flow with PKCE (S256). This enables secure third-party integrations.
Create and manage OAuth clients. All client management methods require the user to be logged in.
// Create a client
const { clientId, clientSecret, client } = await auth.createOAuthClient({
clientName: 'My Integration',
redirectUris: ['https://myapp.com/callback'],
scopes: ['mcp:data:read'],
});
// List clients
const { clients } = await auth.listOAuthClients();
// Get a specific client
const { client: details } = await auth.getOAuthClient(clientId);
// Delete a client
await auth.deleteOAuthClient(clientId);
The full OAuth 2.1 flow with PKCE:
// Step 1: Create an authorization code (user must be logged in)
// PKCE verifier/challenge are generated automatically
const { code, codeVerifier, state } = await auth.createAuthorizationCode({
clientId: 'your-client-id',
redirectUri: 'https://myapp.com/callback',
scope: 'mcp:data:read',
state: 'random-state-value',
});
// Step 2: Exchange the code for tokens (no login required)
const tokens = await auth.exchangeAuthorizationCode({
code,
redirectUri: 'https://myapp.com/callback',
codeVerifier,
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
});
console.log(tokens.access_token);
console.log(tokens.refresh_token);
console.log(tokens.expires_in);
// Step 3: Refresh OAuth tokens when they expire
const newTokens = await auth.refreshOAuthToken({
refreshToken: tokens.refresh_token,
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
});
The SDK supports adaptive two-factor authentication. When TFA is enabled on the organization (settings.tfa.enabled), unknown devices trigger an email OTP challenge. Known (trusted) devices bypass TFA automatically. When TFA is disabled, login() always returns tokens directly.
TFA and email verification are independent settings -- you can use either, both, or neither. See the Server Integration Guide for configuration details.
Use the onTfaRequired event to react when login triggers a TFA challenge:
// Listen for TFA events before calling login
const unsubscribe = auth.onTfaRequired(({ methods, expiresIn, user }) => {
console.log('TFA required for', user.email);
console.log('Available methods:', methods); // ['email_otp']
console.log('Code expires in', expiresIn, 'seconds');
// Show your OTP input UI here
});
const result = await auth.login({ email, password });
if (result.tfaRequired) {
// The onTfaRequired callback has already fired
// Wait for user to enter their OTP code...
}
After the user receives and enters the 6-digit code from their email:
try {
const { user, accessToken, deviceId } = await auth.verifyTfa({
code: '123456',
type: 'email_otp', // optional, defaults to 'email_otp'
});
console.log('Authenticated:', user.email);
console.log('Device trusted:', deviceId);
// This device is now registered as trusted for future logins
} catch (err) {
switch (err.code) {
case 'TFA_INVALID_CODE':
console.log('Wrong code, try again');
break;
case 'TFA_CHALLENGE_EXPIRED':
console.log('Code expired, please log in again');
break;
case 'TFA_CHALLENGE_LOCKED':
console.log('Too many attempts, please log in again');
break;
case 'INVALID_STATE':
console.log('No pending TFA challenge -- call login() first');
break;
}
}
If the user didn't receive the code or it expired:
const { message, expiresIn } = await auth.resendOtp();
console.log(message); // 'New code sent'
console.log('New code expires in', expiresIn, 'seconds');
After successful TFA verification, the device is automatically registered as trusted. Users can view and manage their trusted devices.
const { devices } = await auth.listTrustedDevices();
devices.forEach((device) => {
console.log(device.deviceId, device.deviceName, device.lastUsedAt);
});
await auth.revokeTrustedDevice('device-id-to-revoke');
// The device will require TFA on next login
const { count } = await auth.revokeAllTrustedDevices();
console.log(`Revoked ${count} devices`);
// All devices (including current) will require TFA on next login
isAuthenticated() returns true if there is a non-expired access token stored locally. It does not make a network request.
if (auth.isAuthenticated()) {
// User has a valid token
}
Retrieve the raw access token string for use in custom API calls:
const token = auth.getAccessToken();
if (token) {
fetch('https://your-api-host.com/api/data', {
headers: { Authorization: `Bearer ${token}` },
});
}
The authStateChange event fires on login, logout, and when a token refresh fails (which clears auth state):
const unsubscribe = auth.onAuthStateChange(({ authenticated, user }) => {
if (authenticated) {
console.log('Signed in as', user.email);
} else {
console.log('Signed out');
}
});
// Later: stop listening
unsubscribe();
| Event | Payload | When |
|---|---|---|
authStateChange |
{ authenticated: boolean, user: object | null } |
Login, logout, or refresh failure |
tokenRefreshed |
{ accessToken: string, expiresIn: number } |
Successful token refresh |
error |
AuthError |
Token refresh failure |
tfaRequired |
{ challengeToken: string, methods: string[], expiresIn: number, user: object } |
Login requires TFA verification |
The SDK handles tokens automatically, but here is a summary of the behavior:
localStorage storage (the default).refreshBuffer seconds before expiry. No action is needed from your code.isAuthenticated() returns whether the access token is still valid, without making a network request.authStateChange event fires with { authenticated: false }.tokenStorage: 'memory' for environments where localStorage is unavailable (SSR, tests, private browsing fallback). Tokens are lost on page reload. For production apps, 'memory' is recommended — it keeps refresh tokens out of localStorage, where they could be exfiltrated by XSS. The trade-off is that users must re-authenticate after a page reload.All SDK methods throw AuthError instances on failure. Each error has a code property from the AUTH_ERRORS constant, a human-readable message, and optionally a statusCode and details object.
import { HiyveAuth, AuthError, AUTH_ERRORS } from '@hiyve/identity-client';
try {
await auth.login({ email: 'user@example.com', password: 'wrong' });
} catch (err) {
if (err instanceof AuthError) {
switch (err.code) {
case AUTH_ERRORS.INVALID_CREDENTIALS:
showError('Invalid email or password');
break;
case AUTH_ERRORS.EMAIL_NOT_VERIFIED:
showError('Please verify your email');
break;
case AUTH_ERRORS.RATE_LIMITED:
showError('Too many attempts. Please wait.');
break;
case AUTH_ERRORS.NETWORK_ERROR:
showError('Network error. Check your connection.');
break;
case AUTH_ERRORS.TIMEOUT:
showError('Request timed out. Try again.');
break;
default:
showError(err.message);
}
}
}
| Code | HTTP Status | Description |
|---|---|---|
NETWORK_ERROR |
-- | Network failure (no response received) |
TIMEOUT |
-- | Request exceeded the timeout |
INVALID_CREDENTIALS |
401 | Wrong email or password |
TOKEN_EXPIRED |
401 | Access token has expired |
TOKEN_REFRESH_FAILED |
varies | Refresh token is invalid or expired |
UNAUTHORIZED |
401 | Generic authentication failure |
FORBIDDEN |
403 | Insufficient permissions |
NOT_FOUND |
404 | Resource not found |
RATE_LIMITED |
429 | Too many requests |
VALIDATION_ERROR |
400 | Invalid input (check err.details) |
EMAIL_NOT_VERIFIED |
401 | Email address not yet verified |
USER_ALREADY_EXISTS |
409 | Email already registered |
SERVER_ERROR |
5xx | Unexpected server error |
INVALID_CONFIG |
-- | Bad SDK configuration or destroyed instance |
INVALID_TOKEN |
-- | Token is malformed or unreadable |
INVALID_GRANT |
400 | Invalid authorization code or grant type |
INVALID_SCOPE |
400 | Requested scope is not allowed |
TFA_REQUIRED |
200 | Login requires two-factor authentication |
TFA_INVALID_CODE |
401 | OTP code is incorrect |
TFA_CHALLENGE_EXPIRED |
401 | TFA challenge has expired |
TFA_CHALLENGE_LOCKED |
429 | Too many failed OTP attempts |
INVALID_STATE |
-- | No pending TFA challenge (call login() first) |
When code is VALIDATION_ERROR, the details property contains field-level errors from the server:
try {
await auth.register({ email: 'bad', password: '123' });
} catch (err) {
if (err.code === AUTH_ERRORS.VALIDATION_ERROR) {
console.log(err.details);
// e.g. { email: '"email" must be a valid email', password: 'minimum 8 characters' }
}
}
AuthError instances have a toJSON() method for logging:
catch (err) {
console.log(JSON.stringify(err));
// { "name": "AuthError", "code": "INVALID_CREDENTIALS", "message": "...", "statusCode": 401, "details": null }
}
If you need to make custom requests to additional API endpoints that share the same API key and auth headers:
import { HttpClient } from '@hiyve/identity-client';
const client = new HttpClient({
baseUrl: 'https://your-api-host.com/api',
apiKey: 'pk_live_your_api_key',
timeout: 15000,
getAccessToken: () => auth.getAccessToken(),
});
// Unauthenticated request
const data = await client.get('/public/info');
// Authenticated request
const profile = await client.get('/profile', { auth: true });
// POST with body
const result = await client.post('/items', { name: 'New Item' }, { auth: true });
// PUT with body
await client.put('/items/123', { name: 'Updated Item' }, { auth: true });
// DELETE request
await client.delete('/items/123', { auth: true });
Every request automatically includes the X-Hiyve-Api-Key header. When { auth: true } is passed, the Authorization: Bearer <token> header is included. HTTP errors are mapped to AuthError instances.
For advanced use cases (e.g., building a custom auth flow or integrating with a state management library):
import { TokenManager } from '@hiyve/identity-client';
const tokens = new TokenManager({
storage: 'localStorage', // or 'memory'
autoRefresh: true,
refreshBuffer: 120, // refresh 2 minutes before expiry
});
// Store tokens from your own login endpoint
tokens.setTokens({ accessToken: 'eyJ...', refreshToken: 'eyJ...' });
// Check state
tokens.isAuthenticated(); // true if access token is not expired
tokens.getAccessToken(); // raw JWT string or null
tokens.getRefreshToken(); // raw refresh token string or null
// Decode JWT payload (no signature verification)
const payload = tokens.decodeToken(tokens.getAccessToken());
console.log(payload.sub, payload.email, payload.exp);
// Get expiry as Unix timestamp (seconds)
const exp = tokens.getTokenExpiry(tokens.getAccessToken());
console.log('Expires at:', new Date(exp * 1000));
// Clear everything
tokens.clear();
If you need to generate PKCE pairs for custom OAuth flows:
import { PKCEHelper } from '@hiyve/identity-client';
// Generate a PKCE pair
const { codeVerifier, codeChallenge } = await PKCEHelper.generatePKCEPair();
// Or generate components individually
const verifier = PKCEHelper.generateCodeVerifier();
const challenge = await PKCEHelper.generateCodeChallenge(verifier);
Generates cryptographically secure PKCE pairs for OAuth 2.1 flows.
If you need to access device fingerprinting independently:
import { DeviceFingerprint } from '@hiyve/identity-client';
// Collect browser signals
const fingerprint = DeviceFingerprint.collect();
console.log(fingerprint);
// { screenResolution: '1920x1080', timezone: 'America/New_York', language: 'en-US', platform: 'MacIntel' }
// Get or create a persistent device ID
const deviceId = DeviceFingerprint.getDeviceId('localStorage', 'myapp_');
// Encode fingerprint for transport
const encoded = DeviceFingerprint.encode(fingerprint);
// Base64-encoded JSON string
The SDK uses DeviceFingerprint automatically during login() to send device recognition headers.
Use in-memory storage for server-side rendering, testing, or environments without localStorage:
const auth = new HiyveAuth({
apiKey: 'pk_test_your_api_key',
tokenStorage: 'memory',
});
If localStorage is configured but unavailable at runtime (e.g., private browsing restrictions), the SDK automatically falls back to memory storage.
If your backend mounts the auth routes at a different path:
const auth = new HiyveAuth({
apiKey: 'pk_live_your_api_key',
basePath: '/v2/auth', // overrides the default /identity/auth path
});
A typical pattern for React applications:
import { useEffect, useState, createContext, useContext } from 'react';
import { HiyveAuth } from '@hiyve/identity-client';
// Create a singleton instance
const auth = new HiyveAuth({
apiKey: 'pk_live_your_api_key',
});
const AuthContext = createContext(null);
export function AuthProvider({ children }) {
const [state, setState] = useState({
authenticated: auth.isAuthenticated(),
user: null,
loading: true,
});
useEffect(() => {
// Listen for auth changes
const unsubscribe = auth.onAuthStateChange(({ authenticated, user }) => {
setState({ authenticated, user, loading: false });
});
// Load user if we have a token
if (auth.isAuthenticated()) {
auth.getUser()
.then(({ user }) => setState({ authenticated: true, user, loading: false }))
.catch(() => setState({ authenticated: false, user: null, loading: false }));
} else {
setState((s) => ({ ...s, loading: false }));
}
return () => {
unsubscribe();
auth.destroy();
};
}, []);
return (
{children}
);
}
export function useAuth() {
return useContext(AuthContext);
}
Usage in a component:
function LoginPage() {
const { auth } = useAuth();
const [tfaPending, setTfaPending] = useState(false);
useEffect(() => {
return auth.onTfaRequired(() => setTfaPending(true));
}, []);
async function handleLogin(email, password) {
try {
const result = await auth.login({ email, password });
if (result.tfaRequired) {
// TFA UI will show via onTfaRequired callback
}
// If no TFA, AuthProvider updates state via onAuthStateChange
} catch (err) {
// Handle error
}
}
async function handleVerifyOtp(code) {
try {
await auth.verifyTfa({ code });
setTfaPending(false);
// AuthProvider updates state via onAuthStateChange
} catch (err) {
// Handle TFA_INVALID_CODE, TFA_CHALLENGE_EXPIRED, etc.
}
}
if (tfaPending) {
return ;
}
// ... render login form
}
function Dashboard() {
const { authenticated, user, loading } = useAuth();
if (loading) return Loading...;
if (!authenticated) return ;
return Welcome, {user.name};
}
The main SDK class. Coordinates authentication flows, token storage, and event emission.
new HiyveAuth(config: HiyveAuthConfig)
Throws AuthError with code INVALID_CONFIG if neither apiKey nor baseUrl is provided, or if environment is invalid.
| Method | Returns | Description |
|---|---|---|
register({ email, password, name?, metadata? }) |
Promise<{ user }> |
Register a new user |
login({ email, password }) |
Promise<LoginResult> |
Log in (may return tokens or TFA challenge) |
logout() |
Promise<void> |
Log out and clear tokens |
refreshTokens() |
Promise<{ accessToken, refreshToken, expiresIn }> |
Manually refresh tokens |
getUser() |
Promise<{ user }> |
Fetch authenticated user's basic identity |
getProfile() |
Promise<{ profile }> |
Fetch the full user profile (name, picture, phone, organization, metadata, roles) |
updateProfile({ name?, picture?, phone?, organization?, metadata? }) |
Promise<{ profile }> |
Update the user's own profile |
requestPasswordReset({ email }) |
Promise<{ message }> |
Send password reset email |
resetPassword({ token, password }) |
Promise<{ message }> |
Reset password with token |
verifyEmail({ token }) |
Promise<{ message }> |
Verify email address |
resendVerification({ email }) |
Promise<{ message }> |
Resend verification email |
createMCPToken({ name?, permissions?, expiresIn?, ipRestriction? }) |
Promise<{ rawToken, token }> |
Create an MCP token |
listMCPTokens() |
Promise<{ tokens }> |
List MCP tokens |
revokeMCPToken(tokenId) |
Promise<{ message }> |
Revoke an MCP token |
validateMCPToken(token) |
Promise<{ valid, token? }> |
Validate a raw MCP token |
createOAuthClient({ clientName, redirectUris, scopes? }) |
Promise<{ clientId, clientSecret, client }> |
Create an OAuth client |
listOAuthClients() |
Promise<{ clients }> |
List OAuth clients |
getOAuthClient(clientId) |
Promise<{ client }> |
Get an OAuth client |
deleteOAuthClient(clientId) |
Promise<{ message }> |
Delete an OAuth client |
createAuthorizationCode({ clientId, redirectUri, scope?, state? }) |
Promise<{ code, codeVerifier, codeChallenge, state }> |
Create authorization code with auto-PKCE |
exchangeAuthorizationCode({ code, redirectUri, codeVerifier, clientId, clientSecret }) |
Promise<OAuthTokenResponse> |
Exchange code for tokens |
refreshOAuthToken({ refreshToken, clientId, clientSecret }) |
Promise<OAuthTokenResponse> |
Refresh OAuth tokens |
isAuthenticated() |
boolean |
Check if access token exists and is not expired |
getAccessToken() |
string | null |
Get the raw access token |
onAuthStateChange(callback) |
() => void |
Subscribe to auth state changes; returns unsubscribe function |
verifyTfa({ code, type? }) |
Promise<{ user, accessToken, refreshToken, expiresIn, deviceId }> |
Verify OTP code for pending TFA challenge |
resendOtp() |
Promise<{ message, expiresIn }> |
Resend OTP email for pending challenge |
onTfaRequired(callback) |
() => void |
Subscribe to TFA challenge events; returns unsubscribe function |
listTrustedDevices() |
Promise<{ devices }> |
List trusted devices for authenticated user |
revokeTrustedDevice(deviceId) |
Promise<{ message }> |
Revoke a specific trusted device |
revokeAllTrustedDevices() |
Promise<{ message, count }> |
Revoke all trusted devices |
destroy() |
void |
Stop auto-refresh, remove all listeners. Instance is unusable after this. |
Manages token storage, JWT payload decoding, and auto-refresh scheduling.
new TokenManager(options?: TokenManagerOptions)
| Option | Type | Default | Description |
|---|---|---|---|
storage |
string |
'localStorage' |
'localStorage' or 'memory' |
storagePrefix |
string |
-- | Optional key prefix for localStorage |
autoRefresh |
boolean |
true |
Enable auto-refresh scheduling |
refreshBuffer |
number |
300 |
Seconds before expiry to trigger refresh |
| Method | Returns | Description |
|---|---|---|
setTokens({ accessToken, refreshToken }) |
void |
Store both tokens |
getAccessToken() |
string | null |
Retrieve the access token |
getRefreshToken() |
string | null |
Retrieve the refresh token |
clear() |
void |
Remove all tokens and stop auto-refresh |
isAuthenticated() |
boolean |
Check if access token exists and is not expired |
getTokenExpiry(token) |
number | null |
Decode exp from JWT (seconds since epoch) |
decodeToken(token) |
object | null |
Decode full JWT payload (no signature verification) |
startAutoRefresh(refreshFn) |
void |
Start auto-refresh with the given async function |
stopAutoRefresh() |
void |
Cancel the auto-refresh timer |
Fetch wrapper that adds API key headers, authorization, timeouts, and error mapping.
new HttpClient(options: HttpClientOptions)
| Option | Type | Default | Description |
|---|---|---|---|
baseUrl |
string |
required | Full base URL for requests |
apiKey |
string |
required | API key sent as X-Hiyve-Api-Key header |
timeout |
number |
30000 |
Request timeout in milliseconds |
getAccessToken |
() => string | null |
() => null |
Function returning current access token |
| Method | Returns | Description |
|---|---|---|
request(method, path, body?, options?) |
Promise<object> |
Make an HTTP request |
get(path, options?) |
Promise<object> |
GET request |
post(path, body?, options?) |
Promise<object> |
POST request |
put(path, body?, options?) |
Promise<object> |
PUT request |
delete(path, options?) |
Promise<object> |
DELETE request |
Request options:
| Option | Type | Default | Description |
|---|---|---|---|
auth |
boolean |
false |
Include Authorization: Bearer <token> header |
timeout |
number |
-- | Override the default timeout for this request |
headers |
Record<string, string> |
-- | Additional headers to merge into the request |
All requests include credentials: 'include' for cookie-based flows and Content-Type: application/json.
Static utility class for generating PKCE (Proof Key for Code Exchange) parameters for OAuth 2.1 authorization flows. Uses the Web Crypto API.
| Method | Returns | Description |
|---|---|---|
PKCEHelper.generateCodeVerifier() |
string |
Generate a cryptographically random code verifier (43+ chars, base64url) |
PKCEHelper.generateCodeChallenge(verifier) |
Promise<string> |
Compute S256 code challenge from a verifier (base64url-encoded SHA-256) |
PKCEHelper.generatePKCEPair() |
Promise<{ codeVerifier, codeChallenge }> |
Generate both verifier and challenge |
Static utility class for browser device fingerprinting. Used automatically by HiyveAuth.login() to send device recognition headers.
| Method | Returns | Description |
|---|---|---|
DeviceFingerprint.collect() |
DeviceFingerprintData |
Collect browser signals (screen, timezone, language, platform) |
DeviceFingerprint.getDeviceId(storageType?, prefix?) |
string | null |
Get stored device ID from localStorage or memory |
DeviceFingerprint.setDeviceId(storageType?, prefix?, id?) |
void |
Store a device ID |
DeviceFingerprint.encode(fingerprint) |
string |
Base64-encode fingerprint data for HTTP headers |
Minimal pub/sub event system used internally by HiyveAuth. Also exported for custom use.
| Method | Returns | Description |
|---|---|---|
on(event, callback) |
() => void |
Subscribe; returns unsubscribe function |
emit(event, data?) |
void |
Emit an event to all subscribers |
removeAllListeners(event?) |
void |
Remove listeners for one event, or all events |
listenerCount(event) |
number |
Count of listeners for an event |
Custom error class extending Error. All SDK errors are AuthError instances.
| Property | Type | Description |
|---|---|---|
name |
string |
Always 'AuthError' |
code |
string |
Error code from AUTH_ERRORS |
message |
string |
Human-readable message |
statusCode |
number | null |
HTTP status code (if applicable) |
details |
object | null |
Additional details (e.g., validation errors) |
Methods: toJSON() returns a plain object with all properties.
Constant object mapping error names to string codes. Use for switch/if comparisons:
import { AUTH_ERRORS } from '@hiyve/identity-client';
AUTH_ERRORS.NETWORK_ERROR // 'NETWORK_ERROR'
AUTH_ERRORS.TIMEOUT // 'TIMEOUT'
AUTH_ERRORS.INVALID_CREDENTIALS // 'INVALID_CREDENTIALS'
AUTH_ERRORS.TOKEN_EXPIRED // 'TOKEN_EXPIRED'
AUTH_ERRORS.TOKEN_REFRESH_FAILED // 'TOKEN_REFRESH_FAILED'
AUTH_ERRORS.UNAUTHORIZED // 'UNAUTHORIZED'
AUTH_ERRORS.FORBIDDEN // 'FORBIDDEN'
AUTH_ERRORS.NOT_FOUND // 'NOT_FOUND'
AUTH_ERRORS.RATE_LIMITED // 'RATE_LIMITED'
AUTH_ERRORS.VALIDATION_ERROR // 'VALIDATION_ERROR'
AUTH_ERRORS.EMAIL_NOT_VERIFIED // 'EMAIL_NOT_VERIFIED'
AUTH_ERRORS.USER_ALREADY_EXISTS // 'USER_ALREADY_EXISTS'
AUTH_ERRORS.SERVER_ERROR // 'SERVER_ERROR'
AUTH_ERRORS.INVALID_CONFIG // 'INVALID_CONFIG'
AUTH_ERRORS.INVALID_TOKEN // 'INVALID_TOKEN'
AUTH_ERRORS.INVALID_GRANT // 'INVALID_GRANT'
AUTH_ERRORS.INVALID_SCOPE // 'INVALID_SCOPE'
AUTH_ERRORS.TFA_REQUIRED // 'TFA_REQUIRED'
AUTH_ERRORS.TFA_INVALID_CODE // 'TFA_INVALID_CODE'
AUTH_ERRORS.TFA_CHALLENGE_EXPIRED // 'TFA_CHALLENGE_EXPIRED'
AUTH_ERRORS.TFA_CHALLENGE_LOCKED // 'TFA_CHALLENGE_LOCKED'
AUTH_ERRORS.INVALID_STATE // 'INVALID_STATE'
The Hiyve Identity system passes 9 of 10 OWASP Top 10 (2021) categories with full compliance, covering broken access control, cryptographic controls, injection prevention, adaptive TFA, multi-tenant isolation, and layered rate limiting. One category (Security Logging & Monitoring) has partial compliance with structured audit logging on the roadmap.
Key security highlights:
Token storage advisory: The default
tokenStorage: 'localStorage'persists refresh tokens inlocalStorage, making them accessible to any JavaScript on the same origin. If your site includes third-party scripts (analytics, ads, support widgets), consider usingtokenStorage: 'memory'to keep refresh tokens out of reach of XSS. The trade-off is that sessions will not survive page reloads. When using server-side proxy mode (baseUrl), you can implementhttpOnlycookie-based refresh on your server for the strongest protection.
See COMPLIANCE_REPORT.md for the full compliance report including OWASP mapping, cryptographic controls, architecture diagrams, open items, and a consumer security checklist.
The SDK uses standard web APIs available in all modern browsers:
fetch and ResponseAbortControlleratoblocalStorage (with automatic fallback to memory)setTimeout / clearTimeoutcrypto.subtle and crypto.getRandomValues (for PKCE generation)Minimum browser versions: Chrome 66+, Firefox 57+, Safari 12+, Edge 79+
No polyfills are required for modern browsers. For legacy environments, you may need polyfills for fetch and AbortController.
Commercial - IWantToPractice, LLC