# @bkper/web-auth

> Web authentication SDK for Bkper — OAuth flows, token management, and session handling.

[![npm](https://img.shields.io/npm/v/@bkper/web-auth?color=%235889e4)](https://www.npmjs.com/package/@bkper/web-auth) [![GitHub](https://img.shields.io/badge/bkper%2Fbkper--web--sdks-blue?logo=github)](https://github.com/bkper/bkper-web-sdks)

# @bkper/web-auth

OAuth authentication SDK for apps on the [Bkper Platform](https://bkper.com/docs/build/apps/overview) (`*.bkper.app` subdomains).

## Quick Start

```typescript
import { BkperAuth } from '@bkper/web-auth';

// Initialize client with callbacks
const auth = new BkperAuth({
    onLoginSuccess: () => {
        console.log('User authenticated!');
        loadUserData();
    },
    onLoginRequired: () => {
        console.log('Please sign in');
        showLoginButton();
    },
});

// Initialize authentication flow on app load
await auth.init();

// Get access token for API calls
const token = auth.getAccessToken();
if (token) {
    fetch('/api/data', {
        headers: { Authorization: `Bearer ${token}` },
    });
}
```

## Handling Token Expiration

Access tokens expire and need to be refreshed. The recommended pattern is to handle authentication errors and retry:

```typescript
async function apiRequest(url: string, options: RequestInit = {}) {
    // Add auth header
    const token = auth.getAccessToken();
    options.headers = {
        ...options.headers,
        Authorization: `Bearer ${token}`,
    };

    const response = await fetch(url, options);

    // Handle expired token
    if (response.status === 403) {
        try {
            await auth.refresh();
            options.headers = {
                ...options.headers,
                Authorization: `Bearer ${auth.getAccessToken()}`,
            };
            return fetch(url, options); // Retry once
        } catch (error) {
            // Refresh failed - the onError callback will be triggered
            // Handle the error appropriately (e.g., redirect to login, show error message)
            throw error;
        }
    }

    return response;
}
```

## What's Included

-   OAuth authentication SDK for apps on `*.bkper.app` subdomains
-   Callback-based API for authentication events
-   OAuth flow with in-memory token management
-   Token refresh mechanism
-   TypeScript support with full type definitions

## How It Works

**Session Persistence:**

-   Access tokens are stored in-memory (cleared on page refresh)
-   Sessions persist via HTTP-only cookies scoped to the `.bkper.app` domain
-   Call `init()` on app load to restore the session from cookies

> **Note:** This SDK only works for apps hosted on `*.bkper.app` subdomains. For apps on other domains, use a valid access token directly with [bkper-js](https://github.com/bkper/bkper-js#cdn--browser).

**Security:**

-   HTTP-only cookies protect refresh tokens from XSS
-   In-memory access tokens minimize exposure

## TypeScript Support

This package is written in TypeScript and provides full type definitions out of the box. All public APIs are fully typed, including callbacks and configuration options.

```typescript
import { BkperAuth, BkperAuthConfig } from '@bkper/web-auth';

const config: BkperAuthConfig = {
    onLoginSuccess: () => console.log('Authenticated'),
    onError: error => console.error('Auth error:', error),
};

const auth = new BkperAuth(config);
```

## Browser Compatibility

This package requires a modern browser with support for:

-   [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API#browser_compatibility) for HTTP requests
-   [Location API](https://developer.mozilla.org/en-US/docs/Web/API/Location) for login/logout redirects

The app must be deployed to a `*.bkper.app` subdomain for session cookies to work.

## Classes

### BkperAuth

OAuth authentication client for the Bkper API.

Provides framework-agnostic authentication with callback-based event handling.
Access tokens are stored in-memory; sessions persist via HTTP-only cookies.

```typescript
// Initialize authentication client
const auth = new BkperAuth({
  onLoginSuccess: () => loadUserData(),
  onLoginRequired: () => showLoginButton()
});

// Restore session on app load
await auth.init();
```

**Constructor:** `new BkperAuth(config?: BkperAuthConfig)`

Creates a new BkperAuth instance.

```typescript
// Simple usage with defaults
const auth = new BkperAuth();

// With callbacks
const auth = new BkperAuth({
  onLoginSuccess: () => console.log('Logged in!'),
  onLoginRequired: () => showLoginDialog(),
  onError: (error) => console.error(error)
});
```

**Methods:**

- `getAccessToken()` → `string | undefined` — Gets the current access token.
- `init()` → `Promise<void>` — Initializes the authentication state by attempting to refresh the access token.
- `login()` → `void` — Redirects the user to the login page.
- `logout()` → `void` — Logs out the user and redirects to the logout page.
- `refresh()` → `Promise<void>` — Refreshes the access token using the current session.

**getAccessToken**

```typescript
const token = auth.getAccessToken();
if (token) {
  // Make authenticated API calls
  fetch('/api/data', {
    headers: { 'Authorization': `Bearer ${token}` }
  });
}
```

**init**

Call this method when your app loads to restore the user's session.
Triggers `onLoginSuccess` if a valid session exists, or `onLoginRequired` if login is needed.

**login**

The user will be redirected to the authentication service to complete the login flow.
After successful login, they will be redirected back to the current page.

```typescript
// Trigger login when user clicks a button
loginButton.addEventListener('click', () => {
  auth.login();
});
```

**logout**

Triggers the `onLogout` callback before redirecting.
The user's session will be terminated.

```typescript
// Logout when user clicks logout button
logoutButton.addEventListener('click', () => {
  auth.logout();
});
```

**refresh**

Call this when API requests return 403 to get a new token and retry.
Triggers `onTokenRefresh` callback if successful.
Throws error if the refresh fails (network error, expired session, etc.).

```typescript
// Handle 403 by refreshing and retrying
const response = await fetch('/api/data', {
  headers: { 'Authorization': `Bearer ${auth.getAccessToken()}` }
});

if (response.status === 403) {
  await auth.refresh();
  // Retry with new token
  return fetch('/api/data', {
    headers: { 'Authorization': `Bearer ${auth.getAccessToken()}` }
  });
}
```

## Interfaces

### BkperAuthConfig

Configuration options for the BkperAuth class.

**Properties:**

- `baseUrl?`: `string` — Override the authentication service base URL.
- `getAdditionalAuthParams?`: `() => Record<string, string>` — Provide additional parameters to send to the authentication service.
- `onError?`: `(error: unknown) => void` — Called when an error occurs during authentication.
- `onLoginRequired?`: `() => void` — Called when login is required (user needs to sign in).
- `onLoginSuccess?`: `() => void` — Called when login succeeds (user is authenticated).
- `onLogout?`: `() => void` — Called when the user logs out.
- `onTokenRefresh?`: `(token: string) => void` — Called when the access token is refreshed.

**baseUrl**

Most users don't need this. The default production URL works out of the box.

Use cases:
- Testing: Point to a mock authentication service for integration tests
- Development: Use a local mock server

```typescript
// Testing with mock server
const auth = new BkperAuth({
  baseUrl: 'http://localhost:3000/mock-auth'
});
```

**getAdditionalAuthParams**

Useful for custom authentication flows or passing additional context
to your authentication implementation.

```typescript
// Custom authentication context
const auth = new BkperAuth({
  getAdditionalAuthParams: () => {
    const token = new URLSearchParams(location.search).get('custom-token');
    return token ? { customToken: token } : {};
  }
});
```

