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

Support for multiple tokens in native auth flows #2082

Merged
merged 25 commits into from May 23, 2024
Merged

Conversation

SaurabhMSFT
Copy link
Contributor

@SaurabhMSFT SaurabhMSFT commented Apr 19, 2024

This PR and its companion PR (AzureAD/microsoft-authentication-library-common-for-android#2390) enables multiple access token support for Native Auth. A pair of new method have been added in the AccountState class to allow developer to retrieve different access tokens according to the requested scopes. The way these methods acquire tokens is exactly as same as the one in MSAL. The old method 'getAccessToken''s functionality hasn't been changed. It still returns the access token matching the default OIDC scope. This method though has been deprecated.

@@ -58,6 +60,7 @@ import com.microsoft.identity.nativeauth.utils.serializable
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.Collections

/**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed in the technical design document review, the documentation in comments needs to be updated to explain the difference between these two methods.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments updated

return getAccessTokenInternal(forceRefresh, ArrayList(scopeSet2))
}

suspend fun getAccessTokenInternal(forceRefresh: Boolean, scopes: List<String>?): GetAccessTokenResult {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should scopes be nullable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, the scopes are null from the old deprecated method.

*
* @return [com.microsoft.identity.nativeauth.statemachine.results.GetAccessTokenResult] The result of the getAccessToken action
*/
suspend fun getAccessToken(forceRefresh: Boolean = false, scopes: List<String>?): GetAccessTokenResult {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the scopes list should not be optional here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making it null allows the developers to move to this function from the old deprecated method.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nilo-ms can you confirm what should happen here? Is scopes optional, or not?

  • in current MSAL, it's not optional (i.e. if an empty scopes list is passed, the method returns an error)
  • if we allow empty/null scopes param, how do we communicate to developers how this method works in those scenarios?
  • how is iOS implementing this for native auth?
  • I agree with Saurabh that developers need a way to migrate from the old, now-deprecated method to the new one. If the old one doesn't take a scopes parameter, then the new one needs to have the possibility of also not passing any.

Copy link

@nilo-ms nilo-ms May 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think native auth should work in the same way as the platform specific MSAL works. For example, for MSAL Objective-C, the scope list is not optional, but the external developer can specify an empty list of scopes, no error is thrown. This is how we plan to implement it.
Given that you want to provide a way to migrate to the new method, I suggest to slightly deviate from how MSAL Android work. I propose:

  • adding the scope list as mandatory
  • If an empty scope list is passed by the external developer, add the default OIDC scopes internally (as we do for the signIn)
  • clarify this behaviour in the method comment

Be mindful that if you want to align with the MSAL Android behaviour, it will be considered a breaking change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have made the parameter as NonNull

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SaurabhMSFT can you clarify which approach you're taking:

  1. conceptually, scopes are optional. Similar to how getAccessToken() behaves right now.
  2. scopes are not optional. Similar to how MSAL browser-delegated behaves right now.

If we're going with 1, then the following should be implemented IMO:

  1. scopes param may be null.
  2. scopes list may be empty.

As these two are effectively the same, I don't see a reason to limit one but not the other.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scopes are optional. An empty list of scopes or a null value could show the same intent. Supporting one or the other is just a matter of taste and consistency.

Copy link
Contributor

@SammyO SammyO May 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree @SaurabhMSFT. If scopes are optional, the whole parameter should be nullable. It's a pain for developers to have to create an empty list, only because they can't pas null. both null and empty list serve the same functional purpose, so both should be allowed.

@@ -98,6 +100,19 @@ class AccountState private constructor(
}
}

private fun addDefaultScopes(scopes: List<String>?): List<String> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this done here, rather than inside the controller? The latter is what MSAL is doing currently, and wherever possible I would like to stick to existing designs/architectures.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function wasn't used and has been removed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not the question. I can see default scopes are added in the interface layer (in getAccessTokenInternal()), rather than inside the controller. Why not the latter, and align with how MSAL's browser-delegated flow operates?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have changed the code in getAccessMethod so that empty scopes are not passed to getAccessTokenInternal obviating the need to merge scopes.

@@ -194,14 +209,16 @@ class AccountState private constructor(
interface GetAccessTokenCallback : Callback<GetAccessTokenResult>

/**
* Retrieves the access token for the currently signed in account from the cache.
* Retrieves the access token for the currently signed in account from the cache. If multiple
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment doesn't make sense, as this method doesn't accept a scopes parameter.
Also, shouldn't this method be returning a token for the default scopes? If so, that should be made clear from the comments.

@nilo-ms can you review this based on your technical proposal please.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest adding a comment like:
Retrieves an access token with default OIDC scopes as permission

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Contributor

@SammyO SammyO left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed on Teams, please propose how these changes should be tested by reviewers, before this is merged in.

@SaurabhMSFT SaurabhMSFT marked this pull request as ready for review May 15, 2024 14:51
@SaurabhMSFT SaurabhMSFT requested review from a team as code owners May 15, 2024 14:51
* The INVALID_SCOPES value indicates the scopes provided by the user are not valid
* If this occurs, valid scopes should be resubmitted
*/
const val INVALID_SCOPES = "invalid_scopes"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a shared/common error, right? If so and if you agree with my other comment, please move this out of the common error class and into the one that's specific for this scenario.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not shared now but should be as we should return similar error from signin method.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should return similar error from signin method

what makes you say that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have moved the variable to GetAccessTokenErrorTypes class


assertTrue(accessTokenState is GetAccessTokenError)
assertTrue((accessTokenState as GetAccessTokenError).isInvalidScopes())
assertEquals((accessTokenState as GetAccessTokenError).errorType, ErrorTypes.INVALID_SCOPES)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please update this to use the utility method

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a call to utility method on line 770. Are you looking for some other utility method?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah sorry, missed that. We don't need line 271, IMO, but nit.

changelog Outdated Show resolved Hide resolved
)
}

is Exception -> {
GetAccessTokenError(
exception = commandResult,
correlationId = "UNSET"
correlationId = correlationId
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should actually be using commandResult.correlationid here, for the unlikely case that the API has returned a different correlation ID. That field might be null, so we'd need to update GetAccessTokenError. And that improvement would have to be applied to more places.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change can be done when commandResult is ServiceException if commandResult is Exception then we will use the old correlationId.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel free to move this into a separate PR. It's not blocking.

SaurabhMSFT added a commit to AzureAD/microsoft-authentication-library-common-for-android that referenced this pull request May 23, 2024
This PR and its companion PR
(AzureAD/microsoft-authentication-library-for-android#2082)
enables multiple access token support for Native Auth. A pair of new
method have been added in the AccountState class to allow developer to
retrieve different access tokens according to the requested scopes. The
way these methods acquire tokens is exactly as same as the one in MSAL.
The old method 'getAccessToken''s functionality hasn't been changed. It
still returns the access token matching the default OIDC scope. This
method though has been deprecated.
Two classes that have become unused have been removed.
@SaurabhMSFT SaurabhMSFT merged commit f357998 into dev May 23, 2024
8 checks passed
@SaurabhMSFT SaurabhMSFT deleted the saugautam/2906127 branch May 23, 2024 16:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants