Back to docs
Example relying app
Login with Atmosphere
This is the canonical copy-paste reference: the app asks Login with 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.
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-state="3Fk7RD9baqbT-NF5SL9CAmt-XG8CoK_C"
data-scope="atproto"
data-app-name="Login with Atmosphere 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-state="3Fk7RD9baqbT-NF5SL9CAmt-XG8CoK_C"
data-scope="atproto"
data-app-name="Login with Atmosphere 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);