Debug
Common errors
| Symptom | Likely cause | Fix |
|---|---|---|
| Invalid return URI | The callback does not exactly match the registered URI. | Copy the generated picker URL from the app detail page and add the exact callback. |
| State mismatch | The callback does not match the initiating request. | Use a fresh random state per attempt and bind it to the browser/session. |
| Selection token failed verification | Issuer, audience, signature, expiry, return URI, or state mismatch. | Log the verifier error and compare against registered app metadata. |
| Replayed token | The same jti was already used. | Store jti until expiry and restart the picker flow. |
| Popup blocked | The browser blocked a non-user-initiated popup or third-party flow. | Use redirect mode unless you have a strong popup reason. If you use popup mode, open it directly from a user click, keep the callback origin identical to the registered return URI origin, and avoid strict Cross-Origin-Opener-Policy: same-origin on pages that need the popup handoff. |
| No saved accounts shown | The user has not used Atmosphere Login in this browser. | Show add-account/sign-in in the picker and let the user continue normally. |
| OAuth login hint ignored | The PDS/entryway may require its own account selection or identity resolution. | Still verify the final OAuth sub DID matches the selected account. |
Localhost and 127.0.0.1
These names show up in different parts of the ATProto OAuth development story.
| Value | Where it belongs | Why |
|---|---|---|
| http://localhost/ | Development client_id shortcut | ATProto OAuth defines this virtual client metadata exception so developers do not need to publish metadata while building. |
| http://127.0.0.1:<port>/callback | Local return URI / OAuth redirect URI | Loopback IP callbacks avoid ambiguous hostnames and match common OAuth local-app behavior. |
| https://app.example.com/callback | Production return URI | Production apps should use exact HTTPS callback URLs. |
Developers still need local docs
Even if an app ultimately runs in production, developers usually test the picker and OAuth callback on a local server first. The docs separate local integration rules from production registration rules.