A WIP swift OAuth Library that one day I'll get back to
1//
2// File.swift
3// Gulliver
4//
5// Created by Bailey Townsend on 1/20/26.
6//
7
8import Foundation
9
10
11
12/// OAuth 2.0 Client Metadata
13/// Based on RFC 7591 Section 2 and related specifications
14struct 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
223struct 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)
403struct 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)
456struct 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