AtmosphereAtmosphere
HostsApps
Sign in
Back to docs

Example relying app

Continue with Atmosphere

This is the canonical copy-paste reference: the app asks Atmosphere to choose an account, verifies the signed selection on callback, starts its own AT Protocol OAuth flow, then creates an app-owned signed-in session.

Reference appLocal example using this deployment

Popup mode keeps this page open and finishes through the same server-verified callback.

The picker returns to the reference callback. When the token checks pass, the callback redirects into the app's ATProto OAuth start route.

After callback

Complete app-owned ATProto OAuth

The example uses the selected `handle` or `sub` DID as the login hint, exchanges the OAuth code on its own callback, and then shows the final signed-in app state.

View verifier pageOAuth metadata
Redirect button
<button
  data-atmosphere-login
  data-client-id="https://atmosphereaccount.com/examples/atmosphere-login/client-metadata.json"
  data-return-uri="https://atmosphereaccount.com/examples/atmosphere-login/callback"
  data-scope="atproto"
  data-app-name="Atmosphere Login reference app"
  data-app-homepage="https://atmosphereaccount.com/examples/atmosphere-login/app"
></button>
<script src="https://login.atmosphereaccount.com/atmosphere-login.js" defer></script>
Popup button
<button
  data-atmosphere-login
  data-client-id="https://atmosphereaccount.com/examples/atmosphere-login/client-metadata.json"
  data-return-uri="https://atmosphereaccount.com/examples/atmosphere-login/callback?mode=popup"
  data-scope="atproto"
  data-app-name="Atmosphere Login reference app"
  data-app-homepage="https://atmosphereaccount.com/examples/atmosphere-login/app"
  data-mode="popup"
></button>
<script src="https://login.atmosphereaccount.com/atmosphere-login.js" defer></script>
Selection callback
const selection = AtmosphereLogin.consumeSelection({
  clientId: "https://atmosphereaccount.com/examples/atmosphere-login/client-metadata.json",
});

if (!selection) return showSignedOut();

const verified = await verifyAtmosphereLoginCallback({
  url: request.url,
  publicJwk,
  expectedIssuer: "https://login.atmosphereaccount.com",
  expectedClientId: "https://atmosphereaccount.com/examples/atmosphere-login/client-metadata.json",
  expectedReturnUri: "https://atmosphereaccount.com/examples/atmosphere-login/callback",
  expectedState: selection.state,
  replayStore,
});

if (!verified.ok) throw new Error(verified.error);

return redirect("/examples/atmosphere-login/oauth/start?" + new URLSearchParams({
  handle: verified.claims.handle,
  did: verified.claims.sub,
}));
ATProto OAuth handoff
// /examples/atmosphere-login/oauth/start
await startAtprotoOAuth({
  clientId: "https://atmosphereaccount.com/examples/atmosphere-login/oauth/client-metadata.json",
  redirectUri: "https://atmosphereaccount.com/examples/atmosphere-login/oauth/callback",
  scope: "atproto",
  loginHint: verified.claims.handle || verified.claims.sub,
});

// /examples/atmosphere-login/oauth/callback
const account = await completeAtprotoOAuthCallback(request.url);
return createAppSession(account);
Atmosphere
AT ProtocolDocs
Back to top