Refreshing access tokens in IdentityServer4 clients - asp.net-mvc

I wonder how to refresh a access token in a IdentityServer4 client using the hybrid flow and which is built using ASP.NET Core MVC.
If I have understood the whole concept correctly the client first need to have the "offline_access" scope in order to be able to use refresh tokens which is best practice to enable short lived access tokens and ability to revoke refresh tokens preventing any new access tokens to be issued to the client.
I successfully get a access token and a refresh token, but how should I handle the actual update procedure of the access token in the MVC client?
Can the OpenId Connect (OIDC) middleware handle this automatically? Or should I rather check the expire time of the access token everywhere I call WEB Api's by basically check if the access token have expired or will expire very soon (upcoming 30 seconds) then refresh the access token by calling the token endpoint using the refresh token?
Is it recommended to use the IdentityModel2 library TokenClient extension method RequestRefreshTokenAsync in my Controller action methods for calling the token endpoint?
I have seen code that in the OIDC middleware events request access token and using the response store a claim containing a expire datetime. The problem is that my OIDC in somehow already request a access token automatically so it doesn't feel good to request a new access token directly after recieving the first one.
Example of a Controller action method without access token refresh logic:
public async Task<IActionResult> GetInvoices()
{
var token = await HttpContext.Authentication.GetTokenAsync("access_token");
var client = new HttpClient();
client.SetBearerToken(token);
var response = await client.GetStringAsync("http://localhost:5001/api/getInvoices");
ViewBag.Json = JArray.Parse(response).ToString();
return View();
}

The OIDC middleware will not take care of this for you. It's being executed when it detects a HTTP 401 response, it then redirects the user to IdentityServer login page. After the redirection to your MVC application, it will turn claims into a ClaimsIdentity and pass this on to the Cookies middleware which will materialise that into a session cookie.
Every other request will not involve the OIDC middleware as long as the cookie is still valid.
So you have to take care of this yourself. Another thing you want to consider is that whenever you're going to refresh the access token, you'll have to update the existing one so you don't lose it. If you don't do this, the session cookie will always contain the same token - the original one - and you'll refresh it every time.
A solution I found is to hook that into the Cookies middleware.
Here's the general flow:
On every request, use the Cookies middleware events to inspect the access token
If it's close to its expiration time, request a new one
Replace the new access and refresh tokens in the ClaimsIdentity
Instruct the Cookies middleware to renew the session cookie so it contains the new tokens
What I like with this approach is that in your MVC code, you're pretty much guaranteed to always have a valid access token, unless refereshing the token keeps failing several times in a row.
What I don't like is that it's very tied to MVC - more specifically the Cookies middleware - so it's not really portable.
You can have a look at this GitHub repo I put together. It indeed uses IdentityModel as this takes care of everything and hides most of the complexity of the HTTP calls you'd have to make to IdentityServer.

I created a solution based on a action filter togheter with the OIDC middleware in ASP.NET Core 2.0.
AJAX requests will also go via the action filter hence update the access token/refresh token.
https://gist.github.com/devJ0n/43c6888161169e09fec542d2dc12af09

