Select the types of activity you want to include in your feed.
Stateless auth proxy that converts AT Protocol native apps from public to confidential OAuth clients. Deploy once, get 180-day refresh tokens instead of 24-hour ones.
···123123 "code_challenge": "<pkce_challenge>",
124124 "code_challenge_method": "S256",
125125 "state": "<state>",
126126- "redirect_uri": "yourapp://oauth/callback"
126126+ "redirect_uri": "https://yourapp.com/oauth/callback"
127127}
128128```
129129···156156| Refresh token lifetime | 24 hours | 180 days |
157157| Session lifetime | 7 days max | Unlimited |
158158| User re-login frequency | Every 1-7 days | Only when user chooses |
159159+160160+## HTTPS Redirect URIs (Required for iOS)
161161+162162+Bluesky's auth server enforces that `application_type: "native"` clients must use `token_endpoint_auth_method: "none"`. This means **you cannot use the proxy with `application_type: "native"`** — the PAR request will fail with:
163163+164164+```
165165+{"error":"invalid_client_metadata","error_description":"Native clients must authenticate using \"none\" method"}
166166+```
167167+168168+To use `private_key_jwt`, your client metadata must declare `application_type: "web"`, which requires HTTPS redirect URIs instead of custom URL schemes.
169169+170170+The AT Protocol OAuth spec explicitly supports this: native clients are allowed to use an HTTPS URL as long as the URL origin matches the `client_id`. For example, if your `client_id` is `https://yourapp.com/oauth/client-metadata.json`, your redirect URI must be `https://yourapp.com/...`.
171171+172172+Your client metadata should look like:
173173+174174+```json
175175+{
176176+ "client_id": "https://yourapp.com/oauth/client-metadata.json",
177177+ "redirect_uris": ["https://yourapp.com/oauth/callback"],
178178+ "application_type": "web",
179179+ "token_endpoint_auth_method": "private_key_jwt",
180180+ "token_endpoint_auth_signing_alg": "ES256",
181181+ "dpop_bound_access_tokens": true,
182182+ "jwks_uri": "https://auth.yourapp.com/.well-known/jwks.json"
183183+}
184184+```
185185+186186+### iOS Setup
187187+188188+On iOS, HTTPS OAuth callbacks are handled via `ASWebAuthenticationSession`'s HTTPS callback API (iOS 17.4+). You also need an Apple App Site Association (AASA) file for Universal Links as a safety net.
189189+190190+#### 1. Apple App Site Association File
191191+192192+Serve this at `https://yourapp.com/.well-known/apple-app-site-association`:
193193+194194+```json
195195+{
196196+ "applinks": {
197197+ "details": [
198198+ {
199199+ "appIDs": ["<TEAM_ID>.<BUNDLE_ID>"],
200200+ "components": [
201201+ {
202202+ "/": "/oauth/callback",
203203+ "comment": "AT Protocol OAuth callback"
204204+ }
205205+ ]
206206+ }
207207+ ]
208208+ },
209209+ "webcredentials": {
210210+ "apps": ["<TEAM_ID>.<BUNDLE_ID>"]
211211+ }
212212+}
213213+```
214214+215215+Replace `<TEAM_ID>` with your Apple Developer Team ID and `<BUNDLE_ID>` with your app's bundle identifier.
216216+217217+Requirements:
218218+- Must be served with `Content-Type: application/json`
219219+- Must **not** redirect — Apple's CDN fetches it directly
220220+- Apple caches the file via its CDN; updates can take hours to propagate
221221+- Verify propagation: `https://app-site-association.cdn-apple.com/a/v1/yourapp.com`
222222+223223+#### 2. Entitlements
224224+225225+Add an Associated Domains entitlement to your app (via Xcode: target > Signing & Capabilities > Associated Domains, or via an `.entitlements` file):
226226+227227+```xml
228228+<key>com.apple.developer.associated-domains</key>
229229+<array>
230230+ <string>applinks:yourapp.com</string>
231231+ <string>webcredentials:yourapp.com</string>
232232+</array>
233233+```
234234+235235+#### 3. ASWebAuthenticationSession
236236+237237+Use the HTTPS callback initializer instead of the custom-scheme one:
238238+239239+```swift
240240+// Before (custom scheme — won't work with the proxy)
241241+let session = ASWebAuthenticationSession(
242242+ url: authURL,
243243+ callbackURLScheme: "yourapp"
244244+) { callbackURL, error in ... }
245245+246246+// After (HTTPS callback — required for the proxy)
247247+let session = ASWebAuthenticationSession(
248248+ url: authURL,
249249+ callback: .https(host: "yourapp.com", path: "/oauth/callback")
250250+) { callbackURL, error in ... }
251251+```
252252+253253+This requires iOS 17.4+.
254254+255255+#### 4. Remove Custom URL Scheme
256256+257257+If you were previously using a custom URL scheme (e.g., `yourapp://oauth/callback`) for OAuth, remove the `CFBundleURLTypes` entry from your `Info.plist`. The custom scheme is no longer needed for OAuth callbacks.
258258+259259+#### 5. Web Fallback Page
260260+261261+Add a simple page at `/oauth/callback` on your website. `ASWebAuthenticationSession` intercepts the redirect before it reaches the server, but having a real page there improves the experience for edge cases (e.g., users who land there in a browser):
262262+263263+```html
264264+<h1>Redirecting to YourApp...</h1>
265265+<p>If you're not redirected automatically, open the app on your device.</p>
266266+```
267267+268268+### Deployment Sequence
269269+270270+Order matters — deploy bottom-up so each layer is ready before the one above depends on it:
271271+272272+1. Deploy the auth proxy and verify it's live (`curl https://auth.yourapp.com/health`)
273273+2. Deploy the AASA file to your website
274274+3. **Wait** for Apple CDN propagation — verify via `https://app-site-association.cdn-apple.com/a/v1/yourapp.com`
275275+4. Deploy updated client metadata (`application_type: "web"`, HTTPS redirect URI)
276276+5. Build and test the iOS app **on a real device** (Universal Links don't work in Simulator)
277277+6. Submit the app update
278278+279279+> **Warning:** Do not deploy the client metadata update before the AASA file is cached by Apple's CDN. If it isn't, Universal Links won't work and the OAuth callback won't route to your app.
280280+281281+### Rollback
282282+283283+If something goes wrong, revert your `client-metadata.json`:
284284+- `application_type`: `"web"` back to `"native"`
285285+- `redirect_uris`: HTTPS URL back to custom scheme
286286+- `token_endpoint_auth_method`: `"private_key_jwt"` back to `"none"`
287287+- Remove `jwks_uri` and `token_endpoint_auth_signing_alg`
288288+289289+The iOS app also needs a new build to switch back to `callbackURLScheme`. To minimize rollback friction, keep the AASA file deployed permanently — it has no downside even when unused.
159290160291## Key Rotation
161292