How to refresh Azure AD B2C identity token - azure

I'm using Azure AD B2C with OpenIdConnect for authentication to a web application. I've got it mainly working except that the authentication times out after an hour, even if the user is actively using the application.
It is an old webapp built mostly with ASPX pages. I'm just using an identity token and I'm using cookies. I am not using an access token at all in my app. Access is done a pre-existing way, based on the user claims. I'm using the MSAL.Net library in Microsoft.Identity.Client. Logging in works fine. I get a code back which then gets exchanged for an identity token
AuthenticationResult result = await confidentialClient.AcquireTokenByAuthorizationCode(Globals.Scopes, notification.Code).ExecuteAsync();
Everything was working fine, except that the token would expire after 1 hour, no matter what I did. Even if I was using the app, the first request after an hour would be unauthenticated. I tried adding a call to silently acquire a token to see if that would refresh it, but it did not. With OpenIdConnect the offline_access scope is always included. If I try to include it explicitly it throws an error saying so. But I've never seen any evidence that there is a refresh token, even behind the scenes.
I found this question on StackOverflow - Azure AD B2C OpenID Connect Refresh token - and the first answer referenced an OpenIdConnect property called UseTokenLifetime. If I set that to false, then I wouldn't lose authentication after an hour, but the now it was too far the other way. It seemed like the token/cookie would never expire, and I could stay logged in forever.
My desire is that as long as the user is actively using the application, they stay logged in, but if they stop using it for some time (an hour), they have to re-authenticate. I found a way to make that happen through hours of trial and error, I'm just not sure if it makes sense and/or is secure. What I'm doing now is that on each authenticated request, I update the "exp" claim of the user (not sure this matters), and then generate a new AuthenticationResponseGrant, setting the ExpiresUtc to the new time. In my testing, if I hit this code in less than an hour, it keeps me logged in, and then if I wait beyond an hour, I'm no longer authenticated.
HttpContext.Current.User.SetExpirationClaim(DateTime.Now.AddMinutes(60.0));
public static void SetExpirationClaim(this IPrincipal currentPrincipal, DateTime expiration)
{
System.Diagnostics.Debug.WriteLine("Setting claims expiration to {0}", expiration);
int seconds = (int)expiration.Subtract(epoch).TotalSeconds;
currentPrincipal.AddUpdateClaim("exp", seconds.ToString(), expiration);
}
public static void AddUpdateClaim(this IPrincipal currentPrincipal, string key, string value, DateTime expiration)
{
var identity = currentPrincipal.Identity as ClaimsIdentity;
if (identity == null)
return;
// check for existing claim and remove it
var existingClaim = identity.FindFirst(key);
if (existingClaim != null)
identity.RemoveClaim(existingClaim);
// add new claim
identity.AddClaim(new Claim(key, value));
var authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
authenticationManager.AuthenticationResponseGrant = new AuthenticationResponseGrant(new ClaimsPrincipal(identity),
new AuthenticationProperties() {
IsPersistent = true,
ExpiresUtc = new DateTimeOffset(expiration).UtcDateTime,
IssuedUtc = new DateTimeOffset(DateTime.Now).UtcDateTime
});
}
My question is, does this make sense? Is there any downside? I've never seen any suggestions to do it this way, but it was the only thing I found that worked. If there is a better way to do it, I'd like to know what it is. I considered making my current code an "answer" instead of including it in the question, but I'm not confident that it is correct.

To refresh ID token, you need to use refresh token. Refresh token is opaque to client, but could be cached by MSAL. Then when ID token is expired, MSAL will use the cached refresh token to get a new ID token.
However, you need to implement the cache logic by yourself like instructed in official sample.
Core code snipet:
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = OnRedirectToIdentityProvider,
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = OnAuthenticationFailed,
},
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification notification)
{
try
{
/*
The `MSALPerUserMemoryTokenCache` is created and hooked in the `UserTokenCache` used by `IConfidentialClientApplication`.
At this point, if you inspect `ClaimsPrinciple.Current` you will notice that the Identity is still unauthenticated and it has no claims,
but `MSALPerUserMemoryTokenCache` needs the claims to work properly. Because of this sync problem, we are using the constructor that
receives `ClaimsPrincipal` as argument and we are getting the claims from the object `AuthorizationCodeReceivedNotification context`.
This object contains the property `AuthenticationTicket.Identity`, which is a `ClaimsIdentity`, created from the token received from
Azure AD and has a full set of claims.
*/
IConfidentialClientApplication confidentialClient = MsalAppBuilder.BuildConfidentialClientApplication(new ClaimsPrincipal(notification.AuthenticationTicket.Identity));
// Upon successful sign in, get & cache a token using MSAL
AuthenticationResult result = await confidentialClient.AcquireTokenByAuthorizationCode(Globals.Scopes, notification.Code).ExecuteAsync();
}
catch (Exception ex)
{
throw new HttpResponseException(new HttpResponseMessage
{
StatusCode = HttpStatusCode.BadRequest,
ReasonPhrase = $"Unable to get authorization code {ex.Message}."
});
}
}