I found two possible solutions, both are equal but happens at different times in the OIDC middleware. In the events I extract the access token expire time value and store it as a claim which later can be used to check if it's OK to call an Web API with the current access token or if I rather should request a new access token using the refresh token.
I would appreciate if someone could give any input on which of these events are preferable to use.
var oidcOptions = new OpenIdConnectOptions
{
AuthenticationScheme = appSettings.OpenIdConnect.AuthenticationScheme,
SignInScheme = appSettings.OpenIdConnect.SignInScheme,
Authority = appSettings.OpenIdConnect.Authority,
RequireHttpsMetadata = _hostingEnvironment.IsDevelopment() ? false : true,
PostLogoutRedirectUri = appSettings.OpenIdConnect.PostLogoutRedirectUri,
ClientId = appSettings.OpenIdConnect.ClientId,
ClientSecret = appSettings.OpenIdConnect.ClientSecret,
ResponseType = appSettings.OpenIdConnect.ResponseType,
UseTokenLifetime = appSettings.OpenIdConnect.UseTokenLifetime,
SaveTokens = appSettings.OpenIdConnect.SaveTokens,
GetClaimsFromUserInfoEndpoint = appSettings.OpenIdConnect.GetClaimsFromUserInfoEndpoint,
Events = new OpenIdConnectEvents
{
OnTicketReceived = TicketReceived,
OnUserInformationReceived = UserInformationReceived
},
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = appSettings.OpenIdConnect.NameClaimType,
RoleClaimType = appSettings.OpenIdConnect.RoleClaimType
}
};
oidcOptions.Scope.Clear();
foreach (var scope in appSettings.OpenIdConnect.Scopes)
{
oidcOptions.Scope.Add(scope);
}
app.UseOpenIdConnectAuthentication(oidcOptions);
And here is some event examples I'm can choose among:
public async Task TicketReceived(TicketReceivedContext trc)
{
await Task.Run(() =>
{
Debug.WriteLine("TicketReceived");
//Alternatives to get the expires_at value
//var expiresAt1 = trc.Ticket.Properties.GetTokens().SingleOrDefault(t => t.Name == "expires_at").Value;
//var expiresAt2 = trc.Ticket.Properties.GetTokenValue("expires_at");
//var expiresAt3 = trc.Ticket.Properties.Items[".Token.expires_at"];
//Outputs:
//expiresAt1 = "2016-12-19T11:58:24.0006542+00:00"
//expiresAt2 = "2016-12-19T11:58:24.0006542+00:00"
//expiresAt3 = "2016-12-19T11:58:24.0006542+00:00"
//Remove OIDC protocol claims ("iss","aud","exp","iat","auth_time","nonce","acr","amr","azp","nbf","c_hash","sid","idp")
ClaimsPrincipal p = TransformClaims(trc.Ticket.Principal);
//var identity = p.Identity as ClaimsIdentity;
// keep track of access token expiration
//identity.AddClaim(new Claim("expires_at1", expiresAt1.ToString()));
//identity.AddClaim(new Claim("expires_at2", expiresAt2.ToString()));
//identity.AddClaim(new Claim("expires_at3", expiresAt3.ToString()));
//Todo: Check if it's OK to replace principal instead of the ticket, currently I can't make it work when replacing the whole ticket.
//trc.Ticket = new AuthenticationTicket(p, trc.Ticket.Properties, trc.Ticket.AuthenticationScheme);
trc.Principal = p;
});
}
I also have the UserInformationReceived event, I'm not sure if I should use this instead of the TicketReceived event.
public async Task UserInformationReceived(UserInformationReceivedContext uirc)
{
await Task.Run(() =>
{
Debug.WriteLine("UserInformationReceived");
////Alternatives to get the expires_at value
//var expiresAt4 = uirc.Ticket.Properties.GetTokens().SingleOrDefault(t => t.Name == "expires_at").Value;
//var expiresAt5 = uirc.Ticket.Properties.GetTokenValue("expires_at");
//var expiresAt6 = uirc.Ticket.Properties.Items[".Token.expires_at"];
//var expiresIn1 = uirc.ProtocolMessage.ExpiresIn;
//Outputs:
//expiresAt4 = "2016-12-19T11:58:24.0006542+00:00"
//expiresAt5 = "2016-12-19T11:58:24.0006542+00:00"
//expiresAt6 = "2016-12-19T11:58:24.0006542+00:00"
//expiresIn = "60" <-- The 60 seconds test interval for the access token lifetime is configured in the IdentityServer client configuration settings
var identity = uirc.Ticket.Principal.Identity as ClaimsIdentity;
//Keep track of access token expiration
//Add a claim with information about when the access token is expired, it's possible that I instead should use expiresAt4, expiresAt5 or expiresAt6
//instead of manually calculating the expire time.
//This claim will later be checked before calling Web API's and if needed a new access token will be requested via the IdentityModel2 library.
//identity.AddClaim(new Claim("expires_at4", expiresAt4.ToString()));
//identity.AddClaim(new Claim("expires_at5", expiresAt5.ToString()));
//identity.AddClaim(new Claim("expires_at6", expiresAt6.ToString()));
//identity.AddClaim(new Claim("expires_in1", expiresIn1.ToString()));
identity.AddClaim(new Claim("expires_in", DateTime.Now.AddSeconds(Convert.ToDouble(uirc.ProtocolMessage.ExpiresIn)).ToLocalTime().ToString()));
//identity.AddClaim(new Claim("expires_in3", DateTime.Now.AddSeconds(Convert.ToDouble(uirc.ProtocolMessage.ExpiresIn)).ToString()));
//The following is not needed when to OIDC middleware CookieAuthenticationOptions.SaveTokens = true
//identity.AddClaim(new Claim("access_token", uirc.ProtocolMessage.AccessToken));
//identity.Claims.Append(new Claim("refresh_token", uirc.ProtocolMessage.RefreshToken));
//identity.AddClaim(new Claim("id_token", uirc.ProtocolMessage.IdToken));
});
}

