A WIP swift OAuth Library that one day I'll get back to
at main 476 lines 23 kB view raw
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