AppAuth-iOS
AppAuth-iOS copied to clipboard
application:openURL:options: not called
I'm using this in Flutter where I have just the rootViewController. It all works well I can login, the server redirects to the custom scheme of the app.
In my case I redirect the code to the my backend server and from there to the app. After the server responds the app pops up but the callback is not called, nether the application:openURL:options:
What am I missing.
#include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h"
static NSString *const kAppAuthExampleAuthStateKey = @"authState";
@implementation AppDelegate {
OIDServiceConfiguration *configuration;
OIDAuthState *authState;
FlutterResult flutterResult;
}
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GeneratedPluginRegistrant registerWithRegistry:self];
FlutterViewController *controller = (FlutterViewController *) [[self window] rootViewController];
FlutterMethodChannel *channel =
[FlutterMethodChannel methodChannelWithName:@"app_channel" binaryMessenger: controller];
[channel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) {
[self onMethodCall:call result:result];
}];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
- (void) onMethodCall:(FlutterMethodCall *)call result:(FlutterResult )result {
if([@"init" isEqualToString:call.method]){
OIDServiceDiscovery *doc = [[OIDServiceDiscovery alloc] initWithJSON:call.arguments error:nil];
configuration = [[OIDServiceConfiguration alloc] initWithDiscoveryDocument:doc];
} else if([@"authorize" isEqualToString:call.method]) {
if (flutterResult) {
flutterResult = nil;
@throw [NSInvalidArgumentException initWithString:@"Already login in."];
}
flutterResult = result;
NSString *jsonString = call.arguments[@"params"];
NSStringEncoding encoding = 0;
NSData *jsonData = [jsonString dataUsingEncoding:encoding];
NSError *error=nil;
NSDictionary *params = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:&error];
NSMutableDictionary *additionalParameters = [[NSMutableDictionary alloc] initWithDictionary: params[@"additionalParameters"]];
if (params[@"display"]) {
[additionalParameters setValue:params[@"display"] forKey:@"display"];
}
if (params[@"prompt"]) {
[additionalParameters setValue:params[@"prompt"] forKey:@"prompt"];
}
NSString *nonce = additionalParameters[@"nonce"];
[additionalParameters removeObjectForKey: @"nonce"];
OIDAuthorizationRequest *request =
[[OIDAuthorizationRequest alloc]
initWithConfiguration: configuration
clientId: params[@"clientId"]
clientSecret:nil
scope: params[@"scope"]
redirectURL: [NSURL URLWithString:params[@"redirectUri"]]
responseType: params[@"responseType"]
state: params[@"state"]
nonce: nonce
codeVerifier: nil
codeChallenge: nil
codeChallengeMethod: nil
additionalParameters: additionalParameters];
_currentAuthorizationFlow = [OIDAuthState
authStateByPresentingAuthorizationRequest:request
presentingViewController:[[self window] rootViewController]
callback:^(OIDAuthState * _Nullable authState, NSError * _Nullable error) {
NSLog(@"%@", [[authState lastAuthorizationResponse] additionalParameters]);
if (authState) {
[self setAuthState:authState];
} else {
result([FlutterError errorWithCode:error.description message:error.description details:nil]);
[self setAuthState:nil];
}
}];
} else {
result(FlutterMethodNotImplemented);
}
}
- (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
options:(NSDictionary<NSString *, id> *)options {
NSLog(@"a| %@", [url absoluteString]);
// Sends the URL to the current authorization flow (if any) which will
// process it if it relates to an authorization response.
if ([_currentAuthorizationFlow resumeExternalUserAgentFlowWithURL:url]) {
_currentAuthorizationFlow = nil;
return YES;
}
NSLog(@"%@", [url absoluteString]);
// Your additional URL handling (if any) goes here.
return NO;
}
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
NSLog(@"%@", [url absoluteString]);
return [self application:application openURL:url options:@{}];
}
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
NSLog(@"%@", [url absoluteString]);
return [self application:application openURL:url options:@{}];
}
- (void)didChangeState:(OIDAuthState *)state {
[self saveState];
}
- (void)saveState {
// for production usage consider using the OS Keychain instead
NSData *archivedAuthState = [ NSKeyedArchiver archivedDataWithRootObject:_authState];
[[NSUserDefaults standardUserDefaults] setObject:archivedAuthState
forKey:kAppAuthExampleAuthStateKey];
[[NSUserDefaults standardUserDefaults] synchronize];
}
- (void)loadState {
// loads OIDAuthState from NSUSerDefaults
NSData *archivedAuthState =
[[NSUserDefaults standardUserDefaults] objectForKey:kAppAuthExampleAuthStateKey];
OIDAuthState *authState = [NSKeyedUnarchiver unarchiveObjectWithData:archivedAuthState];
[self setAuthState:authState];
}
- (void)setAuthState:(nullable OIDAuthState *)authState {
if (_authState == authState) {
return;
}
_authState = authState;
_authState.stateChangeDelegate = self;
[self saveState];
}
@end
I've also tried this
_currentAuthorizationFlow = [OIDAuthorizationService presentAuthorizationRequest:request
presentingViewController:[[self window] rootViewController]
callback:^(OIDAuthorizationResponse *authorizationResponse, NSError *error) {
NSLog(@"callback called");
if (authorizationResponse) {
OIDAuthState *authState =
[[OIDAuthState alloc] initWithAuthorizationResponse:authorizationResponse];
[self setAuthState:authState];
} else {
result([FlutterError errorWithCode:error.description message:error.description details:nil]);
[self setAuthState:nil];
}
}];
If application:openURL:options:
isn't being called, then my guess is you haven't registered the URI scheme in your Info plist.
@long1eu What redirect URI protocol are you using? a custom scheme or a https universal link? If it's the latter, it could possibly be related issue #367
I am seeing the same issue. If I set the redirect URL as my app's custom URL directly (e.g. "myapp://oauth-redirect"), then the callback is called. But if I set the redirect URL to a page on my server, then redirect to the same custom URL, the webview closes but the callback is not called (nor is my app delegate's application:openURL:options
.
I can confirm this behavior.
On Tue, Mar 26, 2019, 17:01 sjmerel [email protected] wrote:
I am seeing the same issue. If I set the redirect URL as my app's custom URL directly (e.g. "myapp://oauth-redirect"), then the callback is called. But if I set the redirect URL to a page on my server, then redirect to the same custom URL, the webview closes but the callback is not called (nor is my app delegate's application:openURL:options.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/openid/AppAuth-iOS/issues/356#issuecomment-476747976, or mute the thread https://github.com/notifications/unsubscribe-auth/AI2nVqD93y2fw4dYSNk8gyJoC9J2QSyOks5valJvgaJpZM4bTKLv .
The problem seems to be here, in OIDAuthorizationService.m:
- (BOOL)shouldHandleURL:(NSURL *)URL {
NSURL *standardizedURL = [URL standardizedURL];
NSURL *standardizedRedirectURL = [_request.redirectURL standardizedURL];
return OIDIsEqualIncludingNil(standardizedURL.scheme, standardizedRedirectURL.scheme) &&
OIDIsEqualIncludingNil(standardizedURL.user, standardizedRedirectURL.user) &&
OIDIsEqualIncludingNil(standardizedURL.password, standardizedRedirectURL.password) &&
OIDIsEqualIncludingNil(standardizedURL.host, standardizedRedirectURL.host) &&
OIDIsEqualIncludingNil(standardizedURL.port, standardizedRedirectURL.port) &&
OIDIsEqualIncludingNil(standardizedURL.path, standardizedRedirectURL.path);
}
standardizedURL is the redirect URL that I passed in to my initial request (that matches the one I've set with my identity provider, Fitbit in my case). standardizedRedirectURL is where that page is sending the user (my app's custom URL).
Is this check due to a security concern?
AppAuth on Android doesn't have this problem. (The Android version of my app is the reason why I'm redirecting to a web page in the first place; redirecting straight to the app from the IdP's signon page is somewhat confusing on Android.)
@sjmerel The problem is probably not in this code in that case. The code you mentioned is only called as long as you are properly forwarding the URL to the resumeExternalUserAgentFlowWithURL
method.
In order to catch a Universal Link you need to implement that code in the correct UIApplicationDelegate method and it only works if you have taken proper measures to use a Universal Link. The blog post https://medium.com/@abhimuralidharan/universal-links-in-ios-79c4ee038272 explains it pretty well.
If you have implemented Universal Link properly, you should be able to paste the callback link in Messages app, or link to it from a website or similar, and when you tap on it, the app should open and handle the link.
@grEvenX Not sure what you mean... shouldHandleURL
is getting called; it's returning false, though, which causes resumeExternalUserAgentFlowWithURL
to exit early.
I'm using a Custom URL Scheme, not an Universal Link (though I could use Universal Links instead). I know my scheme is set up correctly because if I enter it into Safari directly, I am prompted to open my app.
I have the same Problem. I have a successful login but the redirect URL doesn't trigger the appDelegate. I have pass my redirectURL( appName://authenticate )
I have configure in the info.plist all the needed info correct.
Nothing get triggered .
Any idea why ?
Here is my code
class func signInAuth(discoveryURLstr: String,presenter : UIViewController,completionHandler: @escaping ( (OIDAuthState?,Error?) -> () )){
guard let discoveruURL = URL(string: discoveryURLstr) else{
completionHandler(nil,AuthErrors.InvalidDiscoveryURL)
return
}
appAuthDiscoverConfiguration(discoveryURL: discoveruURL) { (configurationFile, error) in
guard let configurationFile = configurationFile else {
completionHandler(nil,AuthErrors.InvalidConfigurationFile)
return
}
let authRequest = appAuthRequest(configurationFile: configurationFile)
self.appAuthenticationSession = OIDAuthState.authState(byPresenting: authRequest, presenting: presenter, callback: { (state, error) in
if let error = error {
//self.authState = nil
completionHandler(nil,error)
return
}
if let state = state {
self.authState = state
completionHandler(state,nil)
}else{
completionHandler(nil,AuthErrors.InvalideState)
}
})
}
}
class func appAuthDiscoverConfiguration(discoveryURL : URL, completionHandler: @escaping ((OIDServiceConfiguration?,Error?) -> ())) {
OIDAuthorizationService.discoverConfiguration(forDiscoveryURL: discoveryURL) { (configuration, error) in
if let error = error {
completionHandler(nil,error)
return
}else{
guard let configurationFile = configuration else {
completionHandler(nil,AuthErrors.InvalidConfigurationFile)
return
}
completionHandler(configurationFile,nil)
}
}
}
class func appAuthRequest(configurationFile : OIDServiceConfiguration) -> OIDAuthorizationRequest{
return OIDAuthorizationRequest(configuration: configurationFile, clientId: AppAuthConstants.clientId, clientSecret: nil, scope: AppAuthConstants.scope, redirectURL: AppAuthConstants.redirectURL, responseType: AppAuthConstants.responseType, state: nil, nonce: nil, codeVerifier: nil, codeChallenge: nil, codeChallengeMethod: nil, additionalParameters: AppAuthConstants.additionalParameters)
}
Looks like this issue has been discussed at length in #232
I am seeing the same issue. If I set the redirect URL as my app's custom URL directly (e.g. "myapp://oauth-redirect"), then the callback is called. But if I set the redirect URL to a page on my server, then redirect to the same custom URL, the webview closes but the callback is not called (nor is my app delegate's
application:openURL:options
.
thanks, Your suggestion solved my problem in the demo where i download from https://github.com/insanelydeepak/fitbit-api-example-iOS which login to fitbit by using SFSafariViewController