Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

application:openURL:options: not called #356

Open
long1eu opened this issue Feb 26, 2019 · 11 comments
Open

application:openURL:options: not called #356

long1eu opened this issue Feb 26, 2019 · 11 comments
Labels

Comments

@long1eu
Copy link

long1eu commented Feb 26, 2019

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
@long1eu
Copy link
Author

long1eu commented Feb 26, 2019

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];
               }                            
           }];

@WilliamDenniss
Copy link
Member

If application:openURL:options: isn't being called, then my guess is you haven't registered the URI scheme in your Info plist.

@grEvenX
Copy link

grEvenX commented Mar 25, 2019

@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

@sjmerel
Copy link

sjmerel commented Mar 26, 2019

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.

@long1eu
Copy link
Author

long1eu commented Mar 26, 2019 via email

@sjmerel
Copy link

sjmerel commented Mar 26, 2019

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.)

@grEvenX
Copy link

grEvenX commented Mar 26, 2019

@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.

@sjmerel
Copy link

sjmerel commented Mar 26, 2019

@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.

@grNorway
Copy link

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)
    }

@lapinek
Copy link

lapinek commented May 29, 2019

Looks like this issue has been discussed at length in #232

@zj381652512
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

7 participants