Related

Initiate and store multiple OAuth2 external authentication challenges in a ASP.NET Core MVC application?

I can authenticate against two separate OAuth authentication schemes but it seems only one can be active at a time. I'd like to compare data from two separate SaaS applications and therefore I need two separate Bearer tokens. How can I initiate multiple OAuth challenges when the user loads the application and then store the Bearer Tokens for each? (e.g. in the Context.User cookie?)
My Startup.cs is as follows:
public void ConfigureServices(IServiceCollection services)
{
services.AddRouting();
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.LoginPath = "/signin";
options.LogoutPath = "/signout";
})
.AddScheme1 (options =>
{
options.ClientId = Configuration["Scheme1:ClientId"];
options.ClientSecret = Configuration["Scheme1:ClientSecret"];
options.Scope.Add("scope1");
options.SaveTokens = true;
})
.AddScheme2(options =>
{
options.ClientId = Configuration["Scheme2:ClientId"];
options.ClientSecret = Configuration["Scheme2:ClientSecret"];
options.Scope.Add("scope1");
options.SaveTokens = true;
});...
}
The AuthenticationController calls the Challenge overloaded method from the Microsoft.AspNetCore.Mvc.Core assembly that takes a single provider/scheme (passing multiple schemes in the overloaded method seems to be ignored).
[HttpGet("~/signin")]
public async Task<IActionResult> SignIn() => View("SignIn", await HttpContext.GetExternalProvidersAsync());
[HttpPost("~/signin")]
public async Task<IActionResult> SignIn([FromForm] string provider)
{
...
return Challenge(new AuthenticationProperties { RedirectUri = "/" }, provider);
}
Presumably, you'd prompt the user to sign-into one external application, redirect back to the home page, and then prompt them to sign-into the second one, and then allow them to start using the application proper.
If this is possible - e.g. using a "multiple" Auth cookie - how then would I fetch the correct Bearer token and User values for the given scheme? Currently you just seem to fetch the token with a generic "access_token" name and unique user values:
string accessToken = await HttpContext.GetTokenAsync("access_token");
string userID = User.FindFirstValue(ClaimTypes.NameIdentifier);
There does seem to be some information here regarding using a SignInManager but I'm unable to determine if this is applicable to this problem.
I would aim to start with a standard architecture where the user authenticates with the one and only app, and gets only one set of tokens, issued by your own Authorization Server.
SaaS DATA - OPTION 1
Does the user need to get involved in these connections or can you use a back end to back end flow here?
Your C# code could connect to the SaaS provider with the client credentials grant, using the client ID and secret that you reference above. Provider tokens would then be cached in memory, then used by the back end code to return provider data to the UI. This is a simple option to code.
SaaS DATA - OPTION 2
If the user needs to get involved, because the data is owned by them, you might offer UI options like this. After each click the user is redirected again, to get a token for that provider.
View provider 1 data
View provider 2 data
Aim to emulate the embedded token pattern, where the provider tokens are available as a secondary credential. How you represent this could vary, eg you might prefer to store provider tokens in an encrypted cookie.
CODING AND SIMPLICITY
I would not mix up provider tokens with the primary OAuth mechanism of signing into the app and getting tokens via the .NET security framework, which typically implements OpenID Connect. Instead I would aim to code the SaaS connections on demand.
I think you will find it easier to code the SaaS connections with a library approach, such as Identity Model. This will also help you to deal with SaaS provider differences more easily.
I assume you use OIDC schemes.
First, you need to add two cookie schemes, one for each OIDC authentication scheme as their sign in scheme and set their callback path to different values to stop them competing:
services.AddAuthentication()
.AddCookie("Cookie1")
.AddCookie("Cookie2")
.AddOpenIdConnect("OidcScheme1", opt =>
{
opt.SignInScheme = "Cookie1";
opt.CallbackPath = "/signin-oidc-scheme1";
opt.SaveTokens = true;
})
.AddOpenIdConnect("OidcScheme2", opt =>
{
opt.SignInScheme = "Cookie2";
opt.CallbackPath = "/signin-oidc-scheme2";
opt.SaveTokens = true;
});
This will instruct the OIDC handler to authenticate the user from corresponding cookie.
Second, you need a controller action to challenge the user against each OIDC scheme:
[HttpGet]
[Route("login")]
[AllowAnonymous]
public IActionResult Login([FromQuery]string scheme,
[FromQuery]string? returnUrl)
{
return Challenge(new AuthenticationProperties
{
RedirectUri = returnUrl ?? "/"
}, scheme);
}
From your web app, you need to send the user to the Login endpoint twice with different scheme values:
GET /login?scheme=OidcScheme1
GET /login?scheme=OidcScheme2
Or chain them together using the returnUrl:
GET /login?scheme=OidcScheme1&returnUrl=%2Flogin%3Fscheme%3DOidcScheme2
Once signed in, there should be two cookies in the browser window, for example:
To authenticate the user and restore both identities from two cookies, you can use authorization policy:
[HttpGet]
[Authorize(AuthenticationSchemes = "OidcScheme1,OidcScheme2")]
public async Task<IActionResult> SomeOperation()
{
// Two identities, one from each cookie
var userIdentities = User.Identities;
...
}
To get access token from each authentication scheme, use the method you discovered (GetTokenAsync) and specify authentication scheme:
var token1 = await HttpContext.GetTokenAsync("OidcScheme1", "access_token");
var token2 = await HttpContext.GetTokenAsync("OidcScheme2", "access_token");
It is possible that the access token is not returned from the token endpoint depends on the response_type you used. If this is the case, try set the OpenIdConnectionOptions.ResponseType to OpenIdConnectResponseType.Code and make sure the scope is correct.
I encountered a similar problem where we had microservices that are/were shared across multiple products with each product having a separate IDP tenant (essentially a different token issuer). Perhaps a similar approach might work for your scenario...
The following link helped me with a solution - see here.
Basically I defined a smart authentication scheme
var builder = services.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = "smart";
//...
});
Then in the smart policy scheme definition, I decode the JWT coming in to work out the issuer from the iss claim in the JWT, so that I can forward to the correct location for JWT bearer authentication.
builder.AddPolicyScheme("smart", "smart", options =>
{
options.ForwardDefaultSelector = context =>
{
var jwtEncodedString = context.Request.Headers["Authorization"].FirstOrDefault()?.Substring(7);
if (string.IsNullOrEmpty(jwtEncodedString))
return settings.Tenants.First().Key; // There's no authorization header, so just return any.
var token = new JwtSecurityToken(jwtEncodedString: jwtEncodedString);
var issuer = token.Claims.First(c => c.Type == "iss").Value?.TrimEnd('/');
var tenant = settings.Tenants
.Where(pair => pair.Value.Issuer.TrimEnd('/') == issuer)
.Select(pair => pair.Key).FirstOrDefault();
if (tenant == null)
throw new AuthorizationException($"Failed to locate authorization tenant with issuer '{issuer}'.");
return tenant;
};
});
Note: settings.Tenants is just an array of whitelisted tenants (from appsettings) that I configure as follows:
foreach (var tenant in settings.Tenants)
builder.AddJwtBearer(tenant.Key, options => Configure(options, tenant.Value, defaultJwtBearerEvents));

