Server
Use the verifier helper
import {
fetchAtmosphereLoginPublicJwkForToken,
verifyAtmosphereLoginCallback,
} from "https://login.atmosphereaccount.com/atmosphere-login-server.js";
const callbackUrl = new URL(request.url);
const selectionToken = callbackUrl.searchParams.get("selection_token");
if (!selectionToken) throw new Error("Missing selection token");
const publicJwk = await fetchAtmosphereLoginPublicJwkForToken(
selectionToken,
"https://login.atmosphereaccount.com",
);
const result = await verifyAtmosphereLoginCallback({
url: callbackUrl,
publicJwk,
expectedIssuer: "https://login.atmosphereaccount.com",
expectedClientId: "https://app.example.com/oauth/client-metadata.json",
expectedState,
expectedReturnUri: "https://app.example.com/auth/atmosphere/selected",
replayStore,
});
if (!result.ok) throw new Error(result.error);
const loginHint = result.claims.handle || result.claims.sub;Claims to bind
| Claim | Required check |
|---|---|
| iss | Expected Atmosphere deployment. |
| aud | Your registered client_id. |
| sub | Selected DID; use as durable account identity. |
| handle | Display/login hint only; handles can change. |
| return_uri | Exact callback receiving the token. |
| state | Fresh nonce from the initiating request. |
| exp / iat | Short token lifetime and no future-issued token. |
| jti | Reject replay until expiry. |
Selection is not authorization
The selection token proves the user chose an account in Atmosphere. It does not give your app repository access, blob upload access, or a PDS session.