Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
- Discovery documents with a JSON `null` or a non-string value in an optional URL-valued field no longer raise `NSInvalidArgumentException`; the property reads as `nil`. The same value in a required URL field now fails with `OIDErrorCodeInvalidDiscoveryDocument`. An `authorization_endpoint` that does not parse now fails the document too, like the other required URL fields.
- Deprecated CocoaPods distribution. AppAuth 3.0.0 is the last release published to CocoaPods; CocoaPods trunk becomes read-only on December 2, 2026. See [MIGRATING_FROM_COCOAPODS.md](MIGRATING_FROM_COCOAPODS.md) for moving to Swift Package Manager. Addresses issue #962.
- Fix a crash (`NSInvalidArgumentException`) when a token, registration or device authorization endpoint answers with a JSON array instead of an object. The callback now gets an error. Addresses issue #987.
- Added instance-method counterparts of the `OIDAuthorizationService` class methods, and made `-init` public, so the service can be injected and faked in tests.
- Deprecated `OIDAuthorizationService.configuration`. It is never set and is always `nil`; use the request's `configuration` instead. It will be removed in 4.0.

# 3.0.0
- BREAKING: Updates made to support Xcode 27. ([#972](https://github.com/openid/AppAuth-iOS/pull/972), [#973](https://github.com/openid/AppAuth-iOS/pull/973))
Expand Down
61 changes: 52 additions & 9 deletions Sources/AppAuthCore/OIDAuthorizationService.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,18 +82,18 @@ typedef void (^OIDRegistrationCompletion)(OIDRegistrationResponse *_Nullable reg
*/
@interface OIDAuthorizationService : NSObject

/*! @brief The service's configuration.
@remarks Each authorization service is initialized with a configuration. This configuration
specifies how to connect to a particular OAuth provider. Clients should use separate
authorization service instances for each provider they wish to integrate with.
Configurations may be created manually, or via an OpenID Connect Discovery Document.
/*! @brief Unused, and always nil.
@discussion No initializer sets this, and nothing in AppAuth reads it. Each request carries
its own configuration (@c request.configuration). This property will be removed in
AppAuth 4.0.
*/
@property(nonatomic, readonly) OIDServiceConfiguration *configuration;
@property(nonatomic, readonly) OIDServiceConfiguration *configuration
__deprecated_msg("Always nil. Use the configuration on the request instead.");

/*! @internal
@brief Unavailable. This class should not be initialized.
/*! @brief Creates a service whose instance methods call the matching class methods.
@discussion Tests can hand in a subclass that fakes the instance methods.
*/
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)init;

/*! @brief Convenience method for creating an authorization service configuration from an OpenID
Connect compliant issuer URL.
Expand Down Expand Up @@ -165,6 +165,49 @@ typedef void (^OIDRegistrationCompletion)(OIDRegistrationResponse *_Nullable reg
+ (void)performRegistrationRequest:(OIDRegistrationRequest *)request
completion:(OIDRegistrationCompletion)completion;

/*! @brief Instance counterpart of @c +presentAuthorizationRequest:externalUserAgent:callback:.
@param request The authorization request.
@param externalUserAgent Generic external user-agent that can present an authorization
request.
@param callback The method called when the request has completed or failed.
*/
- (id<OIDExternalUserAgentSession>)
presentAuthorizationRequest:(OIDAuthorizationRequest *)request
externalUserAgent:(id<OIDExternalUserAgent>)externalUserAgent
callback:(OIDAuthorizationCallback)callback;

/*! @brief Instance counterpart of @c +presentEndSessionRequest:externalUserAgent:callback:.
@param request The end-session logout request.
@param externalUserAgent Generic external user-agent that can present user-agent requests.
@param callback The method called when the request has completed or failed.
*/
- (id<OIDExternalUserAgentSession>)
presentEndSessionRequest:(OIDEndSessionRequest *)request
externalUserAgent:(id<OIDExternalUserAgent>)externalUserAgent
callback:(OIDEndSessionCallback)callback;

/*! @brief Instance counterpart of @c +performTokenRequest:callback:.
@param request The token request.
@param callback The method called when the request has completed or failed.
*/
- (void)performTokenRequest:(OIDTokenRequest *)request callback:(OIDTokenCallback)callback;

/*! @brief Instance counterpart of @c +performTokenRequest:originalAuthorizationResponse:callback:.
@param request The token request.
@param authorizationResponse The original authorization response related to this token request.
@param callback The method called when the request has completed or failed.
*/
- (void)performTokenRequest:(OIDTokenRequest *)request
originalAuthorizationResponse:(OIDAuthorizationResponse *_Nullable)authorizationResponse
callback:(OIDTokenCallback)callback;

/*! @brief Instance counterpart of @c +performRegistrationRequest:completion:.
@param request The registration request.
@param completion The method called when the request has completed or failed.
*/
- (void)performRegistrationRequest:(OIDRegistrationRequest *)request
completion:(OIDRegistrationCompletion)completion;

@end

NS_ASSUME_NONNULL_END
41 changes: 41 additions & 0 deletions Sources/AppAuthCore/OIDAuthorizationService.m
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,47 @@ + (void)performRegistrationRequest:(OIDRegistrationRequest *)request
}] resume];
}

