The `@authdog/javascript` SDK is a framework-agnostic browser client. It handles the OIDC redirect, token storage, sign-in and sign-out, and user resolution with no framework dependency.

`@authdog/javascript` is not yet published to npm (`npm view @authdog/javascript` returns 404 at the time of writing). Version `0.1.0` is set to publish publicly from the `web-sdk` repository, so install it from there until it lands on the registry.

## Install

```package-install
@authdog/javascript
```

No peer dependencies. It works in any browser bundle. Use your environment's public key (`pk_...`); it is safe to expose to the browser.

## Create the client

`createAuthdogClient` validates the public key once and throws immediately if it is missing, malformed, or points at an untrusted identity host:

```ts
import { createAuthdogClient } from "@authdog/javascript"

const authdog = createAuthdogClient({
  publicKey: "pk_...",
  // storageKey: "token", // optional localStorage key override
})

// On the page the identity provider redirects back to, consume the token.
authdog.handleRedirectCallback()

const render = async () => {
  if (authdog.isAuthenticated()) {
    const user = await authdog.getUser()
    console.log("signed in as", user)
  }
}
render()

signinButton.onclick = () => authdog.signIn()
signupButton.onclick = () => authdog.signUp()
signoutButton.onclick = () => authdog.signOut("/")

// React to token changes in this tab and other tabs.
const unsubscribe = authdog.subscribe(() => render())
```

## Client methods

| Method | Behaviour |
| --- | --- |
| `handleRedirectCallback()` | Reads `?token=` from the URL, always strips it, and stores it in `localStorage` only if JWT-shaped. Returns the token or `null`. No-op without a token, so call it on every load |
| `signIn(redirectUri?)` | Redirects to the OIDC `authorize` flow. `redirectUri` defaults to `window.location.origin` |
| `signUp(redirectUri?)` | Same as `signIn`, with `prompt=signup` |
| `signOut(redirectTo?)` | Clears the stored token and navigates to `redirectTo` if given |
| `getUser()` | Calls the OIDC `userinfo` endpoint with the stored token. Returns the user, or `null` if unauthenticated or rejected. Never throws |
| `getToken()` | The stored token, or `null` |
| `isAuthenticated()` | Whether a token is present. Presence only |
| `subscribe(listener)` | Calls `listener(token)` on token changes in this tab (custom `authdog:token-updated` event) and other tabs (`storage` event). Returns an unsubscribe function |
| `getSigninUrl()` | The hosted sign-in URL, for use as a plain link target |
| `getPublicKeyPayload()` / `getPublicKey()` | The parsed public-key payload as an object / as a JSON string |

The default storage key is `token`. The `JWT_PATTERN` check is a shape test only. It is not signature, issuer, audience, or expiry validation.

## Billing

The client also exposes `authdog.billing`: `listPlans({ for? })` (no auth required), `getSubscription()`, `createSubscription({ planId, planPeriod? })` (returns a Stripe PaymentIntent client secret), and `cancelSubscription()`. Authenticated calls return `null` or `false` when there is no token or the request fails.

## Other exports

`buildAuthorizeUrl`, `buildSigninUrl`, `getTokenFromUri`, `isJwtShaped`, `getPublicKeyPayload`, `TOKEN_STORAGE_KEY`, `TOKEN_UPDATED_EVENT`, and `JWT_PATTERN` are exported for custom flows.

## Caveats

- The token lives in `localStorage`. This suits public-client SPA flows. For server-rendered apps that can hold an HttpOnly cookie, use a server-side framework SDK.
- `isAuthenticated()` is a UI hint. Validate the bearer token with a [backend SDK](/docs/backend) and enforce [authorization](/docs/concepts/authorization) on every protected operation server-side.
- Outside a browser (no `window`), the client returns `null` or does nothing instead of throwing.

## Next steps

- [Sign up & sign in](/docs/authentication): the flows behind the redirect.
- [Backend requests](/docs/backend): validate sessions on your API.