Related

.Net Core 3.1 Azure Web App - Failed to acquire token silently as no token was found in the cache. Call method AcquireToken

I have an Azure Web App that authenticates a user which then navigates to a page where some Sharepoint documents are retrieved and displayed in the app.
Most of the time the application works fine, but ocassionally App Insights will highlight that Failed to acquire token silently as no token was found in the cache. Call method AcquireToken. Some users report issues from time to time on this page (it's inconsistent so it might happen a few times a day with a somewhat large user base). The problem is that currently the error isn't handled and I'm trying to figure out how to make the call to AcquireTokenAsync.
The following is the method that returns the token (or doesnt):
private async Task<string> GetUserAccessToken()
{
try
{
// Credentials for app
// _clientId and _clientSecret represent the app info - not shown here in code
ClientCredential credential = new ClientCredential(_clientId, _clientSecret);
//Construct token cache
ITokenCacheFactory cacheFactory = Request.HttpContext.RequestServices.GetRequiredService<ITokenCacheFactory>();
TokenCache cache = cacheFactory.CreateForUser(Request.HttpContext.User);
AuthenticationContext authContext = new AuthenticationContext(_authority, cache);
// guid of the user currently logged into the app
string objectID = _userObjectId;
UserIdentifier userIdentifier = new UserIdentifier(objectID, UserIdentifierType.UniqueId);
string resource = "https://test.sharepoint.com";
AuthenticationResult result = await authContext.AcquireTokenSilentAsync(resource, credential, userIdentifier);
return result.AccessToken;
}
catch (Exception ex)
{
throw ex;
}
}
If I understand the flow correctly, the web app here will request a token using it's own credentials on behalf of the user currently logged in. (Am I right in understanding this based on the method signature which states - Identifier of the user token is requested for. This parameter can be Microsoft.IdentityModel.Clients.ActiveDirectory.UserIdentifier.Any.)
Now when this fails, I would need to make a call to AcquireTokenAsync. There are a number of these methods available and I can't seem to find the one that will fulfill this requirement.
Before the suggestion comes, I can't use AcquireTokenAsync(resource, clientId, redirectUri,new PlatformParameters(PromptBehavior.Auto)); because the constructor on PlatformParameters has changed and requires an implementation of a ICustomWebUi and this flow isn't supported on .Net Core 3.1 as far as I'm aware which makes this unusable.
AcquireTokenAsync(resource, credentials) works and returns a token, however, when using that token I get a 401 Unauthorized when accessing the Sharepoint resources, most likely because the token is different and it is now requested on behalf of the application and not the user logged into the application (if I'm following this train of thought correctly...).
My question is - which method do I call? Is there something I would need to add before making the call to AcquireTokenAsync and if so, which of the 10 or so overloads should I use? I tried using AcquireTokenAsync(resource, credenetial, userAssertion) and passed in the AccessToken that I retrieved on the User logged in, but then I got Assertion failed signature validation or variations on that. If I understood correctly, the UserAssertion can be initialized with 1,2 or 3 parameters and I tried providing the AccessToken currently on the user that is logged in the app, but with no success.
Any help is greatly appreciated as I've been looking at this for two days now.
I spent more time investigating this, but none of the methods available would have worked in my case. The auth flow wasn't an on-behalf-of flow, but an auth-code flow. The link is to the newer MSAL library, but the concept is there. The application, a .net core web app, directs the user to sign in. When they sign in, an auth-code is passed into the response once they successfully authenticate.
The auth-code is then used to call AcquireTokenByAuthorizationCodeAsync(AuthCode, Uri, ClientCredential, UserIdentifier). This returns the valid access token that can be stored in the distributed token cache and then used to authenticate in order to access a given resource.
My biggest issue was that the error says you need to use AcquireTokenAsync to retrieve a new token. This is correct to a certain point, because in order to make any calls to any of the 14 or so methods you will need different bits of information, which will be dependent on the way you have setup your authentication flow in your application.
Because the application I worked on used auth code flow, I would need to get a new auth code. This would mean redirecting the user to login, capture the auth code in the response if the login was successful and then call the appropriate AcquireTokenAsync method that takes in an auth code as parameter along with app info, uri and so on.
To solve this, I used the information provided by the Microsoft Github page on Acquiring tokens with auth codes in web apps. Here I found samples on how auth flow is setup, but most importantly, how to trigger a new authentication flow if the user needs to be re-authenticated.
I wrapped the code that would throw the AdalSilentTokenAcquisitionException, catch the error and return a RedirectToAction.
return RedirectToAction("ActionName", "Controller", new RouteValues);
The above redirects the user to a given action, in a particular controller and passes through an object that can hold additional parameters. In my case it's a new { redirectUri = redirectUriString}, which is a string object that holds the URL the user would try to navigate this. I constructed this with a little method that uses the current HttpRequest to find the url the user was trying to get to.
Next, the controller that I had setup which responds to that redirect:
[HttpGet("/SignIn")]
public IActionResult SignIn([FromQuery(Name ="redirectUri")]string redirectUri)
{
return Challenge
(
new AuthenticationProperties { RedirectUri = WebUtility.UrlDecode(redirectUri) },
OpenIdConnectDefaults.AuthenticationScheme
);
}
Here, a Challenge is returned. The challenge triggers a call to the authentication flow that was setup in the Startup class. I think the entire flow here is that the method will send people to go through whatever is in that startup, which, in the case of the application I worked on, it prompts the user to sign in, captures the auth code, requests a new access token and once this is received and saved in the distributed token cache, the user is redirected to the redirectUri that I passed through.
I hope this helps or at least gives a starting point to anyone who might encounter a similar issue.

Xamarin MobileServiceClient RefreshUserAsync with Google 403

I am using Azure's MobileServiceClient sdk to authenticate with my server. With the upgrades to 4.x version I am also using Xamarin.Auth to authenticate users with Google and Facebook. When the response comes back from Google I am getting a refresh token. I then call the mobile service sdk like so:
var accessToken = account.Properties["access_token"];
var idToken = account.Properties["id_token"];
var zumoPayload = new JObject();
zumoPayload["access_token"] = accessToken;
zumoPayload["id_token"] = idToken;
var user = await client.LoginAsync(MobileServiceAuthenticationProvider.Google, zumoPayload, );
This work perfectly fine. What does not work is the call to client.RefreshUserAsync(). That is throwing a 403 every time saying the refresh token is either expired or no longer valid even when I call that method right after I logged in. I do not see many examples at all using the MobileServiceClient 4.x sdk and none of them have examples of how to use the refresh token.
I have tried sending that upin the zumo payload as well but it does not work. I have tried invalidating my user on Google (I am getting the refresh token back), tried logging in through the browser and going to auth/me but the refresh token is not there. Any help would be great!
AFAIK, you could leverage the Xamarin.Auth SDK to independently contact the identity provider and retrieve the access token on your mobile client side, then you need to login with your backend (azure mobile app) along with the token for retrieving the authenticationToken, then you could leverage the authenticationToken to access the resources under your mobile app.
Since you are using Client-managed authentication, for refreshing the new access_token, you need to do it on your mobile client side. I checked Xamarin.Auth and found that there is no method for requesting an access token. You need to refer to Refreshing an access token and implement this feature by yourself. I followed OAuth2Authenticator.cs and created a extension method for requesting an access token as follows:
public static class OAuth2AuthenticatorExtensions
{
public static Task RefreshAccessTokenAsync(this OAuth2Authenticator authenticator, Account account)
{
var dics = new Dictionary<string, string>
{
{"refresh_token",account.Properties["refresh_token"]},
{"client_id", authenticator.ClientId},
{"grant_type", "refresh_token"}
};
if (!string.IsNullOrEmpty(authenticator.ClientSecret))
{
dics["client_secret"] = authenticator.ClientSecret;
}
return authenticator.RequestAccessTokenAsync(dics).ContinueWith(task =>
{
if (task.IsFaulted)
{
//todo:
}
else
{
authenticator.OnRetrievedAccountProperties(task.Result);
}
});
}
}
Additionally, if you leverage Server-managed authentication with Microsoft.Azure.Mobile.Client, then you could leverage RefreshUserAsync for refreshing the access token, at this point your previous access_token, clientId are stored on azure, and your mobile app backend would directly communicate with Google's OAuth 2.0 endpoint and request a new access token for you and update the token store on Azure. For more details about token store within App Service, you could follow here.

Azure MobileApp custom authentication, refresh token

I need my app to support custom authentication against our private database, and following the advices on Adrian Hall book here https://adrianhall.github.io/develop-mobile-apps-with-csharp-and-azure/chapter2/custom/ I got no problem authenticating my users. The problem comes when I need to refresh the access token.
As I'm forced to stay with custom auth, I have the following questions:
1) Should I call MobileServicesClient.RefreshUserAsync(), and if so, what kind of endpoint should I implement on the server? Will it reissue another token every time, invalidating the old? When should be the refresh call be made?
2) I've read about using a never-expiring refresh token, but I can't really find a sample implementation, or instructions on how to implement it in a custom auth scenario, could someone point me to the right direction?
Many thanks in advance
Should I call MobileServicesClient.RefreshUserAsync(), and if so, what kind of endpoint should I implement on the server? Will it reissue another token every time, invalidating the old? When should be the refresh call be made?
I have checked the method RefreshUserAsync from Microsoft.WindowsAzure.Mobile.dll which would send a get request against the /.auth/refresh endpoint for refreshing the access token with the provider for your logged in user. Since you are using custom authentication, you could not use this method for refreshing the authenticationToken.
I've read about using a never-expiring refresh token, but I can't really find a sample implementation, or instructions on how to implement it in a custom auth scenario, could someone point me to the right direction?
When using the CreateToken method from AppServiceLoginHandler, you could specify the lifetime as null, then you would retrieve a never-expiring authenticationToken as follows:
JwtSecurityToken token = AppServiceLoginHandler.CreateToken(claims, signingKey, audience, issuer,null);
Additionally, you could try to build your endpoint for creating new token based on the old valid token as follows:
[Route(".auth/login/customRefreshToken")]
public IHttpActionResult RefreshToken([FromBody] RefreshTokenInput body)
{
string tokenString = body.AuthenticationToken;
try
{
var jwtSecurityToken = new JwtSecurityToken(tokenString);
JwtSecurityToken token = AppServiceLoginHandler.CreateToken(jwtSecurityToken.Claims, signingKey, audience, issuer, TimeSpan.FromDays(30));
return Ok(new LoginResult()
{
AuthenticationToken = token.RawData
});
}
catch (Exception e)
{
return BadRequest("$Error = {e.Message}, StackTrace = {e.StackTrace}");
}
}
Note: For your mobile client, you could use MobileServiceClient.InvokeApiAsync for retrieving the new token, then parse the authenticationToken and update it to MobileServiceClient.CurrentUser.MobileServiceAuthenticationToken.
RESULT

