---
title: The three-key model
description: How Sudomimus proves a redemption is legitimate by splitting
  per-session proof across three keys — exposureKey, hiddenKey, and
  confirmationKey — each held by a different party.
editUrl: true
head: []
template: doc
sidebar:
  order: 2
  hidden: false
  attrs: {}
pagefind: true
draft: false
---

The three-key model is the heart of Sudomimus's session security for the browser-mediated flow. Every authentication round-trip through Connect mints **three distinct keys** at different moments, held by different parties, defending against different classes of attack. A successful `/redeem` must present all three.

:::note[Applies to the Connect browser flow]
The three-key model is specific to the **Connect** lifecycle — the four-phase flow described in [the Connect flow](/en-us/connect/flow/). Native one-shot flows (`/direct-issue/steam-ticket`, `/direct-issue/access-key`) bypass it: the credential they present *is* the proof. OIDC uses its own authorization-code + PKCE shape. This page describes the shape that backs the Connect flow.
:::

This is not about the long-lived material Sudomimus uses to sign tokens (covered in [Tokens and verification](/en-us/concepts/tokens-and-verification/)). It is about how a single browser-mediated login proves itself.

## The three keys

| Key | Created by | Held by | Visible to |
|---|---|---|---|
| **exposureKey** | `/establish` | Application backend, then the browser | Browser, URL, `via.sudomimus.com` |
| **hiddenKey** | `/establish` | Application backend only | Backend |
| **confirmationKey** | `via.sudomimus.com`, after the user authenticates | Application backend (via callback) | Browser, URL |

`exposureKey` and `hiddenKey` are issued together as a **pair** by the same `/establish` call. They belong to one session, and only that session.

Each key carries a role-specific prefix so a misplaced key can be rejected at the request boundary:

- `exp_` + 32 lowercase hex characters
- `hid_` + 32 lowercase hex characters
- `cnf_` + 32 lowercase hex characters

## Why three keys, and not one

If a single opaque session reference were enough to redeem a token, anyone who saw that reference could redeem on the user's behalf. Splitting the proof three ways means an attacker has to compromise **three different vantage points at the same time**:

- **Without `hiddenKey`** — an attacker who steals the URL the user is visiting still cannot redeem. The hidden key never leaves your server.
- **Without `exposureKey`** — an attacker who breaches your server still cannot redeem someone else's session, because each pending session's exposure key is bound to the specific browser it was sent to.
- **Without `confirmationKey`** — neither party can redeem a session the user never actually completed. The confirmation key is only minted by `via.sudomimus.com` after a real passkey or OTP challenge succeeds.

## Lifecycle

```
/establish ────► exposureKey + hiddenKey
                      │              │
                      ▼              ▼
                 to browser    stay on backend
                      │
                      ▼
                  via.sudomimus.com ──► user passes challenge
                      │
                      ▼
                 confirmationKey ──► to your callback
                      │
                      ▼
/redeem ◄──── exposureKey + hiddenKey + confirmationKey
                      │
                      ▼
                accessToken + refreshToken
```

After `/redeem` succeeds, all three keys are consumed and cannot be reused. A second redemption with the same triple fails — even if the application repeats the request.

## Comparison with OAuth 2.0

If you are familiar with the OAuth authorization-code flow, the rough analogues are:

| Sudomimus | OAuth 2.0 |
|---|---|
| `exposureKey` | `state` parameter (loosely) |
| `confirmationKey` | authorization `code` |
| `hiddenKey` | *no direct equivalent* |

OAuth relies on the long-lived `client_secret` to authenticate the token exchange. Sudomimus instead uses a per-session `hiddenKey` that is fresh on every `/establish` call. Leaking one session's hidden key compromises that single redemption — not every redemption your application ever performs.

This is the same idea as proof-of-possession tokens: prefer short-lived, narrowly-scoped secrets over long-lived shared ones.

If you actually want OAuth/OIDC semantics, Sudomimus also runs a [standard OIDC provider](/en-us/oidc/flow/) at `oidc.sudomimus.com`. The three-key model is what powers the Connect protocol underneath; relying parties using OIDC do not see it directly.

## The long-lived keypairs

Aside from the three per-session keys, each application has two RSA-2048 keypairs that *are* long-lived:

- **Token-signing keypair** — Sudomimus signs access and refresh JWTs with its private half; the application verifies signatures with the public half (fetched from `POST /info`).
- **Client-auth keypair** — the application signs every `/establish` request with its private half (delivered once at application creation and at each rotation); Sudomimus verifies with the stored public half.

Neither of these is part of the "three-key" model. They authenticate the *channel* between Sudomimus and the application; the three per-session keys authenticate a *single login*. The details are in [Tokens and verification](/en-us/concepts/tokens-and-verification/).