npm package: @kokolabs-io/client
Description: Authentication client for Koko applications with built-in token management, auto-refresh, and localStorage integration
Author: KokoLabs.io
Latest version: 1.0.0

# @kokolabs-io/client

Authentication client for Koko applications with built-in token management, auto-refresh, and localStorage integration.

## Features

- **Token Management**: Automatic access and refresh token storage
- **Auto-Refresh**: Automatic token refresh before expiration
- **localStorage Integration**: Persistent token storage in the browser
- **Fetch Wrapper**: Built-in fetch with automatic auth headers
- **Unauthorized Handler**: Custom callback for handling 401 responses
- **SSO/OAuth Support**: Easy integration with OAuth flows
- **Type-Safe**: Full TypeScript support

## Installation

```bash
pnpm add @kokolabs-io/client
```

## Usage

### Basic Setup

```typescript
import { createAuthClient } from '@kokolabs-io/client';

const auth = createAuthClient({
  authUrl: 'https://your-app.com/auth/login',
  refreshUrl: 'https://your-app.com/auth/refresh',
  autoRefresh: true,
  onUnauthorized: async ({ response, token }) => {
    console.log('Unauthorized request detected');
    // Handle 401 responses, e.g., redirect to login
    window.location.href = '/login';
  }
});

// Initialize the client (loads tokens from localStorage)
const { authenticated, token } = auth.init();

if (authenticated) {
  console.log('User is authenticated:', token);
} else {
  console.log('User is not authenticated');
}
```

### Login

```typescript
// Redirect to login page
auth.login('/dashboard'); // Optional returnTo parameter

// Or use with OAuth/SSO
auth.login(); // Redirects to authUrl
```

### Logout

```typescript
// Logout and redirect
auth.logout({ redirect: true, returnTo: '/' });

// Logout without redirect (just clear tokens)
auth.logout({ redirect: false });
```

### Token Management

```typescript
// Get current access token
const token = auth.getToken();

// Set access token (e.g., after login)
auth.setToken('your-access-token');

// Clear access token
auth.clearToken();

// Get refresh token
const refreshToken = auth.getRefreshToken();

// Set refresh token
auth.setRefreshToken('your-refresh-token');

// Clear refresh token
auth.clearRefreshToken();
```

### Token Expiration

```typescript
// Get token expiration timestamp (in milliseconds)
const expiresAt = auth.getExpiresAtMs();

// Set token expiration
auth.setExpiresAtMs(Date.now() + 3600000); // 1 hour from now

// Check if token is expired
const isExpired = auth.isExpired();

// Check with time skew (default 60 seconds)
const isExpired = auth.isExpired(120000); // 2 minutes skew
```

### Token Refresh

```typescript
// Manually refresh the access token
try {
  const result = await auth.refresh();
  console.log('New access token:', result.access_token);
  console.log('New refresh token:', result.refresh_token);
  console.log('Expires in (seconds):', result.expires_in);
} catch (error) {
  console.error('Failed to refresh token:', error);
}
```

### Authenticated Fetch

The `fetch` method automatically adds the `Authorization` header and handles token refresh:

```typescript
// Make an authenticated request
const response = await auth.fetch('https://api.example.com/user/profile');
const data = await response.json();

// With custom options
const response = await auth.fetch('https://api.example.com/user/profile', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ name: 'John Doe' })
});
```

### Configuration Options

```typescript
interface KokoAuthClientConfig {
  // Required: URL to redirect to for login
  authUrl: string;

  // Optional: URL to refresh tokens (if not provided, refresh is disabled)
  refreshUrl?: string;

  // Optional: localStorage key for access token (default: 'koko_auth_token')
  storageKey?: string;

  // Optional: localStorage key for refresh token (default: 'koko_auth_refresh_token')
  refreshStorageKey?: string;

  // Optional: localStorage key for expiration timestamp (default: 'koko_auth_expires_at')
  expiresAtKey?: string;

  // Optional: Enable automatic token refresh (default: true)
  autoRefresh?: boolean;

  // Optional: Callback when 401 Unauthorized is received
  onUnauthorized?: (ctx: UnauthorizedContext) => void | Promise<void>;
}
```

## Complete Example

```typescript
import { createAuthClient } from '@kokolabs-io/client';

// Create auth client
const auth = createAuthClient({
  authUrl: 'https://your-app.com/auth/login',
  refreshUrl: 'https://your-app.com/auth/refresh',
  storageKey: 'my_app_token',
  autoRefresh: true,
  onUnauthorized: async ({ response }) => {
    // Clear tokens and redirect to login
    auth.clearToken();
    auth.clearRefreshToken();
    window.location.href = '/login?returnTo=' + encodeURIComponent(window.location.pathname);
  }
});

// Initialize on app load
const { authenticated } = auth.init();

// Login button handler
document.getElementById('login-btn')?.addEventListener('click', () => {
  auth.login(window.location.pathname);
});

// Logout button handler
document.getElementById('logout-btn')?.addEventListener('click', () => {
  auth.logout({ redirect: true, returnTo: '/' });
});

// Make authenticated API calls
async function fetchUserProfile() {
  try {
    const response = await auth.fetch('https://api.example.com/user/profile');

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const profile = await response.json();
    return profile;
  } catch (error) {
    console.error('Failed to fetch profile:', error);
    throw error;
  }
}

// Check authentication before making requests
if (authenticated) {
  fetchUserProfile().then(profile => {
    console.log('User profile:', profile);
  });
} else {
  console.log('Please login first');
  auth.login();
}
```

## API Reference

### Class: KokoAuthClient

#### Methods

- `init(): { authenticated: boolean; token: string | null }` - Initialize and check authentication status
- `login(returnTo?: string): void` - Redirect to login page
- `logout(options?: { redirect?: boolean; returnTo?: string }): void` - Logout user
- `getToken(): string` - Get access token
- `setToken(token: string): void` - Set access token
- `clearToken(): void` - Clear access token
- `getRefreshToken(): string` - Get refresh token
- `setRefreshToken(token: string): void` - Set refresh token
- `clearRefreshToken(): void` - Clear refresh token
- `getExpiresAtMs(): number` - Get token expiration timestamp
- `setExpiresAtMs(ms: number): void` - Set token expiration timestamp
- `clearExpiresAtMs(): void` - Clear token expiration timestamp
- `isExpired(skewMs?: number): boolean` - Check if token is expired
- `refresh(): Promise<{ access_token: string; refresh_token: string; expires_in: number }>` - Refresh access token
- `fetch(url: string, options?: RequestInit): Promise<Response>` - Make authenticated request

## Build

Build from root:

```bash
pnpm -C .. build
```

Build this package only:

```bash
pnpm --filter @kokolabs-io/client build
```

## License

MIT - Copyright (c) 2026 KokoLabs.io