···56let package = Package(
7 name: "Gulliver",
00000008 products: [
9 // Products define the executables and libraries a package produces, making them visible to other packages.
10 .library(
···12 targets: ["Gulliver"]
13 ),
14 ],
00000015 targets: [
16 // Targets are the basic building blocks of a package, defining a module or a test suite.
17 // Targets can depend on other targets in this package and products from dependencies.
18 .target(
19- name: "Gulliver"
00000020 ),
21-22 ]
23)
···56let package = Package(
7 name: "Gulliver",
8+ platforms: [
9+ .iOS(.v14),
10+ .macOS(.v13),
11+ .tvOS(.v14),
12+ .visionOS(.v1),
13+ .watchOS(.v9)
14+ ],
15 products: [
16 // Products define the executables and libraries a package produces, making them visible to other packages.
17 .library(
···19 targets: ["Gulliver"]
20 ),
21 ],
22+ dependencies: [
23+ .package(url: "https://github.com/fatfingers23/ATIdentityTools.git", branch: "main"),
24+// .package(path: "../ATIdentityTools"),
25+ .package(url: "https://github.com/hyperoslo/Cache.git", .upToNextMajor(from: "7.4.0")),
26+ .package(url: "https://github.com/airsidemobile/JOSESwift.git", from: "3.0.0")
27+ ],
28 targets: [
29 // Targets are the basic building blocks of a package, defining a module or a test suite.
30 // Targets can depend on other targets in this package and products from dependencies.
31 .target(
32+ name: "Gulliver",
33+ dependencies: [
34+ .product(name: "ATIdentityTools", package: "atidentitytools"),
35+ .product(name: "Cache", package: "Cache"),
36+// "jose-swift"
37+ "JOSESwift"
38+ ]
39 ),
40+41 ]
42)
···1+//
2+// dpop.swift
3+// Gulliver
4+//
5+// Created by Bailey Townsend on 1/20/26.
6+//
7+8+import Foundation
9+import CryptoKit
10+import JOSESwift
11+12+13+/// DPoP Signer for creating dpop+jwt proofs with P-256 keys
14+public struct DPoPSigner {
15+ private let privateKey: P256.Signing.PrivateKey
16+ /// This can either be the state or session keychain depending if it's doing par or not
17+ private let keychainStore: KeychainStorage
18+19+20+ public init(privateKey: P256.Signing.PrivateKey, keychainStore: KeychainStorage) {
21+ self.privateKey = privateKey
22+ self.keychainStore = keychainStore
23+ }
24+25+ //TODO
26+ // Save state/session metadata is saved to store via store methods, but dpop keys are stored by key thing
27+ // the dpop keys are saved by sessionid or
28+ // Also need to check on those save and delete cause they dont follow the namespace?
29+30+31+ /// Session id is either the state key or the session key
32+ public func saveCurrentDpopKey(sessionId: String) throws {
33+ try keychainStore.storeDPoPKey(self.privateKey, keyTag: sessionId )
34+ }
35+36+ /// Session id is either the state key or the session key
37+38+ public func deleteCurrentDpopKey(sessionId: String) throws {
39+ try keychainStore.deleteDPoPKey(keyTag: sessionId)
40+ }
41+42+ /// Creates a DPoP proof JWT
43+ /// - Parameters:
44+ /// - httpMethod: The HTTP method of the request (e.g., "POST", "GET")
45+ /// - url: The URL of the request (query and fragment will be stripped)
46+ /// - accessToken: Optional access token for resource access (will be hashed as 'ath' claim)
47+ /// - nonce: Optional server-provided nonce
48+ /// - Returns: A DPoP proof JWT string
49+ public func createProof(
50+ httpMethod: String,
51+ url: String,
52+ accessToken: String? = nil,
53+ nonce: String? = nil
54+ ) throws -> String {
55+ // Build the JWK for the public key
56+ let jwkDict = try createJWKDictionary()
57+58+ // Build header
59+ let headerDict: [String: Any] = [
60+ "typ": "dpop+jwt",
61+ "alg": "ES256",
62+ "jwk": jwkDict
63+ ]
64+65+ // Build claims
66+ let jti = UUID().uuidString
67+ let iat = Int(Date().timeIntervalSince1970)
68+ let htu = sanitizeURL(url)
69+70+ var claimsDict: [String: Any] = [
71+ "jti": jti,
72+ "htm": httpMethod.uppercased(),
73+ "htu": htu,
74+ "iat": iat
75+ ]
76+77+ // Add nonce if provided
78+ if let nonce = nonce {
79+ claimsDict["nonce"] = nonce
80+ }
81+82+ // Add access token hash if provided (for resource access)
83+ if let accessToken = accessToken {
84+ let ath = computeAccessTokenHash(accessToken)
85+ claimsDict["ath"] = ath
86+ }
87+88+ // Create the JWT
89+ let headerData = try JSONSerialization.data(withJSONObject: headerDict)
90+ let claimsData = try JSONSerialization.data(withJSONObject: claimsDict)
91+92+ let headerBase64 = base64URLEncode(headerData)
93+ let claimsBase64 = base64URLEncode(claimsData)
94+95+ let signingInput = "\(headerBase64).\(claimsBase64)"
96+97+ guard let signingData = signingInput.data(using: .utf8) else {
98+ throw DPoPError.encodingFailed
99+ }
100+101+ // Sign with ES256 (P-256 + SHA-256)
102+ let signature = try privateKey.signature(for: signingData)
103+ let signatureBase64 = base64URLEncode(signature.rawRepresentation)
104+105+ return "\(signingInput).\(signatureBase64)"
106+ }
107+108+ /// Creates a JWK dictionary representation of the public key
109+ private func createJWKDictionary() throws -> [String: String] {
110+ let publicKey = privateKey.publicKey
111+ let rawRepresentation = publicKey.rawRepresentation
112+113+ // P-256 raw representation is 64 bytes: 32 bytes for x, 32 bytes for y
114+ guard rawRepresentation.count == 64 else {
115+ throw DPoPError.invalidKeyFormat
116+ }
117+118+ let x = rawRepresentation.prefix(32)
119+ let y = rawRepresentation.suffix(32)
120+121+ return [
122+ "kty": "EC",
123+ "crv": "P-256",
124+ "x": base64URLEncode(Data(x)),
125+ "y": base64URLEncode(Data(y))
126+ ]
127+ }
128+129+ /// Sanitizes URL by removing query and fragment
130+ private func sanitizeURL(_ urlString: String) -> String {
131+ guard let url = URL(string: urlString),
132+ var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
133+ return urlString
134+ }
135+ components.query = nil
136+ components.fragment = nil
137+ return components.string ?? urlString
138+ }
139+140+ /// Computes the access token hash (ath) using SHA-256
141+ private func computeAccessTokenHash(_ accessToken: String) -> String {
142+ guard let tokenData = accessToken.data(using: .utf8) else {
143+ return ""
144+ }
145+ let hash = SHA256.hash(data: tokenData)
146+ return base64URLEncode(Data(hash))
147+ }
148+149+150+ /// Returns the public key JWK as a JSON string (useful for client metadata)
151+ public func publicKeyJWK() throws -> String {
152+ let jwkDict = try createJWKDictionary()
153+ let data = try JSONSerialization.data(withJSONObject: jwkDict, options: .sortedKeys)
154+ return String(data: data, encoding: .utf8) ?? ""
155+ }
156+}
157+158+/// Errors that can occur during DPoP operations
159+public enum DPoPError: Error {
160+ case encodingFailed
161+ case invalidKeyFormat
162+ case signingFailed
163+}
164+165+// MARK: - Keychain Storage Helpers
166+167+func createDPoPKey(for sessionId: String) async throws -> P256.Signing.PrivateKey {
168+ let newKey = P256.Signing.PrivateKey()
169+ //Will always want the state store when creating a new key chain. transfer it over during login
170+ let storage = getStateKeychainStore()
171+ try await storage.storeDPoPKey(newKey, keyTag: sessionId)
172+ return newKey
173+}
174+175+176+
+44
Sources/Gulliver/Errors.swift
···00000000000000000000000000000000000000000000
···1+//
2+// File.swift
3+// Gulliver
4+//
5+// Created by Bailey Townsend on 1/20/26.
6+//
7+8+import Foundation
9+10+11+extension OAuthClientError: LocalizedError {
12+ var errorDescription: String? {
13+ switch self {
14+ case .identifierParsingFailed:
15+ return "Failed to parse the identifier as a did, handle, or PDS URL."
16+ case .couldNotResolveADID:
17+ return "Could not resolve a DID for the provided input. Is this a valid handle or DID?"
18+ case .noPdsFound:
19+ return "No PDS endpoint was found for the provided identifier."
20+ case .unknownError(let error):
21+ let error = error as NSError
22+ let message = error.localizedDescription.isEmpty ? "An unknown error occurred." : error.localizedDescription
23+ return message
24+ case .webRequestError(let message):
25+ return message
26+ case .metaDatasError(let message):
27+ return message ?? "An unknown error occurred trying to get or parse the metadata from the resource server."
28+ case .catchAll(let message):
29+ return message
30+ }
31+ }
32+}
33+34+35+enum OAuthClientError: Error {
36+ case identifierParsingFailed
37+ case couldNotResolveADID
38+ case noPdsFound
39+ case unknownError(Error)
40+ case webRequestError(String)
41+ case metaDatasError(String?)
42+ case catchAll(String)
43+44+}
···1+//
2+// File.swift
3+// Gulliver
4+//
5+// Created by Bailey Townsend on 1/20/26.
6+//
7+8+import Foundation
9+10+11+12+/// OAuth 2.0 Client Metadata
13+/// Based on RFC 7591 Section 2 and related specifications
14+struct OAuthClientMetadata: Codable {
15+ /// REQUIRED. Array of redirection URIs for use in redirect-based flows
16+ let redirectUris: [URL]
17+18+ /// OPTIONAL. Array of OAuth 2.0 response_type values that the client will restrict itself to using
19+ let responseTypes: [String]?
20+21+ /// OPTIONAL. Array of OAuth 2.0 grant types that the client will restrict itself to using
22+ let grantTypes: [String]?
23+24+ /// OPTIONAL. String containing a space-separated list of scope values
25+ let scope: String?
26+27+ /// OPTIONAL. Indicator of the requested authentication method for the token endpoint
28+ let tokenEndpointAuthMethod: String?
29+30+ /// OPTIONAL. JWS algorithm that must be used for signing request objects
31+ let tokenEndpointAuthSigningAlg: String?
32+33+ /// OPTIONAL. JWS algorithm required for signing UserInfo Responses
34+ let userinfoSignedResponseAlg: String?
35+36+ /// OPTIONAL. JWE algorithm required for encrypting UserInfo Responses
37+ let userinfoEncryptedResponseAlg: String?
38+39+ /// OPTIONAL. URL string referencing the client's JSON Web Key (JWK) Set document
40+ let jwksUri: URL?
41+42+ /// OPTIONAL. Client's JSON Web Key Set document value
43+ let jwks: [String: Any]?
44+45+ /// OPTIONAL. Kind of application: "web" or "native"
46+ let applicationType: String?
47+48+ /// OPTIONAL. Subject type requested for responses to this client: "public" or "pairwise"
49+ let subjectType: String?
50+51+ /// OPTIONAL. JWS algorithm that must be used for signing Request Objects
52+ let requestObjectSigningAlg: String?
53+54+ /// OPTIONAL. JWS algorithm required for signing the ID Token issued to this client
55+ let idTokenSignedResponseAlg: String?
56+57+ /// OPTIONAL. JWS algorithm required for signing authorization responses
58+ let authorizationSignedResponseAlg: String?
59+60+ /// OPTIONAL. JWE encryption encoding for authorization responses
61+ let authorizationEncryptedResponseEnc: String?
62+63+ /// OPTIONAL. JWE algorithm required for encrypting authorization responses
64+ let authorizationEncryptedResponseAlg: String?
65+66+ /// OPTIONAL. Unique client identifier
67+ let clientId: String?
68+69+ /// OPTIONAL. Human-readable name of the client
70+ let clientName: String?
71+72+ /// OPTIONAL. URL of the home page of the client
73+ let clientUri: URL?
74+75+ /// OPTIONAL. URL that the client provides to the end-user to read about how the profile data will be used
76+ let policyUri: URL?
77+78+ /// OPTIONAL. URL that the client provides to the end-user to read about the client's terms of service
79+ let tosUri: URL?
80+81+ /// OPTIONAL. URL that references a logo for the client application
82+ let logoUri: URL?
83+84+ /// OPTIONAL. Default Maximum Authentication Age in seconds
85+ /// Specifies that the End-User MUST be actively authenticated if the End-User was authenticated
86+ /// longer ago than the specified number of seconds
87+ let defaultMaxAge: Int?
88+89+ /// OPTIONAL. Whether the auth_time Claim in the ID Token is REQUIRED
90+ let requireAuthTime: Bool?
91+92+ /// OPTIONAL. Array of email addresses of people responsible for this client
93+ let contacts: [String]?
94+95+ /// OPTIONAL. Whether TLS client certificate bound access tokens are requested
96+ let tlsClientCertificateBoundAccessTokens: Bool?
97+98+ /// OPTIONAL. Whether DPoP-bound access tokens are requested (RFC 9449 Section 5.2)
99+ let dpopBoundAccessTokens: Bool?
100+101+ /// OPTIONAL. Array of authorization details types supported (RFC 9396 Section 14.5)
102+ let authorizationDetailsTypes: [String]?
103+104+ enum CodingKeys: String, CodingKey {
105+ case redirectUris = "redirect_uris"
106+ case responseTypes = "response_types"
107+ case grantTypes = "grant_types"
108+ case scope
109+ case tokenEndpointAuthMethod = "token_endpoint_auth_method"
110+ case tokenEndpointAuthSigningAlg = "token_endpoint_auth_signing_alg"
111+ case userinfoSignedResponseAlg = "userinfo_signed_response_alg"
112+ case userinfoEncryptedResponseAlg = "userinfo_encrypted_response_alg"
113+ case jwksUri = "jwks_uri"
114+ case jwks
115+ case applicationType = "application_type"
116+ case subjectType = "subject_type"
117+ case requestObjectSigningAlg = "request_object_signing_alg"
118+ case idTokenSignedResponseAlg = "id_token_signed_response_alg"
119+ case authorizationSignedResponseAlg = "authorization_signed_response_alg"
120+ case authorizationEncryptedResponseEnc = "authorization_encrypted_response_enc"
121+ case authorizationEncryptedResponseAlg = "authorization_encrypted_response_alg"
122+ case clientId = "client_id"
123+ case clientName = "client_name"
124+ case clientUri = "client_uri"
125+ case policyUri = "policy_uri"
126+ case tosUri = "tos_uri"
127+ case logoUri = "logo_uri"
128+ case defaultMaxAge = "default_max_age"
129+ case requireAuthTime = "require_auth_time"
130+ case contacts
131+ case tlsClientCertificateBoundAccessTokens = "tls_client_certificate_bound_access_tokens"
132+ case dpopBoundAccessTokens = "dpop_bound_access_tokens"
133+ case authorizationDetailsTypes = "authorization_details_types"
134+ }
135+136+ // Custom decoder to handle the jwks field which can contain arbitrary JSON
137+ init(from decoder: Decoder) throws {
138+ let container = try decoder.container(keyedBy: CodingKeys.self)
139+140+ redirectUris = try container.decode([URL].self, forKey: .redirectUris)
141+ responseTypes = try container.decodeIfPresent([String].self, forKey: .responseTypes)
142+ grantTypes = try container.decodeIfPresent([String].self, forKey: .grantTypes)
143+ scope = try container.decodeIfPresent(String.self, forKey: .scope)
144+ tokenEndpointAuthMethod = try container.decodeIfPresent(String.self, forKey: .tokenEndpointAuthMethod)
145+ tokenEndpointAuthSigningAlg = try container.decodeIfPresent(String.self, forKey: .tokenEndpointAuthSigningAlg)
146+ userinfoSignedResponseAlg = try container.decodeIfPresent(String.self, forKey: .userinfoSignedResponseAlg)
147+ userinfoEncryptedResponseAlg = try container.decodeIfPresent(String.self, forKey: .userinfoEncryptedResponseAlg)
148+ jwksUri = try container.decodeIfPresent(URL.self, forKey: .jwksUri)
149+150+ // Decode jwks as generic dictionary
151+ if let jwksData = try? container.decodeIfPresent(Data.self, forKey: .jwks),
152+ let jwksDict = try? JSONSerialization.jsonObject(with: jwksData) as? [String: Any] {
153+ jwks = jwksDict
154+ } else {
155+ jwks = nil
156+ }
157+158+ applicationType = try container.decodeIfPresent(String.self, forKey: .applicationType)
159+ subjectType = try container.decodeIfPresent(String.self, forKey: .subjectType)
160+ requestObjectSigningAlg = try container.decodeIfPresent(String.self, forKey: .requestObjectSigningAlg)
161+ idTokenSignedResponseAlg = try container.decodeIfPresent(String.self, forKey: .idTokenSignedResponseAlg)
162+ authorizationSignedResponseAlg = try container.decodeIfPresent(String.self, forKey: .authorizationSignedResponseAlg)
163+ authorizationEncryptedResponseEnc = try container.decodeIfPresent(String.self, forKey: .authorizationEncryptedResponseEnc)
164+ authorizationEncryptedResponseAlg = try container.decodeIfPresent(String.self, forKey: .authorizationEncryptedResponseAlg)
165+ clientId = try container.decodeIfPresent(String.self, forKey: .clientId)
166+ clientName = try container.decodeIfPresent(String.self, forKey: .clientName)
167+ clientUri = try container.decodeIfPresent(URL.self, forKey: .clientUri)
168+ policyUri = try container.decodeIfPresent(URL.self, forKey: .policyUri)
169+ tosUri = try container.decodeIfPresent(URL.self, forKey: .tosUri)
170+ logoUri = try container.decodeIfPresent(URL.self, forKey: .logoUri)
171+ defaultMaxAge = try container.decodeIfPresent(Int.self, forKey: .defaultMaxAge)
172+ requireAuthTime = try container.decodeIfPresent(Bool.self, forKey: .requireAuthTime)
173+ contacts = try container.decodeIfPresent([String].self, forKey: .contacts)
174+ tlsClientCertificateBoundAccessTokens = try container.decodeIfPresent(Bool.self, forKey: .tlsClientCertificateBoundAccessTokens)
175+ dpopBoundAccessTokens = try container.decodeIfPresent(Bool.self, forKey: .dpopBoundAccessTokens)
176+ authorizationDetailsTypes = try container.decodeIfPresent([String].self, forKey: .authorizationDetailsTypes)
177+ }
178+179+ // Custom encoder to handle the jwks field
180+ func encode(to encoder: Encoder) throws {
181+ var container = encoder.container(keyedBy: CodingKeys.self)
182+183+ try container.encode(redirectUris, forKey: .redirectUris)
184+ try container.encodeIfPresent(responseTypes, forKey: .responseTypes)
185+ try container.encodeIfPresent(grantTypes, forKey: .grantTypes)
186+ try container.encodeIfPresent(scope, forKey: .scope)
187+ try container.encodeIfPresent(tokenEndpointAuthMethod, forKey: .tokenEndpointAuthMethod)
188+ try container.encodeIfPresent(tokenEndpointAuthSigningAlg, forKey: .tokenEndpointAuthSigningAlg)
189+ try container.encodeIfPresent(userinfoSignedResponseAlg, forKey: .userinfoSignedResponseAlg)
190+ try container.encodeIfPresent(userinfoEncryptedResponseAlg, forKey: .userinfoEncryptedResponseAlg)
191+ try container.encodeIfPresent(jwksUri, forKey: .jwksUri)
192+193+ // Encode jwks as generic dictionary
194+ if let jwks = jwks,
195+ let jwksData = try? JSONSerialization.data(withJSONObject: jwks) {
196+ try container.encode(jwksData, forKey: .jwks)
197+ }
198+199+ try container.encodeIfPresent(applicationType, forKey: .applicationType)
200+ try container.encodeIfPresent(subjectType, forKey: .subjectType)
201+ try container.encodeIfPresent(requestObjectSigningAlg, forKey: .requestObjectSigningAlg)
202+ try container.encodeIfPresent(idTokenSignedResponseAlg, forKey: .idTokenSignedResponseAlg)
203+ try container.encodeIfPresent(authorizationSignedResponseAlg, forKey: .authorizationSignedResponseAlg)
204+ try container.encodeIfPresent(authorizationEncryptedResponseEnc, forKey: .authorizationEncryptedResponseEnc)
205+ try container.encodeIfPresent(authorizationEncryptedResponseAlg, forKey: .authorizationEncryptedResponseAlg)
206+ try container.encodeIfPresent(clientId, forKey: .clientId)
207+ try container.encodeIfPresent(clientName, forKey: .clientName)
208+ try container.encodeIfPresent(clientUri, forKey: .clientUri)
209+ try container.encodeIfPresent(policyUri, forKey: .policyUri)
210+ try container.encodeIfPresent(tosUri, forKey: .tosUri)
211+ try container.encodeIfPresent(logoUri, forKey: .logoUri)
212+ try container.encodeIfPresent(defaultMaxAge, forKey: .defaultMaxAge)
213+ try container.encodeIfPresent(requireAuthTime, forKey: .requireAuthTime)
214+ try container.encodeIfPresent(contacts, forKey: .contacts)
215+ try container.encodeIfPresent(tlsClientCertificateBoundAccessTokens, forKey: .tlsClientCertificateBoundAccessTokens)
216+ try container.encodeIfPresent(dpopBoundAccessTokens, forKey: .dpopBoundAccessTokens)
217+ try container.encodeIfPresent(authorizationDetailsTypes, forKey: .authorizationDetailsTypes)
218+ }
219+}
220+221+/// OAuth 2.0 Authorization Server Metadata
222+/// Based on RFC 8414 and related specifications
223+struct OAuthAuthorizationServerMetadata: Codable {
224+ /// The authorization server's issuer identifier
225+ let issuer: String
226+227+ /// Array of claim types supported
228+ let claimsSupported: [String]?
229+230+ /// Languages and scripts supported for claims
231+ let claimsLocalesSupported: [String]?
232+233+ /// Whether the claims parameter is supported
234+ let claimsParameterSupported: Bool?
235+236+ /// Whether the request parameter is supported
237+ let requestParameterSupported: Bool?
238+239+ /// Whether the request_uri parameter is supported
240+ let requestUriParameterSupported: Bool?
241+242+ /// Whether request_uri values must be pre-registered
243+ let requireRequestUriRegistration: Bool?
244+245+ /// Array of OAuth 2.0 scope values supported
246+ let scopesSupported: [String]?
247+248+ /// Subject identifier types supported
249+ let subjectTypesSupported: [String]?
250+251+ /// Response types supported
252+ let responseTypesSupported: [String]?
253+254+ /// Response modes supported
255+ let responseModesSupported: [String]?
256+257+ /// Grant types supported
258+ let grantTypesSupported: [String]?
259+260+ /// PKCE code challenge methods supported
261+ let codeChallengeMethodsSupported: [String]?
262+263+ /// Languages and scripts supported for UI
264+ let uiLocalesSupported: [String]?
265+266+ /// Algorithms supported for signing ID tokens
267+ let idTokenSigningAlgValuesSupported: [String]?
268+269+ /// Display values supported
270+ let displayValuesSupported: [String]?
271+272+ /// Prompt values supported
273+ let promptValuesSupported: [String]?
274+275+ /// Algorithms supported for signing request objects
276+ let requestObjectSigningAlgValuesSupported: [String]?
277+278+ /// Whether authorization response issuer parameter is supported
279+ let authorizationResponseIssParameterSupported: Bool?
280+281+ /// Authorization details types supported
282+ let authorizationDetailsTypesSupported: [String]?
283+284+ /// Algorithms supported for encrypting request objects
285+ let requestObjectEncryptionAlgValuesSupported: [String]?
286+287+ /// Encryption encodings supported for request objects
288+ let requestObjectEncryptionEncValuesSupported: [String]?
289+290+ /// URL of the authorization server's JWK Set document
291+ let jwksUri: URL?
292+293+ /// URL of the authorization endpoint
294+ let authorizationEndpoint: URL
295+296+ /// URL of the token endpoint
297+ let tokenEndpoint: URL
298+299+ /// Authentication methods supported at token endpoint (RFC 8414 Section 2)
300+ let tokenEndpointAuthMethodsSupported: [String]?
301+302+ /// Signing algorithms supported for token endpoint authentication
303+ let tokenEndpointAuthSigningAlgValuesSupported: [String]?
304+305+ /// URL of the revocation endpoint
306+ let revocationEndpoint: URL?
307+308+ /// Authentication methods supported at revocation endpoint
309+ let revocationEndpointAuthMethodsSupported: [String]?
310+311+ /// Signing algorithms supported for revocation endpoint authentication
312+ let revocationEndpointAuthSigningAlgValuesSupported: [String]?
313+314+ /// URL of the introspection endpoint
315+ let introspectionEndpoint: URL?
316+317+ /// Authentication methods supported at introspection endpoint
318+ let introspectionEndpointAuthMethodsSupported: [String]?
319+320+ /// Signing algorithms supported for introspection endpoint authentication
321+ let introspectionEndpointAuthSigningAlgValuesSupported: [String]?
322+323+ /// URL of the pushed authorization request endpoint
324+ let pushedAuthorizationRequestEndpoint: URL?
325+326+ /// Authentication methods supported at PAR endpoint
327+ let pushedAuthorizationRequestEndpointAuthMethodsSupported: [String]?
328+329+ /// Signing algorithms supported for PAR endpoint authentication
330+ let pushedAuthorizationRequestEndpointAuthSigningAlgValuesSupported: [String]?
331+332+ /// Whether pushed authorization requests are required
333+ let requirePushedAuthorizationRequests: Bool?
334+335+ /// URL of the UserInfo endpoint
336+ let userinfoEndpoint: URL?
337+338+ /// URL of the end session endpoint
339+ let endSessionEndpoint: URL?
340+341+ /// URL of the dynamic client registration endpoint
342+ let registrationEndpoint: URL?
343+344+ /// DPoP signing algorithms supported (RFC 9449 Section 5.1)
345+ let dpopSigningAlgValuesSupported: [String]?
346+347+ /// Protected resource URIs (RFC 9728 Section 4)
348+ let protectedResources: [URL]?
349+350+ /// Whether client ID metadata document is supported
351+ let clientIdMetadataDocumentSupported: Bool?
352+353+ enum CodingKeys: String, CodingKey {
354+ case issuer
355+ case claimsSupported = "claims_supported"
356+ case claimsLocalesSupported = "claims_locales_supported"
357+ case claimsParameterSupported = "claims_parameter_supported"
358+ case requestParameterSupported = "request_parameter_supported"
359+ case requestUriParameterSupported = "request_uri_parameter_supported"
360+ case requireRequestUriRegistration = "require_request_uri_registration"
361+ case scopesSupported = "scopes_supported"
362+ case subjectTypesSupported = "subject_types_supported"
363+ case responseTypesSupported = "response_types_supported"
364+ case responseModesSupported = "response_modes_supported"
365+ case grantTypesSupported = "grant_types_supported"
366+ case codeChallengeMethodsSupported = "code_challenge_methods_supported"
367+ case uiLocalesSupported = "ui_locales_supported"
368+ case idTokenSigningAlgValuesSupported = "id_token_signing_alg_values_supported"
369+ case displayValuesSupported = "display_values_supported"
370+ case promptValuesSupported = "prompt_values_supported"
371+ case requestObjectSigningAlgValuesSupported = "request_object_signing_alg_values_supported"
372+ case authorizationResponseIssParameterSupported = "authorization_response_iss_parameter_supported"
373+ case authorizationDetailsTypesSupported = "authorization_details_types_supported"
374+ case requestObjectEncryptionAlgValuesSupported = "request_object_encryption_alg_values_supported"
375+ case requestObjectEncryptionEncValuesSupported = "request_object_encryption_enc_values_supported"
376+ case jwksUri = "jwks_uri"
377+ case authorizationEndpoint = "authorization_endpoint"
378+ case tokenEndpoint = "token_endpoint"
379+ case tokenEndpointAuthMethodsSupported = "token_endpoint_auth_methods_supported"
380+ case tokenEndpointAuthSigningAlgValuesSupported = "token_endpoint_auth_signing_alg_values_supported"
381+ case revocationEndpoint = "revocation_endpoint"
382+ case revocationEndpointAuthMethodsSupported = "revocation_endpoint_auth_methods_supported"
383+ case revocationEndpointAuthSigningAlgValuesSupported = "revocation_endpoint_auth_signing_alg_values_supported"
384+ case introspectionEndpoint = "introspection_endpoint"
385+ case introspectionEndpointAuthMethodsSupported = "introspection_endpoint_auth_methods_supported"
386+ case introspectionEndpointAuthSigningAlgValuesSupported = "introspection_endpoint_auth_signing_alg_values_supported"
387+ case pushedAuthorizationRequestEndpoint = "pushed_authorization_request_endpoint"
388+ case pushedAuthorizationRequestEndpointAuthMethodsSupported = "pushed_authorization_request_endpoint_auth_methods_supported"
389+ case pushedAuthorizationRequestEndpointAuthSigningAlgValuesSupported = "pushed_authorization_request_endpoint_auth_signing_alg_values_supported"
390+ case requirePushedAuthorizationRequests = "require_pushed_authorization_requests"
391+ case userinfoEndpoint = "userinfo_endpoint"
392+ case endSessionEndpoint = "end_session_endpoint"
393+ case registrationEndpoint = "registration_endpoint"
394+ case dpopSigningAlgValuesSupported = "dpop_signing_alg_values_supported"
395+ case protectedResources = "protected_resources"
396+ case clientIdMetadataDocumentSupported = "client_id_metadata_document_supported"
397+ }
398+}
399+400+/// OAuth 2.0 Protected Resource Metadata
401+/// Based on RFC 9728 Section 3.2
402+/// - SeeAlso: [RFC 9728 Section 3.2](https://www.rfc-editor.org/rfc/rfc9728.html#section-3.2)
403+struct OAuthProtectedResourceMetadata: Codable {
404+ /// REQUIRED. The protected resource's resource identifier, which is a URL that
405+ /// uses the https scheme and has no query or fragment components.
406+ let resource: URL
407+408+ /// OPTIONAL. JSON array containing a list of OAuth authorization server issuer
409+ /// identifiers, as defined in RFC8414, for authorization servers that can be
410+ /// used with this protected resource.
411+ let authorizationServers: [String]?
412+413+ /// OPTIONAL. URL of the protected resource's JWK Set document.
414+ let jwksUri: URL?
415+416+ /// RECOMMENDED. JSON array containing a list of the OAuth 2.0 scope values that
417+ /// are used in authorization requests to request access to this protected resource.
418+ let scopesSupported: [String]?
419+420+ /// OPTIONAL. JSON array containing a list of the supported methods of sending
421+ /// an OAuth 2.0 Bearer Token to the protected resource.
422+ let bearerMethodsSupported: [String]?
423+424+ /// OPTIONAL. JSON array containing a list of the JWS signing algorithms
425+ /// supported by the protected resource for signing resource responses.
426+ let resourceSigningAlgValuesSupported: [String]?
427+428+ /// OPTIONAL. URL of a page containing human-readable information that
429+ /// developers might want or need to know when using the protected resource.
430+ let resourceDocumentation: URL?
431+432+ /// OPTIONAL. URL that the protected resource provides to read about the
433+ /// protected resource's requirements on how the client can use the data.
434+ let resourcePolicyUri: URL?
435+436+ /// OPTIONAL. URL that the protected resource provides to read about the
437+ /// protected resource's terms of service.
438+ let resourceTosUri: URL?
439+440+ enum CodingKeys: String, CodingKey {
441+ case resource
442+ case authorizationServers = "authorization_servers"
443+ case jwksUri = "jwks_uri"
444+ case scopesSupported = "scopes_supported"
445+ case bearerMethodsSupported = "bearer_methods_supported"
446+ case resourceSigningAlgValuesSupported = "resource_signing_alg_values_supported"
447+ case resourceDocumentation = "resource_documentation"
448+ case resourcePolicyUri = "resource_policy_uri"
449+ case resourceTosUri = "resource_tos_uri"
450+ }
451+}
452+453+/// OAuth 2.0 Pushed Authorization Request (PAR) Response
454+/// Based on RFC 9126 Section 2.2
455+/// - SeeAlso: [RFC 9126 Section 2.2](https://www.rfc-editor.org/rfc/rfc9126.html#section-2.2)
456+struct OAuthPushedAuthorizationResponse: Codable {
457+ /// REQUIRED. The request URI corresponding to the authorization request posted.
458+ /// This URI is a single-use reference to the respective request data in the
459+ /// subsequent authorization request. The way the authorization process obtains
460+ /// the authorization request data is at the discretion of the authorization server
461+ /// and is out of scope of this specification.
462+ let requestUri: String
463+464+ /// REQUIRED. A JSON number that represents the lifetime of the request URI in seconds.
465+ /// The request URI lifetime is at the discretion of the authorization server but
466+ /// will typically be relatively short (e.g., between 5 and 600 seconds).
467+ let expiresIn: Int
468+469+ enum CodingKeys: String, CodingKey {
470+ case requestUri = "request_uri"
471+ case expiresIn = "expires_in"
472+ }
473+}
474+475+476+