---
title: Your first login
description: Configure an application and complete one verified login with the
  Sudomimus Connect API.
editUrl: true
head: []
template: doc
sidebar:
  order: 3
  hidden: false
  attrs: {}
pagefind: true
draft: false
---

import { CardGrid, LinkButton, LinkCard } from "@astrojs/starlight/components";

This tutorial takes one web application from portal setup through its first completed login. At the end, your backend will hold an access token and refresh token for a user who authenticated through Sudomimus.

:::tip[Recommended: use an official SDK]
For a supported web framework, use a [framework SDK](/en-us/frameworks/overview/) to handle the browser login round-trip. For direct API integration, install [`@sudomimus/connect`](/en-us/sdk/typescript/) and call typed `establish` and `redeem` methods instead of building raw HTTP requests by hand. Use a standards-based JOSE implementation to verify access tokens, and use the Session API for refresh after tokens are issued.

<LinkButton href="/en-us/sdk/overview/" variant="primary" icon="right-arrow">Choose an SDK</LinkButton>
:::

:::note[Using an AI coding assistant?]
Use the Sudomimus CLI when an assistant needs to operate Sudomimus directly. It exposes shell commands and JSON output while keeping login in your browser through the device authorization flow.

<LinkButton href="/en-us/ai/cli/" variant="secondary" icon="right-arrow">Use the CLI with AI</LinkButton>
:::

Not building a web app? Pick the right guide:

<CardGrid>
<LinkCard
    title="Sudomimus CLI"
    description="Shell-friendly account and developer operations for AI agents, local automation, and source checkouts."
    href="/en-us/ai/cli/"
/>
<LinkCard
    title="Native clients"
    description="Desktop apps, games via Steam, and CLI tools — including the browser Errand for consent and profile completion."
    href="/en-us/native/overview/"
/>
<LinkCard
    title="OIDC relying parties"
    description="Standard OpenID Connect — authorization_code + PKCE."
    href="/en-us/oidc/flow/"
/>
</CardGrid>

## Prerequisites