#pragma mark - Instance API

- (instancetype)init {
return [super init];
}

- (id<OIDExternalUserAgentSession>)
presentAuthorizationRequest:(OIDAuthorizationRequest *)request
externalUserAgent:(id<OIDExternalUserAgent>)externalUserAgent
callback:(OIDAuthorizationCallback)callback {
return [OIDAuthorizationService presentAuthorizationRequest:request
externalUserAgent:externalUserAgent
callback:callback];
}

- (id<OIDExternalUserAgentSession>)
presentEndSessionRequest:(OIDEndSessionRequest *)request
externalUserAgent:(id<OIDExternalUserAgent>)externalUserAgent
callback:(OIDEndSessionCallback)callback {
return [OIDAuthorizationService presentEndSessionRequest:request
externalUserAgent:externalUserAgent
callback:callback];
}

- (void)performTokenRequest:(OIDTokenRequest *)request callback:(OIDTokenCallback)callback {
[self performTokenRequest:request originalAuthorizationResponse:nil callback:callback];
}

- (void)performTokenRequest:(OIDTokenRequest *)request
originalAuthorizationResponse:(OIDAuthorizationResponse *_Nullable)authorizationResponse
callback:(OIDTokenCallback)callback {
[OIDAuthorizationService performTokenRequest:request
originalAuthorizationResponse:authorizationResponse
callback:callback];
}

- (void)performRegistrationRequest:(OIDRegistrationRequest *)request
completion:(OIDRegistrationCompletion)completion {
[OIDAuthorizationService performRegistrationRequest:request completion:completion];
}

@end

NS_ASSUME_NONNULL_END
159 changes: 159 additions & 0 deletions UnitTests/OIDAuthorizationServiceInstanceTests.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*! @file OIDAuthorizationServiceInstanceTests.m
@brief AppAuth iOS SDK
@copyright
Copyright 2026 The AppAuth Authors. All Rights Reserved.
@copydetails
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

#import <XCTest/XCTest.h>

#import "OIDAuthorizationRequestTests.h"
#import "OIDEndSessionRequestTests.h"
#import "OIDTokenRequestTests.h"

#if SWIFT_PACKAGE
@import AppAuthCore;
#else
#import "Sources/AppAuthCore/OIDAuthorizationResponse.h"
#import "Sources/AppAuthCore/OIDAuthorizationService.h"
#import "Sources/AppAuthCore/OIDEndSessionResponse.h"
#import "Sources/AppAuthCore/OIDError.h"
#import "Sources/AppAuthCore/OIDExternalUserAgent.h"
#import "Sources/AppAuthCore/OIDExternalUserAgentSession.h"
#import "Sources/AppAuthCore/OIDTokenRequest.h"
#import "Sources/AppAuthCore/OIDTokenResponse.h"
#endif

// Ignore warnings about "Use of GNU statement expression extension" which is raised by our use of
// the XCTAssert___ macros.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wgnu"

NS_ASSUME_NONNULL_BEGIN

/*! @brief An external user agent that refuses to present anything.
*/
@interface OIDRefusingExternalUserAgent : NSObject <OIDExternalUserAgent>
@end

@implementation OIDRefusingExternalUserAgent

- (BOOL)presentExternalUserAgentRequest:(id<OIDExternalUserAgentRequest>)request
session:(id<OIDExternalUserAgentSession>)session {
return NO;
}

