+9
-1
Package.swift
+9
-1
Package.swift
···
+6
-1
Sources/CoreATProtocol/APEnvironment.swift
+6
-1
Sources/CoreATProtocol/APEnvironment.swift
·········
+28
Sources/CoreATProtocol/CoreATProtocol.swift
+28
Sources/CoreATProtocol/CoreATProtocol.swift
······+public func applyAuthenticationContext(login: Login, generator: @escaping DPoPSigner.JWTGenerator, resourceNonce: String? = nil) {
+83
Sources/CoreATProtocol/DPoPJWTGenerator.swift
+83
Sources/CoreATProtocol/DPoPJWTGenerator.swift
···+private static func makeJWKHeader(from key: ES256PrivateKey) throws -> [String: JWTHeaderField] {
+11
Sources/CoreATProtocol/Extensions/Data+Base64URL.swift
+11
Sources/CoreATProtocol/Extensions/Data+Base64URL.swift
···
+35
-4
Sources/CoreATProtocol/Networking.swift
+35
-4
Sources/CoreATProtocol/Networking.swift
·········
+1
-1
Sources/CoreATProtocol/Networking/Services/NetworkRouter.swift
+1
-1
Sources/CoreATProtocol/Networking/Services/NetworkRouter.swift
+157
Documentation/CoreATProtocol.docc/BuildBlueskyLogin.md
+157
Documentation/CoreATProtocol.docc/BuildBlueskyLogin.md
···+Learn how an iOS app can depend on ``CoreATProtocol`` and guide a user through the AT Protocol OAuth flow using Bluesky as the authorization server.+Bluesky issues DPoP-bound access tokens, so the app must generate and persist a single ES256 key pair. The example below stores the private key in the Keychain and recreates it when needed.+Pass ``DPoPJWTGenerator.jwtGenerator()`` to ``LoginService`` and later to ``applyAuthenticationContext(login:generator:resourceNonce:)`` so API calls share the same key material.+Provide a ``LoginStorage`` implementation that reads and writes the user’s Bluesky session securely. The storage runs on the calling actor, so use async APIs.+3. Start the Bluesky OAuth flow. Use the client metadata URL registered with the Authorization Server (for example, the one served from your app’s hosted metadata file).+4. Share the authentication context with CoreATProtocol so the networking layer can add DPoP proofs automatically:+5. When Bluesky returns a new DPoP nonce (`DPoP-Nonce` header), call ``updateResourceDPoPNonce(_:)`` with the latest value before the next request.+6. To sign the user out, call ``clearAuthenticationContext()`` and erase any stored login and keychain items.+Attach the package’s router delegate to your networking stack (for example, the client that wraps ``URLSession``) so that access tokens and DPoP proofs are injected into outgoing requests.+With the context applied, subsequent calls through ``APRouterDelegate`` will refresh DPoP proofs, hash access tokens into the `ath` claim, and keep the nonce in sync with the server.+- Ensure the DPoP key persists across app launches. If the key changes, all tokens issued by Bluesky become invalid and the user must reauthenticate.+- Always call ``applyAuthenticationContext(login:generator:resourceNonce:)`` after refreshing tokens via ``updateTokens(access:refresh:)`` or custom flows so the delegate has current credentials.+- If Bluesky rejects requests with `use_dpop_nonce`, update the cached value via ``updateResourceDPoPNonce(_:)`` and retry.