MVC sign in to IdentityServer4 without redirect

So I'm trying to sign in users from my ASP.NET Core 2.2 MVC app without redirecting them to IdentityServer4. So far I'm able to use IS4's ResourceOwnerPassword flow to get a token and get a token with RequestPasswordTokenAsync, but even after I set my client with the access token it's not authenticating my app.
Controller:
public async Task<IActionResult> Login()
{
var client = new HttpClient();
var response = await client.RequestPasswordTokenAsync(new PasswordTokenRequest
{
Address = "http://localhost:5000/connect/token",
ClientId = "mvc3",
ClientSecret = "secret",
UserName = "LegitUsername",
Password = "VeryLegitamitePassword",
Scope = "api1"
});
client.SetBearerToken(response.AccessToken);
return Redirect("/");
}
Current behavior: Token is granted, but my header still has the "Login" button
Expected behavior: Token is granted, "Logout" button is displayed
The current behavior suggests that I haven't been authenticated even though I'm holding the token. I know I'm missing something to pass the token to the HttpContext to authenticate my app, but can't figure out what. Any help would be appreciated.
Well, you do not log in the user. You request an access token from id4. Normally you request an access token to add it to a request (as you did) to access a resource.
Please refer to the examples: https://github.com/IdentityServer/IdentityServer4.Samples/tree/master/Clients/src
There are several examples implementations for mvc.

