# Quickstart

> Add sign-in to a web app in five minutes with the JavaScript SDK, then call your API with the user's token.

Source: https://9in.io/docs/quickstart/

This walks through signing users in to a browser app with `@9in-io/sdk`, then calling your own API with their access token. For a server-rendered app, follow [Server-rendered apps](https://9in.io/docs/server-apps/) instead; the console steps are the same.

## 1. Create a tenant and an application

1. Sign in to the [console](https://app.9in.io) and create a tenant, say `acme`. You get two issuers:
   - production: `https://acme.9in.io`
   - sandbox: `https://acme-sandbox.9in.io` (separate users; any password works and one-time codes are always `000000`)
2. Go to **Applications → Create application → Single-Page App** and set:
   - **Allowed callback URLs:** `http://localhost:5173/callback`
   - **Allowed logout URLs:** `http://localhost:5173/`
   - **Allowed web origins:** `http://localhost:5173` (the browser calls the token endpoint directly, so its origin must be allowed for CORS)
3. Copy the **Client ID** (`app_…`). Single-page apps have no client secret.

## 2. Install the SDK

```bash
npm install @9in-io/sdk
```

## 3. Sign in and out

```ts
import { NineAuth } from "@9in-io/sdk";

export const auth = new NineAuth({
  issuer: "https://acme-sandbox.9in.io",     // the sandbox while you develop
  clientId: "app_7f3k2m9q",
  redirectUri: `${location.origin}/callback`,
});

// A "Sign in" button
document.querySelector("#login")!.addEventListener("click", () => auth.login());

// A "Create account" button opens the sign-up form directly
document.querySelector("#signup")!.addEventListener("click", () => auth.signup());

// On /callback: finish signing in, then go where the user was heading
if (location.pathname === "/callback") {
  const { user, returnTo } = await auth.handleRedirect();
  console.log(`Signed in as ${user.email}`);
  history.replaceState(null, "", returnTo ?? "/");
}

// Anywhere
if (auth.isAuthenticated()) {
  const user = await auth.getUser();   // claims from the ID token: sub, email, name, picture…
}

// Sign out of your app and of 9in
document.querySelector("#logout")!.addEventListener("click", () => auth.logout({ returnTo: location.origin + "/" }));
```

`login()` redirects to the hosted sign-in page with authorization code + PKCE. `handleRedirect()` checks `state`, exchanges the code and stores the tokens; by default they are kept in `localStorage` and refreshed automatically (refresh tokens rotate on every use).

Open your app, click **Sign in**, and sign in to the sandbox with any email and any password.

## 4. Call your API

Tell 9in about your API, so access tokens can be issued for it:

1. In the console, go to **APIs & Scopes → Create API**. Set the identifier to your API's URL, e.g. `https://api.acme.com`, and add a scope such as `orders:read`.
2. Request tokens for it in the browser:

```ts
const auth = new NineAuth({
  issuer: "https://acme-sandbox.9in.io",
  clientId: "app_7f3k2m9q",
  redirectUri: `${location.origin}/callback`,
  audience: "https://api.acme.com",
  scope: "openid profile email offline_access orders:read",
});

const res = await auth.fetch("https://api.acme.com/orders");   // adds Authorization: Bearer <access token>
```

3. Verify the token in your API (Node, Workers, Deno or Bun):

```ts
import { verifyAccessToken } from "@9in-io/sdk/server";

export async function handle(request: Request) {
  const bearer = request.headers.get("authorization")?.replace(/^Bearer /i, "");
  const claims = await verifyAccessToken(bearer, {
    issuer: "https://acme-sandbox.9in.io",
    audience: "https://api.acme.com",
    scope: "orders:read",
  }); // throws NineError (invalid_token, insufficient_scope) otherwise
  return Response.json({ orders: await listOrders(claims.sub) });
}
```

`verifyAccessToken` checks the signature against your tenant's public keys (cached), the issuer, audience, expiry and scopes. To also notice revoked tokens within seconds, see [Protecting your API](https://9in.io/docs/protecting-apis/).

## 5. Go to production

- Add your real URLs to the application (callback, logout and web origin).
- Switch the issuer to `https://acme.9in.io`, or to your [custom domain](https://9in.io/docs/custom-domains/).
- Users, sessions and activity in production are separate from the sandbox.

## Next

- Turn on more [sign-in methods](https://9in.io/docs/sign-in-methods/), [social login](https://9in.io/docs/social-login/) or [enterprise SSO](https://9in.io/docs/enterprise-sso/).
- Make the pages look like your product with [themes](https://9in.io/docs/themes/).
- Get notified about sign-ups with [webhooks](https://9in.io/docs/webhooks/).