- (void)dismissExternalUserAgentAnimated:(BOOL)animated completion:(void (^)(void))completion {
if (completion) {
completion();
}
}

@end

/*! @brief Records the three-argument token request instead of performing it.
*/
@interface OIDTokenRecordingAuthorizationService : OIDAuthorizationService

/*! @brief How many three-argument token requests were made. */
@property(nonatomic, readonly) NSUInteger tokenRequestCount;

/*! @brief The request passed to the most recent three-argument token request. */
@property(nonatomic, readonly, nullable) OIDTokenRequest *lastTokenRequest;

/*! @brief The authorization response passed to the most recent three-argument token request. */
@property(nonatomic, readonly, nullable)
OIDAuthorizationResponse *lastOriginalAuthorizationResponse;

@end

@implementation OIDTokenRecordingAuthorizationService

- (void)performTokenRequest:(OIDTokenRequest *)request
originalAuthorizationResponse:(nullable OIDAuthorizationResponse *)authorizationResponse
callback:(OIDTokenCallback)callback {
_tokenRequestCount++;
_lastTokenRequest = request;
_lastOriginalAuthorizationResponse = authorizationResponse;
}

@end

/*! @brief Unit tests for the instance methods of @c OIDAuthorizationService.
*/
@interface OIDAuthorizationServiceInstanceTests : XCTestCase
@end

@implementation OIDAuthorizationServiceInstanceTests

/*! @brief A refused authorization request fails with @c OIDErrorCodeSafariOpenError. */
- (void)testPresentAuthorizationRequestFailsWhenUserAgentRefuses {
OIDAuthorizationService *service = [[OIDAuthorizationService alloc] init];
OIDRefusingExternalUserAgent *userAgent = [[OIDRefusingExternalUserAgent alloc] init];
__block OIDAuthorizationResponse *receivedResponse;
__block NSError *receivedError;
id<OIDExternalUserAgentSession> session =
[service presentAuthorizationRequest:[OIDAuthorizationRequestTests testInstance]
externalUserAgent:userAgent
callback:^(OIDAuthorizationResponse *_Nullable response,
NSError *_Nullable error) {
receivedResponse = response;
receivedError = error;
}];
XCTAssertNotNil(session);
XCTAssertNil(receivedResponse);
XCTAssertEqualObjects(receivedError.domain, OIDGeneralErrorDomain);
XCTAssertEqual(receivedError.code, OIDErrorCodeSafariOpenError);
}

/*! @brief A refused end session request fails with @c OIDErrorCodeSafariOpenError. */
- (void)testPresentEndSessionRequestFailsWhenUserAgentRefuses {
OIDAuthorizationService *service = [[OIDAuthorizationService alloc] init];
OIDRefusingExternalUserAgent *userAgent = [[OIDRefusingExternalUserAgent alloc] init];
__block OIDEndSessionResponse *receivedResponse;
__block NSError *receivedError;
id<OIDExternalUserAgentSession> session =
[service presentEndSessionRequest:[OIDEndSessionRequestTests testInstance]
externalUserAgent:userAgent
callback:^(OIDEndSessionResponse *_Nullable response,
NSError *_Nullable error) {
receivedResponse = response;
receivedError = error;
}];
XCTAssertNotNil(session);
XCTAssertNil(receivedResponse);
XCTAssertEqualObjects(receivedError.domain, OIDGeneralErrorDomain);
XCTAssertEqual(receivedError.code, OIDErrorCodeSafariOpenError);
}

/*! @brief The two-argument token request goes through the three-argument one, with no
authorization response.
*/
- (void)testTwoArgumentTokenRequestCallsThreeArgumentTokenRequest {
OIDTokenRecordingAuthorizationService *service =
[[OIDTokenRecordingAuthorizationService alloc] init];
OIDTokenRequest *request = [OIDTokenRequestTests testInstance];
[service performTokenRequest:request
callback:^(OIDTokenResponse *_Nullable response,
NSError *_Nullable error) {}];
XCTAssertEqual(service.tokenRequestCount, 1u);
XCTAssertEqual(service.lastTokenRequest, request);
XCTAssertNil(service.lastOriginalAuthorizationResponse);
}

@end

NS_ASSUME_NONNULL_END

#pragma GCC diagnostic pop
Loading