Getting username and group info from Azure using adal4j

I am developing a mobile app in which I need to authenticate a user against Azure AD. Basically the user will be prompted their organisational email and password, which the mobile phone app sends to the backend server which will authenticate.
I have the 'public-client-app-sample' of 'azure-activedirectory-library-for-java' working, and can authenticate against 'graph.windows.net':
private static AuthenticationResult getAccessTokenFromUserCredentials(
String username, String password) throws Exception {
AuthenticationContext context = null;
AuthenticationResult result = null;
ExecutorService service = null;
try {
service = Executors.newFixedThreadPool(1);
context = new AuthenticationContext(AUTHORITY, false, service);
Future<AuthenticationResult> future = context.acquireToken(
"https://graph.windows.net", CLIENT_ID, username, password,
null);
result = future.get();
} finally {
service.shutdown();
}
if (result == null) {
throw new ServiceUnavailableException(
"authentication result was null");
}
return result;
}
However, this does not return any userInfo (is null), and I can't figure out at this moment how to query to get a list with groups the user belongs to?
Do I just do manual lookups using the API using the tokens obtained from Adal4j, or is there a provided function within the library?
I am only starting with Azure, so it might be obvious, I might just be looking in the wrong places. I tried e.g. 'https://graph.windows.net/xxx.com/groups?api-version=1.5' but get 'Resource 'https://graph.windows.net/xxx.com/groups?api-version=1.5' is not registered for the account.'
First, you're absolutely right, adal4j was failing to return UserInfo. The reason for this was that the token request did not include the scope=openid parameter, which is required if the caller wants an id_token in the response. I opened an issue, and it has already been resolved. So, an updated copy of adal4j will fix your first issue of not getting UserInfo.
Now, regarding group membership for the current user: Normally, I would recommend that you simply configure you application to return the groups claim. This can be done very easily by changing the application manifest (downloaded and uploaded via the Azure portal, under the Application's configuration page) to include:
"groupMembershipClaims": "All",
Unfortunately, adal4j does not yet include the groups claim in the result of getUserInfo(), so that probably won't work much for you (issue opened, it really depends on how quickly it gets implemented, or if you want to implement youself).
Regardless, because it is possible for there to be too many groups to include in the token (indicated by , your application should always be able to use the AAD Graph API to retrieve the full set of groups the user is a member of.
And that brings me to the last point: adal4j does not implement a client of the Azure AD Graph API. So yes, you would have to implement that yourself (or perhaps use/modify an existing OData client).
(You can read more about Group Claims in this Azure AD blog post.)

Regarding oAuth Token used in web api

I am using OAuth/Owin to generate a token and when that token is sent back in the Authorization header of the request; it gets compared automatically and that's how I validate whether the request is coming from valid user or not.Token is of bearer type.
Code that is used to generate token is as below:
private string GenerateAuthToken(TelephonyLoginCdto loginDto, double tokenExpirationTimeInHours)
{
//Generate AuthToken
var identity = new ClaimsIdentity(OAuthDefaults.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, loginDto.Username));
var currentUtc = DateTime.UtcNow;
var props = new AuthenticationProperties()
{
IssuedUtc = currentUtc,
ExpiresUtc = currentUtc.Add(TimeSpan.FromHours(tokenExpirationTimeInHours))
};
var ticket = new AuthenticationTicket(identity, props);
var accessToken = StartUp.OAuthBearerOptions.AccessTokenFormat.Protect(ticket);
return accessToken;
}
An external system would consume my API.
Flow would be:
1. External system hits login api first. Login api returns a token.
2. External system use that token and pass it in another request Authorization header to do some update using another method of the API.
They can repeat the above process many times in a day say 5~6 times.They come and hit login; get token. Use that token to do some operation by calling save/get methods of API.
My problem is when external system is not hitting any of my API methods for a longer period of time and is idle. Now if external system has to hit the API to do something they need to hit login API again in order to get the token and then consume other methods of API. If they do it more frequently say within 10~15 minutes it take only 1~2 seconds to return the token but if they do it say after 2~2.5 hours my login API taken 30 seconds to return the token.
I am not doing any refresh token mechanism and kind of thinking is it taking this much of time because so many token are already generated and owin is trying to clean the server and then given token. I have no idea what's happenning?
You might want to have a look at AccessTokenExpireTimeSpan.
The period of time the access token remains valid after being issued.
The default is twenty minutes.
The client application is expected to refresh or acquire a new access token after the token has expired.
In your startup you should have something like this:
var OAuthOptions = new OAuthAuthorizationServerOptions
{
// AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/oauth/Token"),
AccessTokenExpireTimeSpan = TimeSpan.FromHours(8),
Provider = new Providers.MyAuthorizationServerProvider(),
// RefreshTokenProvider = new Providers.MyRefreshTokenProvider(DateTime.UtcNow.AddHours(8))
};
app.UseOAuthAuthorizationServer(OAuthOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
Reading your code it seems that your not following the right pipe-line.
I would suggest you to read this article which will guide you through the whole process.
Regarding your last question about the lag when the api hasn't been hit by any request for more than 2 hours, that could be cause IIS (if your hosting your api in that environment) is shout down.

Resources