MVC App using Azure AD with ADAL 3 - Authentication Cookie expires after 1 hour

I work on an MVC Web Application using Azure AD with OAuth 2 and Open ID Connect for Authorization of users.
Per documentation tokens are refreshed automatically when a token expires after 60 minutes (which is fine).
Now the problem is, to acquire a token I need to know the currently authenticated user which is stored in a cookie. The code to acquire a Token is like this:
public async Task<AuthenticationToken> GetTokenForApplication(string resourceID)
{
string signedInUserID = ClaimsPrincipal.Current.SignedinUserId();
var tenantID = ClaimsPrincipal.Current.TenantId();
string userObjectID = ClaimsPrincipal.Current.SignedinUserObjectId();
// get a token for the Graph without triggering any user interaction (from the cache, via multi-resource refresh token, etc)
ClientCredential clientcred = new ClientCredential(Config.ClientId, Config.AppKey);
// initialize AuthenticationContext with the token cache of the currently signed in user, as kept in the app's database
AuthenticationContext authenticationContext = new AuthenticationContext(string.Format("{0}{1}", Config.AadInstance, tenantID), new ADALTokenCache(signedInUserID));
AuthenticationResult authenticationResult = await authenticationContext.AcquireTokenSilentAsync(resourceID, clientcred, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
var token = new AuthenticationToken(authenticationResult.AccessToken) { ExpiresOn = authenticationResult.ExpiresOn };
return token;
}
Now I am in the dilemma, that the ClaimsPrincipal.Current.SignedinUserId() method call throws a null reference exception. When I inspect the ClaimsPrincipal.Current object, no data about the logged in user is available. But this is the Information needed to renew / request a token.
What is the best practice in an MVC Web App? Is there a way to extend the validity of the cookie or is there any way to reauthenticate the current user without redirecting to the root page of the web application?
After doing more research I have found these two pages which describe some options to deal with my problem pretty good:
Controlling a Web App’s session duration
and ASP.NET-Identity-Cookie-Authentication-Timeouts
are these good approaches?
After doing more research I have found these two pages which describe some options to deal with my problem pretty good:
Controlling a Web App’s session duration
and ASP.NET-Identity-Cookie-Authentication-Timeouts
are these good approaches?

Using OAuth2 refresh tokens in an ASPMVC application

Scenario
I am using the OWIN cookie authentication middleware to protected my site as follows
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
ExpireTimeSpan = new TimeSpan(0, 20, 0),
SlidingExpiration = true
});
}
On login, I use the resource owner password flow to call my token service and retrieve both an access and refresh token.
I then add the refresh token, access token and the time the access token expires to my claims and then call the following to to persist this information to my authentication cookie.
HttpContext
.GetOwinContext()
.Authentication
.SignIn(claimsIdentityWithTokenAndExpiresAtClaim);
Then before calling any service, I can retrieve the access token from my current claims and associate it with the service call.
Problem
Before calling any service, I should really check if the access token has expired and if so use the refresh token to get a new one. Once I have a new access token, I can call the service, however I then need to persist a new authentication cookie with the new access token, refresh token and expiry time.
Is there any nice way to do this transparently to the caller of the service?
Attempted solutions
1) Check before calling every service
[Authorize]
public async Task<ActionResult> CallService(ClaimsIdentity claimsIdentity)
{
var accessToken = GetAccessToken();
var service = new Service(accessToken).DoSomething();
}
private string GetAccessToken(ClaimsIdentity claimsIdentity) {
if (claimsIdentity.HasAccessTokenExpired())
{
// call sts, get new tokens, create new identity with tokens
var newClaimsIdentity = ...
HttpContext
.GetOwinContext()
.Authentication
.SignIn(newClaimsIdentity);
return newClaimsIdentity;
} else {
return claimsIdentity.AccessToken();
}
}
This would work, but it's not sustainable. Also I could not longer use dependency injection to inject my services as the service needs the access token at call time and not construction time.
2) Use some kind of service factory
Before create the service with its access token, it would perform the refresh if needed. The issue it that I'm not sure how I can get the factory to return both a service and also set the cookie within the implementation in a nice way.
3) Do it in a action filter instead.
The thinking is that the session cookie has a 20 minutes sliding expiry. On ever page request, I can check if the access token is more than halfway through it's expiry (ie. if the access token has an expiry of an hour, check to see if it has less than 30 minutes to expiry). If so, perform the refresh. The services can rely on the access token not being expired. Lets say you hit the page just before the 30 minutes expiry and stayed on the page for 30 minutes, the assumption is the session timeout (20 minutes idle) will kick in before you call the service and you wil be logged off.
4) Do nothing and catch the exception from calling a service with an expired token
I couldn't figure out a nice way to get a new token and retry the service call again without having to worry about side effects etc. Plus it would be nicer to check for expiration first, rather than wait for the time it takes the service to fail.
Neither of these solutions are particularly elegant. How are others handling this?
Update:
I spent some time looking in to various options on how to implement this efficiently at the server side with your current setup.
There are multiple ways (like Custom-Middleware, AuthenticationFilter, AuthorizationFilter or ActionFilter) to achieve this on the server side. But, looking at these options I would lean towards AuthroziationFilter. The reason are:
AuthroziationFilters gets executed after AuthenticationFilters. So, it is early in the pipe line that you can make a decision of whether to get a new token or not based on expiry time. Also, we can be sure that the user is authenticated.
The scenario we are dealing with is about access_token which is related to authorization than the authentication.
With filters we have the advantage of selectively using it with actions that are explicitly decorated with that filter unlike the custom middleware which gets executed with every request. This is useful as there will be cases where you do not want to get a refreshed token (since the current one is still valid as we are getting new token well before the expiration) when you are not calling any service.
Actionfilters are called little late in the pipeline also we do not have a case for after executing method in an action filter.
Here is a question from Stackoverflow that has some nice details on how to implement an AuthorizationFilter with dependency injection.
Coming to attaching the Authorization header to the service:
This happens inside your action method. By this time you are sure that the token is valid. So I would create an abstract base class that instantiates a HttpClient class and sets the authorization header. The service class implements that base class and uses the HttpClient to call the web service. This approach is clean as consumers of your setup do not have to know how and when you are getting and attaching the token to the outgoing request for web service. Also, you are getting and attaching the refreshed access_token only when you are calling the web service.
Here is some sample code (please note that I haven't fully tested this code, this is to give you an idea of how to implement):
public class MyAuthorizeAttribute : FilterAttribute, IAuthorizationFilter
{
private const string AuthTokenKey = "Authorization";
public void OnAuthorization(AuthorizationContext filterContext)
{
var accessToken = string.Empty;
var bearerToken = filterContext.HttpContext.Request.Headers[AuthTokenKey];
if (!string.IsNullOrWhiteSpace(bearerToken) && bearerToken.Trim().Length > 7)
{
accessToken = bearerToken.StartsWith("Bearer ") ? bearerToken.Substring(7) : bearerToken;
}
if (string.IsNullOrWhiteSpace(accessToken))
{
// Handle unauthorized result Unauthorized!
filterContext.Result = new HttpUnauthorizedResult();
}
// call sts, get new token based on the expiration time. The grace time before which you want to
//get new token can be based on your requirement. assign it to accessToken
//Remove the existing token and re-add it
filterContext.HttpContext.Request.Headers.Remove(AuthTokenKey);
filterContext.HttpContext.Request.Headers[AuthTokenKey] = $"Bearer {accessToken}";
}
}
public abstract class ServiceBase
{
protected readonly HttpClient Client;
protected ServiceBase()
{
var accessToken = HttpContext.Current.Request.Headers["Authorization"];
Client = new HttpClient();
Client.DefaultRequestHeaders.Add("Authorization", accessToken);
}
}
public class Service : ServiceBase
{
public async Task<string> TestGet()
{
return await Client.GetStringAsync("www.google.com");
}
}
public class TestController : Controller
{
[Authorize]
public async Task<ActionResult> CallService()
{
var service = new Service();
var testData = await service.TestGet();
return Content(testData);
}
}
Please note that using the Client Credentials flow from OAuth 2.0 spec is the approach we need to take when calling an API. Also, the JavaScript solution feels more elegant for me. But, I am sure you have requirements that might be forcing you to do it the way you want. Please let me know if you have any questions are comments. Thank you.
Adding access token, refresh token and expires at to the claims and passing it to the following service may not be a good solution. Claims are more suited for identifying the user information/ authorization information. Also, the OpenId spec specifies that the access token should be sent as part of the authorization header only. We should deal with the problem of expired/ expiring tokens in a different way.
At the client, you can automate the process of getting a new access token well before its expiration using this great Javascript library oidc-client. Now you send this new and valid access token as part of your headers to the server and the server will pass it to the following APIs. As a precaution, you can use the same library to validate the expiration time of the token before sending it to the server. This is much cleaner and better solution in my opinion. There are options to silently update the token without the user noticing it. The library uses a an iframe under the hood to update the token. Here is a link for a video in which the author of the library Brock Allen explains the same concepts. The implementation of this functionality is very straightforward. Examples of how the library can be used is here. The JS call we are interested in would look like:
var settings = {
authority: 'http://localhost:5000/oidc',
client_id: 'js.tokenmanager',
redirect_uri: 'http://localhost:5000/user-manager-sample.html',
post_logout_redirect_uri: 'http://localhost:5000/user-manager-sample.html',
response_type: 'id_token token',
scope: 'openid email roles',
popup_redirect_uri:'http://localhost:5000/user-manager-sample-popup.html',
silent_redirect_uri:'http://localhost:5000/user-manager-sample-silent.html',
automaticSilentRenew:true,
filterProtocolClaims: true,
loadUserInfo: true
};
var mgr = new Oidc.UserManager(settings);
function iframeSignin() {
mgr.signinSilent({data:'some data'}).then(function(user) {
log("signed in", user);
}).catch(function(err) {
log(err);
});
}
The mgr is an instance of
FYI, we can achieve similar functionality at the server by building a custom middleware and using it as part of the request flow in a MessageHandler. Please let me know if you have any questions.
Thanks,
Soma.

MVC5 app using Azure Active Directory + REST API -- to auth for PowerBI / O365

I'm trying to adapt the WebAPI example shown here, to use in MVC5:
https://msdn.microsoft.com/en-US/library/dn931282.aspx#Configure
I have a regular AccountController based login system, but I also need the user to login via OAuth into PowerBI, so I can pull datasets via the PowerBI REST API. However, I'm gettting the ClaimsPrincipal.Current.FindFirst(..) to be null.
private static async Task<string> getAccessToken()
{
// Create auth context (note: token is not cached)
AuthenticationContext authContext = new AuthenticationContext(Settings.AzureADAuthority);
// Create client credential
var clientCredential = new ClientCredential(Settings.ClientId, Settings.Key);
// Get user object id
var userObjectId = ClaimsPrincipal.Current.FindFirst(Settings.ClaimTypeObjectIdentifier).Value;
// Get access token for Power BI
// Call Power BI APIs from Web API on behalf of a user
return authContext.AcquireToken(Settings.PowerBIResourceId, clientCredential, new UserAssertion(userObjectId, UserIdentifierType.UniqueId.ToString())).AccessToken;
}
It all works fine in the sample app (a WebAPI project). I've also configured the OWIN app.UseOpenIdConnectAuthentication stuff in Startup.Auth.cs.
It seems the issue is the only type of Claim I have in 'ClaimsPrincipal.Current' is a 'CookieAuthentication' - it is missing the http://schemas.microsoft.com/identity/claims/objectidentifier Claim.
Also...the Microsoft OAuth window never opens in the browser...however, the error is within the ActiveDirectory related code...that code shouldn't need an OAuth token in the first place, right?
The recommended way to do this is to use the code that the Open ID Connect middleware will automatically retrieve for you. There is relevant sample here:
https://github.com/AzureADSamples/WebApp-WebAPI-OpenIDConnect-DotNet
This sample uses OAuth to get a token for the AAD Graph API. I don't know PowerBI but I believe that this is exactly analogous to getting a token for PowerBI.
Pay attention in particular to this file:
https://github.com/AzureADSamples/WebApp-WebAPI-OpenIDConnect-DotNet/blob/master/TodoListWebApp/App_Start/Startup.Auth.cs
AuthorizationCodeReceived = (context) =>
{
var code = context.Code;
ClientCredential credential = new ClientCredential(clientId, appKey);
string userObjectID = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
AuthenticationContext authContext = new AuthenticationContext(Authority, new NaiveSessionCache(userObjectID));
AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, graphResourceId);
return Task.FromResult(0);
},
The code above is called on every successful authentication, and ADAL is used to retrieve a token to the Graph API. At this point the only reason to get a token for the Graph API is to exchange the short lived auth code for a longer lived refresh token and get that stored in the cache. That is why the 'result' is never used.
Later, in the following file, the cache is employed to retrieve the token and use it to access the graph:
https://github.com/AzureADSamples/WebApp-WebAPI-OpenIDConnect-DotNet/blob/master/TodoListWebApp/Controllers/UserProfileController.cs
string tenantId = ClaimsPrincipal.Current.FindFirst(TenantIdClaimType).Value;
string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
AuthenticationContext authContext = new AuthenticationContext(Startup.Authority, new NaiveSessionCache(userObjectID));
ClientCredential credential = new ClientCredential(clientId, appKey);
result = authContext.AcquireTokenSilent(graphResourceId, credential, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
This time the token is actually used.
Substitute PowerBI for Graph API in the sample and I think you should be good to go.
Note that one other thing to pay attention to is the cache implementation. This file contains an appropriately name NaiveSessionCache.
https://github.com/AzureADSamples/WebApp-WebAPI-OpenIDConnect-DotNet/blob/master/TodoListWebApp/Utils/NaiveSessionCache.cs
If you have multiple front ends you will need to implement your own, less naive, session cache so that all the front ends can share the same cache.
A potential workaround, at least for me, is to use the "native app" setup on Azure AD and follow this workflow, instead of the web app + oauth workflow:
https://msdn.microsoft.com/en-US/library/dn877545.aspx

Resources