Search code examples
iosiphoneadalrefresh-tokenazure-ad-msal

iOS ADAL-Make silent call using refresh token


I am using iOS ADAL library version 2.2.6 and receiving refresh token upon successful login. Now I want to make a silent call by using this refresh token. I tried with following method but it fails to return the access token.

 ADAuthenticationContext *authContext;              
[authContext acquireTokenSilentWithResource:resourceId
                                    clientId:clientId
                                  redirectUri:redirectUri
                                       userId:strUserID //loggedIn userID
                              completionBlock:^(ADAuthenticationResult *result){ 

// It alway throws an error //Please call the non-silent acquireTokenWithResource methods.
if(result.error){

ADAuthenticationError *error = nil;
authContext = [ADAuthenticationContext authenticationContextWithAuthority:inputData.authority error:&error];

[authContext acquireTokenWithResource:inputData.ResourceID
                             clientId:inputData.ClientId                         // Comes from App Portal
                          redirectUri:inputData.RedirectUri // Comes from App Portal
                      completionBlock:^(ADAuthenticationResult *result)
 {
     if (AD_SUCCEEDED != result.status){
         // Show alert with error description
     }
     else{

         //Handle Success token
     }
 }];

}else{

         //Handle Success token
 }

}];

But it always throws an error saying "The user credentials are needed to obtain access token. Please call the non-silent acquireTokenWithResource methods."

Is there any way to make a silent call using refresh token? please help me on it. Thanks in advance.


Solution

  • When you use Microsoft's authentication libraries, you should always first check to see if there is a user in the cache that can be used for your resource before prompting the user to sign in. This allows us to check if the user had previously signed in to your app or if there are other apps that share state with your app that may have already asked the user to sign in elsewhere.

    If the user is found, we will try to acquire a token without interrupting the user at all. Sometimes a user will have changed their password or done some other action that will require them to sign in again even if they have signed in to your app previously. This is what you are seeing. The library is telling you that for the user you are trying to acquire a token for, they need to sign in again to make something right.

    In order to handle all these cases elegantly, we recommend that you use the pseudocode pattern of:

    acquireTokenSilent()
    (if error InteractiveAuthenticationRequired) {
        acquireTokenInteractively() }
    

    The pattern first checks if a user you specify is available in the token cache. If it is, we then call the Azure Active Directory service to see if the Refresh token for that user is valid. If both of these are true, then the user is signed in silently. If the user isn't found or the server rejects the Refresh Token, then an error is sent from the library that indicates the user needs to sign in interactively.

    In the above, you are doing this first part, but you aren't handling the case where the user needs to sign in if there is a problem.

    The best way is to catch the error with a ADErrorCode of AD_ERROR_USER_INPUT_NEEDED

    Here is a code sample on how to do this pattern.

    // Here we try to get a token from the stored user information we would have from a successful authentication
    
        [authContext acquireTokenSilentWithResource:data.resourceId
                                           clientId:data.clientId
                                        redirectUri:redirectUri
                                             userId:data.userItem.userInformation.userId
                                    completionBlock:^(ADAuthenticationResult *result) {
                                            if (!result.error)
                                                {
    
                                              completionBlock(result.tokenCacheStoreItem.userInformation, nil);
                                            } else {
    
                                                    if ([result.error.domain isEqual:ADAuthenticationErrorDomain] && result.error.code == AD_ERROR_USER_INPUT_NEEDED) {
    
                                                        // Here we know that input is required because we couldn't get a token from the cache
    
                                                        [authContext acquireTokenWithResource:data.resourceId
                                                                                     clientId:data.clientId
                                                                                  redirectUri:redirectUri
                                                                                       userId:data.userItem.userInformation.userId
                                                                              completionBlock:^(ADAuthenticationResult *result) {
    
                                                                                  if (result.status != AD_SUCCEEDED)
                                                                                  {
                                                                                      completionBlock(nil, result.error);
                                                                                  }
                                                                                  else
                                                                                  {
                                                                                      data.userItem = result.tokenCacheStoreItem;
                                                                                      completionBlock(result.tokenCacheStoreItem.userInformation, nil);
                                                                                  }
                                                                              }];
                                                    } else {
    
    
                                                        completionBlock(nil, result.error);
                                                    }
                                            }
    
    
                                    }];
    

    Keep in mind this code is very verbose. You will most likely want to have acquireTokenWithResource: a separate method that you could call with [self acquireTokenWithResource]