npm package: @fjordid/client
Description: Lightweight JavaScript/TypeScript client for FjordID authentication. Wraps Keycloak OIDC with a simple, developer-friendly API.
Latest version: 0.2.1

# @fjordid/client

Lightweight JavaScript/TypeScript client for [FjordID](https://fjordid.eu) authentication. Wraps Keycloak OIDC with a simple, developer-friendly API.

## Install

```bash
npm install @fjordid/client
# or
pnpm add @fjordid/client
```

## Quick Start

```typescript
import { FjordAuth } from "@fjordid/client";

// Option A — FjordID-hosted (domain shorthand)
const auth = new FjordAuth({
  domain: "your-app.fjordid.eu",
  clientId: "your-client-id",
});

// Option B — self-hosted or custom URL+realm
const auth = new FjordAuth({
  authUrl: "https://auth.example.com",
  realm: "my-realm",
  clientId: "your-client-id",
});

await auth.init();
auth.login();
```

## Usage

### Initialize

```typescript
// FjordID-hosted — use domain shorthand
const auth = new FjordAuth({
  domain: "your-app.fjordid.eu",
  clientId: "your-client-id",
});

// Self-hosted / custom setup — provide authUrl + realm directly
const auth = new FjordAuth({
  authUrl: window.__config__.FJORDID_URL,
  realm: window.__config__.FJORDID_REALM,
  clientId: "your-client-id",
  // Optional:
  // redirectUri: window.location.origin,
  // silentSso: true,
  // initTimeout: 10000,
});

const isLoggedIn = await auth.init();
```

### Login / Register / Logout

```typescript
auth.login(); // Redirect to login page
auth.register(); // Redirect to registration page
auth.logout(); // Log out and redirect home

// Custom redirect after login
auth.login({ redirectUri: "/dashboard" });

// Pass locale so the login page opens in the right language
auth.login({ locale: i18n.language });

// Both at once
auth.login({ redirectUri: "/dashboard", locale: "nb" });

// Legacy string form still works
auth.login("/dashboard");
```

### User Info

```typescript
if (auth.authenticated) {
  console.log(auth.user?.email);
  console.log(auth.user?.name);
  console.log(auth.user?.id);
}
```

### Access Tokens

```typescript
// Get a valid token (auto-refreshes if expiring soon)
const token = await auth.getToken();

// Use in API calls
const res = await fetch("/api/data", {
  headers: { Authorization: `Bearer ${token}` },
});
```

### Events

```typescript
const unsub = auth.on("onAuthSuccess", () => {
  console.log("Logged in:", auth.user?.email);
});

auth.on("onAuthLogout", () => {
  console.log("Logged out");
});

// Unsubscribe
unsub();
```

## API Reference

### `new FjordAuth(options)`

| Option                  | Type      | Default                   | Description                                                       |
| ----------------------- | --------- | ------------------------- | ----------------------------------------------------------------- |
| `domain`                | `string`  | —                         | FjordID-hosted domain (required unless `authUrl`+`realm` are set) |
| `authUrl`               | `string`  | `https://auth.fjordid.eu` | Keycloak server URL (required when `domain` is omitted)           |
| `realm`                 | `string`  | `fjordid`                 | Keycloak realm (required when `domain` is omitted)                |
| `clientId`              | `string`  | **required**              | OIDC client ID                                                    |
| `redirectUri`           | `string`  | `window.location.origin`  | Post-login redirect                                               |
| `postLogoutRedirectUri` | `string`  | `window.location.origin`  | Post-logout redirect                                              |
| `silentSso`             | `boolean` | `true`                    | Enable silent SSO check                                           |
| `initTimeout`           | `number`  | `10000`                   | Init timeout (ms)                                                 |
| `tokenRefreshBuffer`    | `number`  | `30`                      | Seconds before expiry to refresh                                  |

### Properties

| Property        | Type                  | Description                   |
| --------------- | --------------------- | ----------------------------- |
| `authenticated` | `boolean`             | Whether the user is logged in |
| `user`          | `FjordUser \| null`   | Current user profile          |
| `token`         | `string \| undefined` | Raw access token              |
| `idToken`       | `string \| undefined` | Raw ID token                  |

### Methods

| Method                 | Returns                        | Description                                                        |
| ---------------------- | ------------------------------ | ------------------------------------------------------------------ |
| `init()`               | `Promise<boolean>`             | Initialize auth (call once)                                        |
| `login(options?)`      | `void`                         | Redirect to login (accepts `LoginOptions` or URI string)           |
| `register(options?)`   | `void`                         | Redirect to registration (accepts `RegisterOptions` or URI string) |
| `logout(redirectUri?)` | `void`                         | Log out                                                            |
| `getToken()`           | `Promise<string \| undefined>` | Get valid access token                                             |
| `refreshToken()`       | `Promise<string \| undefined>` | Force token refresh                                                |
| `on(event, callback)`  | `() => void`                   | Subscribe to events                                                |

### Events

| Event            | When                             |
| ---------------- | -------------------------------- |
| `onReady`        | Keycloak adapter ready           |
| `onAuthSuccess`  | Login succeeded                  |
| `onAuthLogout`   | User logged out                  |
| `onAuthError`    | Auth error occurred              |
| `onTokenExpired` | Token expired and refresh failed |

## Silent SSO

For silent SSO to work, serve this HTML file at `/silent-check-sso.html`:

```html
<!doctype html>
<html>
  <body>
    <script>
      parent.postMessage(location.href, location.origin);
    </script>
  </body>
</html>
```

## License

MIT