1. **Create or join an organization at [`with.sudomimus.com`](https://with.sudomimus.com)**. The developer self-serve portal is organization-based: applications and sectors live inside an organization, so you need one before you can create an application. Most accounts create their first organization on the spot (the form pre-fills a suggested name); if a teammate already runs one, have them invite you instead. The `/applications` and `/sectors` pages redirect to `/organizations` until you belong to one.

2. **Create your application** inside that organization. When you create the application you receive:
   - The **`applicationAnchor`** — a stable, lowercase-kebab identifier (e.g. `my-app`), the public name of your application across the API.
   - The **client-auth private key** — shown **once** at creation, used to sign `/establish` requests. Store it like any production secret.
   - A per-application **Session JWKS URL** at `https://session-api.sudomimus.com/applications/{applicationAnchor}/jwks.json`, used to verify access and refresh tokens by `kid`. The portal's Signing keys tab manages rotation; application creation does not return a one-off signing PEM.

3. **Add at least one [Return Rule](/en-us/application-rules/return-rules/)** of type `CALLBACK`, listing the hostnames you will redirect users back to. The concrete `callbackUrl` is supplied per inquiry on `/establish`; the rule just gates which hostnames are allowed.

4. **Add at least one [Authentication Rule](/en-us/application-rules/authentication-rules/)** (e.g. `PASSKEY_USERNAMELESS`, `PASSKEY_REASONED`, or `EMAIL_VERIFICATION`) and one [Realize Rule](/en-us/application-rules/realize-rules/) (e.g. `EMAIL` with `allowedEmails: ["*"]` for a public sign-up). Rules are **allowlist-only with default-deny** — an application with zero rules in any layer cannot be used.

5. **Take the application live.** New applications start in `DRAFT`. Follow [Take an application live](/en-us/with-portal/application-lifecycle/) for the authoritative readiness, activation, disablement, and reactivation contract.

## The four phases

Every authentication round-trip through Connect has three phases, followed by the shared Session API refresh phase:

1. **Establish** — your application backend asks Connect to start an authentication session and gets back a session reference (`exposureKey` + `hiddenKey`).
2. **Authenticate** — your application sends the user to `via.sudomimus.com` with the `exposureKey`; the user completes a passkey or email-OTP challenge there.
3. **Redeem** — once `via.sudomimus.com` hands control back via your callback URL (with `exposure-key` + `confirmation-key` in the query string), your backend exchanges the three keys at Connect for a signed access token and refresh token.
4. **Refresh** — your backend calls Session API to exchange the refresh token for a fresh access token whenever the current one nears expiry.

See [the Connect flow](/en-us/connect/flow/) for the full request shapes and how Connect, `via.sudomimus.com`, and your application interact.

## Run your first login

The examples below are a framework-neutral backend skeleton. Replace `pendingSessions` and the response helpers with your framework's server-side session store and HTTP primitives. Never put the client-auth private key or `hiddenKey` in browser code.

### 1. Install and configure the client

```bash
pnpm add @sudomimus/connect
```

Set these values only in your backend environment:

```dotenv
SUDOMIMUS_APPLICATION_ANCHOR=your-application
SUDOMIMUS_CLIENT_AUTH_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
SUDOMIMUS_CALLBACK_URL=https://your-app.com/auth/callback
```

The callback hostname must match the `CALLBACK` Return Rule you configured in the portal.

### 2. Start login from a backend route

```ts
import { ConnectClient, RETURN_METHOD } from "@sudomimus/connect";

const applicationAnchor = process.env.SUDOMIMUS_APPLICATION_ANCHOR!;
const client = new ConnectClient({
    clientAuth: {
        applicationAnchor,
        privateKeyPem: process.env.SUDOMIMUS_CLIENT_AUTH_PRIVATE_KEY!,
    },
});

const inquiry = await client.establish({
    applicationAnchor,
    returnMethods: [{
        type: RETURN_METHOD.CALLBACK,
        payload: { callbackUrl: process.env.SUDOMIMUS_CALLBACK_URL! },
    }],
});

await pendingSessions.save(inquiry.exposureKey, inquiry.hiddenKey);

const hostedLogin = new URL("https://via.sudomimus.com/");
hostedLogin.searchParams.set("exposure-key", inquiry.exposureKey);
return redirect(hostedLogin.toString(), 302);
```

Open this route in a browser. Sudomimus shows one of the authentication methods allowed by your Layer 1 rules. Complete the challenge with a test account.

### 3. Redeem the callback on your backend

After authentication, Sudomimus redirects the browser to your callback with `exposure-key` and `confirmation-key` query parameters:

```ts
const callbackUrl = new URL(request.url);
const exposureKey = callbackUrl.searchParams.get("exposure-key");
const confirmationKey = callbackUrl.searchParams.get("confirmation-key");

if (exposureKey === null || confirmationKey === null) {
    throw new Error("Missing Sudomimus callback keys");
}

const hiddenKey = await pendingSessions.take(exposureKey);
const tokens = await client.redeem({
    exposureKey,
    hiddenKey,
    confirmationKey,
});
```

`take` should atomically consume the pending server-side value so a callback cannot reuse it. A successful redeem returns `accessToken` and `refreshToken`.

### 4. Verify the result

Verify `tokens.accessToken` against the application's Session JWKS, including its signature, `kid`, `typ`, issuer, audience, expiry, and actor shape. Then read the application-visible user identifier from `sub`. Follow the complete [token verification procedure](/en-us/concepts/tokens-and-verification/#verification); do not treat decoding alone as verification.

Persist the refresh token in protected server-side storage, then follow [Managing sessions](/en-us/guides/managing-sessions/) to rotate it. Your first-login path is complete when the callback redeems once, verification succeeds, and your application establishes its own local session without exposing either token to logs or URLs.

## Next steps

<CardGrid>
<LinkCard
    title="Connect flow"
    description="End-to-end curl, Node.js, Python, and Go examples for the Connect API."
    href="/en-us/connect/flow/"
/>
<LinkCard
    title="The three-key model"
    description="How a single login proves itself across exposureKey, hiddenKey, and confirmationKey."
    href="/en-us/connect/three-key-model/"
/>
<LinkCard
    title="Managing sessions"
    description="Refresh, introspect, logout, and revoke-all — the lifecycle after the initial login."
    href="/en-us/guides/managing-sessions/"
/>
<LinkCard
    title="Sudomimus CLI"
    description="A command-line tool for AI assistants and local automation."
    href="/en-us/ai/cli/"
/>
</CardGrid>