@@ -181,17 +181,21 @@ open class OAuth2Base: OAuth2Securable {
181
181
182
182
- verbose (Bool, false by default, applies to client logging)
183
183
*/
184
- override public init ( settings: OAuth2JSON ) {
185
- clientConfig = OAuth2ClientConfig ( settings: settings)
184
+ override public init ( settings: OAuth2JSON , serverMetadata : OAuth2ServerMetadata ? ) {
185
+ clientConfig = OAuth2ClientConfig ( settings: settings, serverMetadata : serverMetadata )
186
186
187
187
// auth configuration options
188
188
if let ttl = settings [ " title " ] as? String {
189
189
authConfig. ui. title = ttl
190
190
}
191
- super. init ( settings: settings)
191
+
192
+ super. init ( settings: settings, serverMetadata: serverMetadata)
193
+
194
+ if let serverMetadata {
195
+ self . validateConfiguration ( against: serverMetadata)
196
+ }
192
197
}
193
198
194
-
195
199
// MARK: - Keychain Integration
196
200
197
201
/** Overrides base implementation to return the authorize URL. */
@@ -290,28 +294,39 @@ open class OAuth2Base: OAuth2Securable {
290
294
/**
291
295
Internally used on error, calls the callbacks on the main thread with the appropriate error message.
292
296
293
- This method is only made public in case you want to create a subclass and need to call `didFail(error:)` at an override point. If you
294
- call this method yourself on your OAuth2 instance you might screw things up royally.
297
+ This method handles authorization failures by cleaning up state, logging the error,
298
+ and notifying all registered callbacks. It ensures proper cleanup of async continuations
299
+ and maintains thread safety by dispatching callbacks to the main thread.
300
+
301
+ This method is only made public in case you want to create a subclass and need to call
302
+ `didFail(with:)` at an override point. If you call this method yourself on your OAuth2
303
+ instance you might cause unexpected behavior.
295
304
296
- - parameter error: The error that led to authorization failure; will use `.requestCancelled` on the callbacks if nil is passed
305
+ - parameter error: The error that led to authorization failure; will use `.requestCancelled` if nil is passed
297
306
*/
298
307
public final func didFail( with error: OAuth2Error ? ) {
299
- var finalError = error
300
- if let error = finalError {
301
- logger? . debug ( " OAuth2 " , msg: " \( error) " )
302
- }
303
- else {
304
- finalError = OAuth2Error . requestCancelled
308
+ let finalError = error ?? OAuth2Error . requestCancelled
309
+
310
+ // Log the error with appropriate level
311
+ if let error = error {
312
+ logger? . warn ( " OAuth2 " , msg: " Authorization failed with error: \( error) " )
313
+ } else {
314
+ logger? . debug ( " OAuth2 " , msg: " Authorization was cancelled or aborted " )
305
315
}
316
+
317
+ // Dispatch callbacks to main thread with proper error handling
306
318
callOnMainThread ( ) {
307
319
self . isAuthorizing = false
308
320
self . internalAfterAuthorizeOrFail ? ( true , finalError)
309
321
self . afterAuthorizeOrFail ? ( nil , finalError)
310
322
}
311
323
312
- // Finish `doAuthorize` call
313
- self . doAuthorizeContinuation? . resume ( throwing: error ?? OAuth2Error . requestCancelled)
314
- self . doAuthorizeContinuation = nil
324
+ // Complete async continuation if present
325
+ if let continuation = doAuthorizeContinuation {
326
+ continuation. resume ( throwing: finalError)
327
+ doAuthorizeContinuation = nil
328
+ logger? . trace ( " OAuth2 " , msg: " Completed async authorization continuation with error " )
329
+ }
315
330
}
316
331
317
332
/**
@@ -504,87 +519,27 @@ open class OAuth2Base: OAuth2Securable {
504
519
open func assureRefreshTokenParamsAreValid( _ params: OAuth2JSON ) throws {
505
520
}
506
521
507
- }
508
-
509
-
510
- /**
511
- Class, internally used, to store current authorization context, such as state and redirect-url.
512
- */
513
- open class OAuth2ContextStore {
514
-
515
- /// Currently used redirect_url.
516
- open var redirectURL : String ?
517
-
518
- /// Current code verifier used for PKCE
519
- public internal( set) var codeVerifier : String ?
520
- public let codeChallengeMethod = " S256 "
521
-
522
- /// The current state.
523
- internal var _state = " "
524
-
525
522
/**
526
- The state sent to the server when requesting a token .
523
+ Validates the current OAuth2 configuration against server metadata .
527
524
528
- We internally generate a UUID and use the first 8 chars if `_state` is empty.
529
- */
530
- open var state : String {
531
- if _state. isEmpty {
532
- _state = UUID ( ) . uuidString
533
- _state = String ( _state [ _state. startIndex..< _state. index ( _state. startIndex, offsetBy: 8 ) ] ) // only use the first 8 chars, should be enough
534
- }
535
- return _state
536
- }
525
+ This method checks if the configured grant type and response type are supported
526
+ by the authorization server according to the provided metadata.
537
527
538
- /**
539
- Checks that given state matches the internal state.
540
-
541
- - parameter state: The state to check (may be nil)
542
- - returns: true if state matches, false otherwise or if given state is nil.
528
+ - parameter serverMetadata: The server metadata to validate against
543
529
*/
544
- func matchesState( _ state: String ? ) -> Bool {
545
- if let st = state {
546
- return st == _state
530
+ open func validateConfiguration( against serverMetadata: OAuth2ServerMetadata ) {
531
+ // validate the grant type
532
+ let grantType = type ( of: self ) . grantType
533
+ let grantTypesSupported = serverMetadata. grantTypesSupported ?? [ OAuth2GrantTypes . authorizationCode, OAuth2GrantTypes . implicit]
534
+ if !grantTypesSupported. contains ( grantType) {
535
+ logger? . warn ( " OAuth2 " , msg: " The authorization server doesn't support the \" \( grantType) \" grant type. " )
547
536
}
548
- return false
549
- }
550
-
551
- /**
552
- Resets current state so it gets regenerated next time it's needed.
553
- */
554
- func resetState( ) {
555
- _state = " "
556
- }
557
-
558
- // MARK: - PKCE
559
-
560
- /**
561
- Generates a new code verifier string
562
- */
563
- open func generateCodeVerifier( ) {
564
- var buffer = [ UInt8] ( repeating: 0 , count: 32 )
565
- _ = SecRandomCopyBytes ( kSecRandomDefault, buffer. count, & buffer)
566
- codeVerifier = Data ( buffer) . base64EncodedString ( )
567
- . replacingOccurrences ( of: " + " , with: " - " )
568
- . replacingOccurrences ( of: " / " , with: " _ " )
569
- . replacingOccurrences ( of: " = " , with: " " )
570
- . trimmingCharacters ( in: . whitespaces)
571
- }
572
-
573
-
574
- open func codeChallenge( ) -> String ? {
575
- guard let verifier = codeVerifier, let data = verifier. data ( using: . utf8) else { return nil }
576
- var buffer = [ UInt8] ( repeating: 0 , count: Int ( CC_SHA256_DIGEST_LENGTH) )
577
- data. withUnsafeBytes {
578
- _ = CC_SHA256 ( $0. baseAddress, CC_LONG ( data. count) , & buffer)
537
+
538
+ // validate the response type
539
+ if let responseType = type ( of: self ) . responseType,
540
+ !serverMetadata. responseTypesSupported. contains ( responseType) {
541
+ logger? . warn ( " OAuth2 " , msg: " The authorization server doesn't support the \" \( responseType) \" response type. " )
579
542
}
580
- let hash = Data ( buffer)
581
- let challenge = hash. base64EncodedString ( )
582
- . replacingOccurrences ( of: " + " , with: " - " )
583
- . replacingOccurrences ( of: " / " , with: " _ " )
584
- . replacingOccurrences ( of: " = " , with: " " )
585
- . trimmingCharacters ( in: . whitespaces)
586
- return challenge
587
543
}
588
544
589
545
}
590
-
0 commit comments