I am developing an app on Xamarin. I am trying to implement a single sign on across multiple apps. For this I authenticate the user first time with Azure AD using ADAL and user enters the credentials in the O365 page displayed by ADAL in the AcquireTokenAsync().
var authContext = new AuthenticationContext(ServiceConstants.AUTHORITY);
var authResult = await authContext.AcquireTokenAsync(resourceUri, ServiceConstants.CLIENTID, ServiceConstants.RETURNURI, param);
var apiAccessToken = authResult.AccessToken;
When successfully authenticated, I save the received token to a file which does persists even if the user uninstalls the app.
Now next time the user starts the app, I pass the saved token to the
AcquireTokenAsync() as UserAssertion
//Pass old accessToken
var userAssertion = new UserAssertion(accessToken);
var authResult = await authContext.AcquireTokenAsync(resourceUri, ServiceConstants.CLIENTID, userAssertion);
This works as expected and the user is authenticated without showing the O365 login screen.
Now I want this same behavior when the user uninstalls the app and reinstalls it. In my case, when the user uninstalls and reinstalls the app, I try to get a fresh token in the same way by adding the saved token as UserAssertion to the AquireTokenAsync(). However I get an error like so :-
Invalid JWT token. Token format not valid.
I cross checked the Token via Postman and it is a valid token.
EDIT
Any idea on how I can resolve this? Or a better way I can implement the same? Any help is highly appreciated.
If you have got the refresh_token, we can use this token to acquire the access_token silently directly via the RETS.
Here is a example for your reference:
POST /{tenant}/oauth2/token HTTP/1.1
Host: https://login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded
client_id=6731de76-14a6-49ae-97bc-6eba6914391e
&refresh_token=OAAABAAAAiL9Kn2Z27UubvWFPbm0gLWQJVzCTE9UkP3pSx1aXxUjq...
&grant_type=refresh_token
&resource=https%3A%2F%2Fservice.contoso.com%2F
&client_secret=JqQX2PNo9bpM0uEihUPzyrh // NOTE: Only required for web apps
More detail please refer the link below.
Refreshing the access tokens
Related
When a user logs out of Azure B2C using the MSAL library on a mobile device this only clears the local cache. The remote session on the server still exists which means any existing refresh tokens could still be used.
From searching I know that the Microsoft Graph API can be used to revoke the current user's sign in session, and therefore invalidate all current refresh tokens. I believe I am doing this, but the refresh tokens keep remaining active.
Here is my flow:
I get a token for user A (I tried this with auth code flow and ROPC but I don't believe that should make a differnce).
I confirmed that I can get a new access token by using the current refresh token that is returned in a Postman call -
{{b2c_login_url}}/B2C_1_ROPC_SignIn/oauth2/v2.0/token?grant_type=refresh_token&client_id={{b2c_ropc_client_id}}&refresh_token=xxxxx&scope={{b2c_scopes}}&redirect_uri={{b2c_api_redirect_uri}}
This returns a new access token as expected.
I then take the azure userId value ("oid" property in the access token) and pass that through to my API that then runs the following code.
var graphClient = GetGraphClient();
var result = await graphClient.Users["{" + userId + "}"]
.RevokeSignInSessions()
.Request()
.PostAsync();
return result.GetValueOrDefault();
I can see that the result of this expression is true. I can also go onto the Azure B2C user details and see that "StsRefreshTokensValidFrom" has been updated to the current date time as expected.
Now, I run the exact same http request I ran previously using the refresh token to get another access token, but this time, it should fail. However, I continue to get new access tokens.
The strange thing is that I am sure I tested this previously, tried to get a new token, and it failed as I'd expect. But now it will always return me new tokens.
I feel I am missing something here. Any advice?
I tried to reproduce the same in my environment and got below results:
I generated token for a B2C user using ROPC flow via Postman with parameters as below:
POST https://<tenant_name>.b2clogin.com/<tenant_name>.onmicrosoft.com/<policy>/oauth2/v2.0/token
client_id : xxxxx-xxxx-xxxx-xxxxxxxx
grant_type : password
scope : https://<tenant_name>.onmicrosoft.com/web_api/api.read offline_access
username : b2c_username
password : password
Response:
Using the above refresh token, I'm able to generate access token successfully like below:
POST https://<tenant_name>.b2clogin.com/<tenant_name>.onmicrosoft.com/<policy>/oauth2/v2.0/token
client_id : xxxxx-xxxx-xxxx-xxxxxxxx
grant_type:refresh_token
scope:https://<tenant_name>.onmicrosoft.com/web_api/api.read
redirect_uri:https://jwt.ms
refresh_token:paste_refresh_token
Response:
To revoke refresh tokens, I ran below query via Graph Explorer like this:
POST https://graph.microsoft.com/beta/users/<user_id>/invalidateAllRefreshTokens
Response:
Code Sample in C#:
GraphServiceClient graphClient = new GraphServiceClient( authProvider );
await graphClient.Users["userid"]
.InvalidateAllRefreshTokens()
.Request()
.PostAsync();
To confirm that, I checked user's details in Portal like below:
When I tried to get access token with same refresh token, I got error saying token is revoked like below:
After revoking the tokens from Graph API, it may take up to 5 minutes to work.
If you run the query for access token as soon as you revoked the refresh tokens, you may still get access token.
So, wait for 5-10 minutes and try to get the access token with same query. Then, you won't be getting access token as the refresh token will be revoked at that time.
A native app created which is calling web api.Two apps has been created in the azure.Here is the code code for getting access token and it worked well,I am getting access token:
UserCredential uc = new UserPasswordCredential(userName, password);
result = authContext.AcquireTokenAsync(todoListResourceId,clientId,
uc).Result;
Now to access new token after the expiry of old one(1 hr) i am using the code:
AuthenticationContext authContext = new AuthenticationContext(authority);
UserAssertion userAssertion = new UserAssertion(oldToken, "urn:ietf:params:oauth:grant-type:jwt-bearer", userName);
AuthenticationResult result = authContext.AcquireTokenAsync(todoListResourceId, clientId, userAssertion).ConfigureAwait(false).GetAwaiter().GetResult();
But I am getting Error as:"Invalid JWT token. AADSTS50027: Invalid JWT token. Token format not valid".
Checked JWT token :it is correct in format can able to decode using jwt.io.
Note: client Id am using for these two code snippet are the same appId.
I know this is the exact duplication of the question asked by devangi.I cannot able to comment on that question that's why I am asking it again.
Any one can able to help me out?
Or
It will be great if any one can able to help with other ways to get token with out using user password since i need to internally generate new token without user enter password again.
For the scenario when user has authenticated on a native application, and this native application needs to call a web API. Azure AD issues a JWT access token to call the web API. If the web API needs to call another downstream web API, it can use the on-behalf-of flow to delegate the user’s identity and authenticate to the second-tier web API .
Please refer to this document for more details about On-Behalf-Of flow . You can also refer to code sample .
For the scenario when when a daemon application(Your web api) needs to call a web API without user's identity , you should use client credential flow to use its own credentials instead of impersonating a user, to authenticate when calling another web service. Code sample here is for your reference .
Please click here for explanation about above two scenarios. Your code is using Resource Owner Password Credentials Grant ,this flow has multi restrictions such as don't support 2FA and is not recommended .
If i misunderstand your requirement , please feel free to let me know .
in my Xamarin.forms project, I use ADAL (Microsoft.IdentityModel.Clients.ActiveDirectory) to authenticate on the Azure portal (Auth 1.0 endpoint). That part work great, but I need to get the security group of the user. So I use this code and passing the token received with ADAL:
HttpClient client = new HttpClient();
HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Get, "https://graph.microsoft.com/v1.0/me/memberOf");
message.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", token);
HttpResponseMessage response = await client.SendAsync(message);
string responseString = await response.Content.ReadAsStringAsync();
I always got StatusCode: 401, ReasonPhrase: 'Unauthorized'.
In my azure AD app registration, I add the Graph API and these permissions:
I think I miss something. Any idea?
----- EDIT 1 ---
Here my payload. I changed it for a picture for lisibility. I don't know how to post json here
----- EDIT 2 ---
OH! I see. I think I need to understand more the Azure Login process. For now I follow an example of ADAL and Azure that let my log and use some function in my backend. So the login process use:
var authContext = new AuthenticationContext(authority); var authResult = await authContext.AcquireTokenAsync(resource, clientId, uri, platformParams);
Where authority = https://login.microsoftonline.com/mysite.com, ResourceID is my backend app ID and clientID is my native app ID. So Shawn is correct, I do not use the Graph.microsoft.com to get the token. Do we have another way to achieve all that? The need of using Graph is only to get the AD group the user has to adjust permission inside the app.
You are correct in your comment. If you add a new permission to your application, you must ask the user to re-consent to the app.
You can force re-consent through ADAL by setting the PromptBehavior to Always:
platformParams.PromptBehavior = PromptBehavior.Always
Or you can simply modify your Login URL to force it, by adding the query string:
&prompt=consent
In terms of building an app to help overcome this problem, if you think your app will be changing permissions after release, you can integrate logic which detects Unauthorized, and then sends the user to re-consent.
Another option is for your app to track changes which may require a new consent prompt, and detect when the user uses this new version of your application the first time, and asks them to consent.
In our new App Model V2, we support the concept of Incremental and Dynamic Consent, which should get rid of this problem all together for you.
Edit:
I have added the "id_token" but still get an "Unauthorized" response.
Here is my login code:
PublicClientApplication myApp = new PublicClientApplication("My-AppID-From-App-Registration-Portal");
string[] scopes = new string[] { "User.Read" };
AuthenticationResult authenticationResult = await myApp.AcquireTokenAsync(scopes).ConfigureAwait(false);
JObject payload = new JObject();
payload["access_token"] = authenticationResult.AccessToken;
payload["id_token"] = authenticationResult.IdToken;
user = await MobileService.LoginAsync(MobileServiceAuthenticationProvider.MicrosoftAccount, payload);
Original Post:
Is it possible to authenticate to a App Services backend using the token retrieved from Microsoft Graph?
I have already tried using this token and calling LoginAsync() with AzureActiveDirectory as the provider, this doesn't work.
JObject payload = new JObject();
payload["access_token"] = GraphAuthenticationHelper.TokenForUser;
user = await MobileService.LoginAsync(MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory, payload);
Is this possible?
UPDATE: In my original answer, I said you cannot do this. But in reality, you can do this but it's a dangerous thing to do since anyone with a valid Microsoft Graph token could theoretically access your APIs. Before I walk you down that path, let me describe the "right" way to access the Microsoft Graph on behalf of your end user.
The right way to do this is to use the on-behalf-of flow in the mobile backend code to exchange the user's ID token for a Microsoft Graph token. The flow looks like the following:
Client initiates a login with AAD using MSAL and sets the resource to the mobile backend (not the Graph). The result should be a set of tokens.
Client uses the mobile SDK to do a login with BOTH the access_token AND the id_token from #1.
Example code:
JObject payload = new JObject();
payload["access_token"] = {access_token.from.msal};
payload["id_token"] = {id_token.from.msal};
var user = await MobileService.LoginAsync(
MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory,
payload);
The backend code exchanges the user's ID token (from the x-ms-token-aad-id-token request header) for a graph token. This token exchange is known as "on-behalf-of" and is documented here. I think this can be done using ADAL or MSAL libraries, but I wasn't able to find documentation. It's also simple enough that you could implement the HTTP protocol directly without too much trouble.
The backend uses the newly acquired MS Graph token and makes the graph API call.
You can also cache the graph token that you acquire on the backend so that each API call doesn't require more AAD API calls to do token exchange.
I think no ,please refer to document : https://learn.microsoft.com/en-us/azure/app-service-mobile/app-service-mobile-dotnet-how-to-use-client-library#a-nameauthenticationaauthenticate-users
Replace INSERT-RESOURCE-ID-HERE with the client ID for your mobile app backend. You can obtain the client ID from the Advanced tab under Azure Active Directory Settings in the portal.
The audience of the access token should be the client ID for your mobile app backend . So if resource is https://graph.microsoft.com/(aud claim in access token) , then Client-managed authentication won't work .
I have registered a multitenant app at https://apps.dev.microsoft.com since the "admin consent" prompt wasn't available in the Azure AD apps. Admin consent is required for our app to retrieve info about users and their calendars.
I can provide admin consent from a completely different tenant than what this app is registered from and use the provided access token to retrieve all necessary information, however that obviously expires after an hour and we need offline access.
I have tried using the tenantId instead of 'common' in the https://login.windows.net/common/oauth2/token endpoint, however receive the same message as below.
The following is the data being submitted to the token endpoint in json format (converted within node to form encoded format before submitting):
{
grant_type: 'refresh_token',
client_id: 'e5c0d59d-b2c8-4916-99ac-3c06d942b3e3',
client_secret: '(redacted)',
refresh_token: '(redacted)',
scope: 'openid offline_access calendars.read user.read.all'
}
When I try to refresh the access token I receive an error:
{
"error":"invalid_grant",
"error_description":"AADSTS65001: The user or administrator has not consented to use the application with ID 'e5c0d59d-b2c8-4916-99ac-3c06d942b3e3'. Send an interactive authorization request for this user and resource.\r\nTrace ID: 2bffaa08-8c56-4872-8f9c-985417402e00\r\nCorrelation ID: c7653601-bf96-46c3-b1ff-4857fb25b7dc\r\nTimestamp: 2017-03-22 02:17:13Z",
"error_codes":[65001],
"timestamp":"2017-03-22 02:17:13Z",
"trace_id":"2bffaa08-8c56-4872-8f9c-985417402e00",
"correlation_id":"c7653601-bf96-46c3-b1ff-4857fb25b7dc"
}
This error occurs even when standard consent is used. I have also tried using the node-adal library instead of raw http requests which produces the exact same result.
I note that "offline_access" isn't a permission I am able to set within the MS apps portal, however I would guess the fact that I am getting a refresh token back means that I can refresh the access token?
For the record, the following is the node-adal code I used to see if I was doing something wrong:
var self = this;
var authenticationContext = new AuthenticationContext('https://login.windows.net/common');
authenticationContext.acquireTokenWithRefreshToken(
self.refreshToken,
self.clientId,
self.clientSecret,
'https://graph.microsoft.com/',
function(a) {
console.log(a);
}
);
Any help in getting this refresh process working is appreciated!
Please ensure that the tenant that you using for refreshing token is same as the tenant that you requesting for the access_token.
The refresh token request works well for me unless in the scenario of below:
register the app from protal using Microsoft account
user1 is in tenant1
add user1 as the external users to tenant2
request the access_token/refresh_token from tenant1(OK)
try to refresh the token using tenant1 in the request(OK)
try to refresh the token using tenant2 in the request(same error message)