npm package: @meshagent/meshagent-ts-auth
Description: Meshagent Typescript Auth
Author: Meshagent Software
Latest version: 0.36.3

# Meshagent Auth

Helpers for Meshagent OAuth login, PKCE, token refresh, and auth state.

This package is framework-agnostic. It gives you the primitives to:

- ensure a browser user is logged in with a single `ensureLogin(...)` call
- start an OAuth login with PKCE
- exchange the callback `code` for access and refresh tokens
- persist auth state in `localStorage`
- refresh expired access tokens
- create a Meshagent client with the current access token

## Install

```sh
npm install @meshagent/meshagent-ts-auth
```

## Tests

Use `npm test` to run the local Jest suite.
Use `npm run test:watch` while iterating on a regression.

The starter tests focus on the areas most likely to break in day-to-day auth work:

- auth state persistence and sign-out behavior
- expiry parsing and normalization
- access-token refresh behavior
- PKCE verifier and challenge generation

Add new regression tests in `test/*.test.ts` as issues come up.

## Before You Start

You need an OAuth client in Meshagent Studio.

1. Sign in to `https://studio.meshagent.com`.
2. Open `OAuth Clients`.
3. Create a new OAuth client.
4. Add your callback URL.

You will use:

- `serverUrl`: your Meshagent server, for example `https://api.meshagent.com`
- `oauthClientId`: the OAuth client ID from Studio
- `callbackUrl`: the URL Meshagent redirects back to after login

## What The Package Stores

`MeshagentAuth` stores the session in browser storage under these keys:

- `ma:access_token`
- `ma:refresh_token`
- `ma:expiration`

By default, the package uses `window.localStorage` in the browser and falls back to in-memory storage outside the browser.

## Main Building Blocks

- `ensureLogin`: high-level browser helper that starts login or completes the OAuth callback
- `MeshagentAuth`: stores tokens, expiration, and the current user
- `PkceGenerator`: generates `code_verifier` and `code_challenge`
- `LocalStoragePkceCache`: stores the PKCE verifier between redirect steps
- `exchangeToken`: exchanges the OAuth authorization code for tokens
- `OAuthSessionManager`: returns a valid access token and refreshes when needed
- `RefreshAccessTokenProvider`: thin wrapper around `OAuthSessionManager`
- `getMeshagentClient`: creates `@meshagent/meshagent` with the current token

## Login Flow

The simplest browser login flow is a single `await ensureLogin(...)` call.

`ensureLogin(...)` handles all of these cases:

- the user is already logged in, so it returns immediately
- the current URL contains `?code=...` and matches the configured callback URL, or when `callbackUrl` is omitted matches the current page URL after auth query params are stripped, so it exchanges the code, stores the session, loads the current user, and removes auth query params from the URL
- the user is not logged in yet, so it generates PKCE values, stores the verifier, and redirects to `/oauth/authorize`

When you omit `callbackUrl`, `ensureLogin(...)` uses the current page as the callback URL. After the OAuth redirect returns, it strips the auth query params from the current URL and reuses that same URL for the token exchange.

## Example: Ensure Login

Use the same function from a login button, on app startup, or on a dedicated callback page.

```ts
import {
  MeshagentAuth,
  ensureLogin,
} from "@meshagent/meshagent-ts-auth";

const serverUrl = "https://api.meshagent.com";
const callbackUrl = "http://localhost:3000/auth/callback";
const oauthClientId = "YOUR_OAUTH_CLIENT_ID";

export async function ensureMeshagentSession(provider?: string): Promise<void> {
  await ensureLogin({
    serverUrl,
    callbackUrl,
    oauthClientId,
    provider,
  });

  if (!MeshagentAuth.current.isLoggedIn()) {
    return;
  }

  console.log("Logged in:", MeshagentAuth.current.getSnapshot());
}
```

If you want to force a specific identity provider, pass `provider`, for example:

```ts
await ensureMeshagentSession("google");
```

`ensureLogin(...)` does all of the following for the default browser flow:

- returns immediately when the user is already logged in
- reads the stored PKCE verifier after the OAuth redirect
- sends the `/oauth/token` request
- stores `access_token` and `refresh_token`
- loads the current user profile
- stores token expiration when `expires_in` is present
- removes the auth query params from the current URL after a successful callback exchange

Use `exchangeToken(...)`, `PkceGenerator`, and `LocalStoragePkceCache` directly only if you need manual control over the redirect flow.

## Example: Use The Logged-In Session

After `ensureLogin(...)` completes on the callback return, `MeshagentAuth.current` contains the current session.

```ts
import { MeshagentAuth } from "@meshagent/meshagent-ts-auth";

const auth = MeshagentAuth.current;

if (auth.isLoggedIn()) {
  console.log("access token:", auth.getAccessToken());
  console.log("refresh token:", auth.getRefreshToken());
  console.log("expires at:", auth.expiration);
  console.log("user:", auth.getUser());
}
```

## Example: Refresh Access Tokens

Use `OAuthSessionManager` when you need a token that is still valid, or a refresh if the current token is too close to expiry.

```ts
import { OAuthSessionManager } from "@meshagent/meshagent-ts-auth";

const session = new OAuthSessionManager({
  serverUrl: "https://api.meshagent.com",
  clientId: "YOUR_OAUTH_CLIENT_ID",
});

const accessToken = await session.getValidAccessTokenOrThrow();
console.log(accessToken);
```

You can also use `RefreshAccessTokenProvider` if another API expects an object with `getToken(): Promise<string>`.

```ts
import { RefreshAccessTokenProvider } from "@meshagent/meshagent-ts-auth";

const tokenProvider = new RefreshAccessTokenProvider({
  serverUrl: "https://api.meshagent.com",
  clientId: "YOUR_OAUTH_CLIENT_ID",
});

const token = await tokenProvider.getToken();
```

## Example: Create A Meshagent Client

If you want a ready-to-use Meshagent client after login:

```ts
import {
  MeshagentAuth,
  getMeshagentClient,
} from "@meshagent/meshagent-ts-auth";

const client = getMeshagentClient(
  MeshagentAuth.current,
  "https://api.meshagent.com",
);

const me = await client.getUserProfile("me");
console.log(me);
```

`getMeshagentClient(...)` throws if the user is not logged in.

## Example: Sign Out

```ts
import { MeshagentAuth } from "@meshagent/meshagent-ts-auth";

MeshagentAuth.current.signOut();
```

This clears the stored access token, refresh token, expiration, and cached user.

## Example: Subscribe To Auth Changes

```ts
import { MeshagentAuth } from "@meshagent/meshagent-ts-auth";

const unsubscribe = MeshagentAuth.current.subscribe(() => {
  console.log("auth changed", MeshagentAuth.current.getSnapshot());
});

// later
unsubscribe();
```

## Browser Global Bundle

Run `npm run build` to generate `dist/browser/ma-auth.global.js`.

Serve that file from your app or copy it into your static assets, then include it with a classic script tag:

```html
<script src="https://storage.googleapis.com/cdn.meshagent.com/latest/ma-auth.global.js"></script>
<script>
  const auth = window.meshagent.auth.MeshagentAuth.current;
  console.log(auth.isLoggedIn());
</script>
```

TypeScript consumers using the global bundle can add:

```ts
/// <reference types="@meshagent/meshagent-ts-auth/global" />
```

The browser-global API matches the module API, so the earlier examples translate directly. For example:

```html
<script src="https://storage.googleapis.com/cdn.meshagent.com/latest/ma-auth.global.js"></script>
<script>
  async function ensureMeshagentSession(provider) {
    await window.meshagent.auth.ensureLogin({
      serverUrl: "https://api.meshagent.com",
      callbackUrl: "http://localhost:3000/auth/callback",
      oauthClientId: "YOUR_OAUTH_CLIENT_ID",
      provider,
    });

    if (!window.meshagent.auth.MeshagentAuth.current.isLoggedIn()) {
      return;
    }

    console.log(
      "Logged in:",
      window.meshagent.auth.MeshagentAuth.current.getSnapshot(),
    );
  }
<\/script>
```

## Notes

- This package assumes a browser-style OAuth redirect flow.
- `PkceGenerator` requires Web Crypto (`globalThis.crypto`).
- `ensureLogin(...)` manages the PKCE verifier for the default browser flow.
- `exchangeToken(...)` expects the PKCE verifier to still be available in storage after the redirect when you use the low-level helpers directly.
- `OAuthSessionManager` uses the stored refresh token and calls `/oauth/token` with `grant_type=refresh_token`.