Acquiring an Access token by using JWT used for AzureBearerAuthentication - authentication

I have a WebApi app that is using Windows Azure Active Directory Bearer Authentication to authenticate users. After the user is authenticated, I want to query Azure's Graph Api to get more information about the user.
I have a solution that works, but seems very hacky. I read the Authorization header and strip out the bearer part, and then I use AquireToken to get the new token:
var authHeader = HttpContext.Current.Request.Headers["Authorization"];
var tokenMatch = Regex.Match(authHeader, #"(?<=^\s*bearer\s+).+$", RegexOptions.IgnoreCase);
var result = authInfo.AuthContext.AcquireToken(resourceId, authInfo.Credential,
new UserAssertion(tokenMatch.Value));
return result.AccessToken;
There has to be a better way, but I've tried AcquireToken many different overloads and this was the only way I could get it to work. I tried AcquireTokenSilent, which works in my client app because there is a token in the TokenCache, but when I try in the WebApi, there doesn't seem anywhere to implement a TokenCache.

That is indeed somewhat hacky :-) see https://github.com/AzureADSamples/WebAPI-OnBehalfOf-DotNet for a way in which you can retrieve the incoming token through the ClaimsPrincipal. It boils down to passing TokenValidationParameters = new TokenValidationParameters{ SaveSigninToken = true } in the options and retrieving in from your controller or filter code via
var bootstrapContext = ClaimsPrincipal.Current.Identities.First().BootstrapContext as System.IdentityModel.Tokens.BootstrapContext;

Related

ServiceStack API aspnet core with Azure AD B2C returns 401 for request even with bearer token

I have a working ServiceStack API that authenticates against a AzureAD tenant. We are trying to move this to start using Azure B2C. The application is build with c# and runs on net 5.0. I've managed to change the configuration to use the 'correct' config. I'm then using Postman to get my access token from my tenant suing the authorization code flow.
However, when i make a request to the api, the response is always a 401 status code.
Where in the servicestack code can I put a break point to see why this failure is happening? I have tried multiple places in our AppHostConfigurator.cs/AppHost.cs files, but the break points doesn't appear to display why a 401 is being sent back as a response. I'm sure it's something related to wrong claims/roles expected etc, maybe the Azure ADB2C application being setup incorrectly, but obviously i need to know exactly so that i can resolve.
I'm setting up the authentication like this:
private static void ConfigureAuthentication(IAppHost host)
{
var authProviders = new List<IAuthProvider> {new NetCoreIdentityAuthProvider(host.AppSettings)};
if (host.AppSettings.GetAllKeys().Contains("AzureAdB2C"))
{
var debugMode = host.AppSettings.Get(nameof(HostConfig.DebugMode), false);
var azureSettings = host.AppSettings.Get<AzureAdB2COptions>("AzureAdB2C");
var jwt = azureSettings.GetB2CJWTProviderReader(debugMode);
jwt.PopulateSessionFilter = (session, payload, request) =>
{
if (session.Email == null && payload.ContainsKey("upn") && payload["upn"].Contains("#"))
session.Email = payload["upn"];
if (session.UserName == null && payload.ContainsKey("unique_name"))
session.UserName = payload["unique_name"];
};
authProviders.Add(jwt);
}
var auth = new AuthFeature(() => new AuthUserSession(), authProviders.ToArray())
{
HtmlRedirect = "/account/signin",
HtmlLogoutRedirect = "/account/signout",
IncludeAssignRoleServices = false,
IncludeRegistrationService = false
};
// remove default service authentication services
auth.ServiceRoutes.Remove(typeof(AuthenticateService));
host.Plugins.Add(auth);
}
We are using swagger as well to call the API (which works as expected). This question is more about that requests that are submitted with a bearer token.
thanks
Please refer to this existing answer for examples of how to validate why a 3rd Party JWT Token is invalid with ServiceStack's JWT Auth Provider.

Identity Server 4 : Custom OIDC login callback to store tokens in a store

I'm currently building a Blazor app that needs authentication and retrieve acces_token to access an API. I'm also using refresh_token to avoid user relogin when browsing.
My current way to manage it is to register a custom httpClient in dependency injection system, retrieve HttpContext to check for access_token and if not good, call manually idsrv4 to retrieve new access & refresh tokens
The main issue is that by doing so, I cannot write into httpContext the new values (response has already begin) so I'll have to do it everytime, losing advantages of token lifetime.
My goal is to store all user informations & token informations within a custom store. To do so, I'll have to handle the point when tokens are retrieved, but I can't find a god place to fit in as callback ?
I've already tried the Events property within the AddOpenIdConnect options, but it doesn't seems any of them can fit my needs ?
Where can I put my custom callback logic after login ?
If you want to get and store the access token after client app get the access token , you can use OnTokenResponseReceived event :
options.Events = new OpenIdConnectEvents
{
OnTokenResponseReceived = ctx =>
{
var accessToken = ctx.TokenEndpointResponse.AccessToken;
var idToken = ctx.TokenEndpointResponse.IdToken;
return Task.CompletedTask;
},
};

identityserver 4 get current user's access_token

I am having trouble getting my current user's access_token.
Here is my setup:
QuickstartIdentityServer (QIS) in aspnet core, identity and EF storage
API (API) in NodeJs. Validates jwt tokens in header against QIS.
SPA angular app that works great with QIS and API and is out of the scope of this question
In a section of the QuickstartIdentityServer (QIS) site (user details page), I would like to call an API endpoint using an access_token to authenticate the request. I am struggling to retrieve the current user's access_token from my QIS site. Whenever I call HttpContext.GetTokenAsync("access_token") I get a null value. I have seen this section of IdSrv4 documentation: https://identityserver4.readthedocs.io/en/release/quickstarts/5_hybrid_and_api_access.html?highlight=gettokenasync but it seems to apply to an MVC client and not my own identity server.
Anyone could shed some light on how to get my user's access_token ?
Thanks
EDIT
Here is a starting point to try to explain better my issue:
https://github.com/IdentityServer/IdentityServer4.Samples/tree/release/Quickstarts/6_AspNetIdentity/src/IdentityServerWithAspNetIdentity
Starting from this QIS project, I would like to get the logged in user's access token. So for instance, if I edit HomeController to add this call:
public async Task<IActionResult> Index()
{
var accessToken = await HttpContext.GetTokenAsync("access_token");
return View(accessToken);
}
I would then be able to call my NodeJS API with this token in the Auth Header.
Hope this explains better my issue.
So I managed to authenticate myself w/ my API using a dedicated Client using client credentials grant and the following call to get an access_token:
var disco = await DiscoveryClient.GetAsync("http://localhost:5000");
var tokenClient = new TokenClient(disco.TokenEndpoint, clientId, clientSecret);
var tokenResponse = await tokenClient.RequestClientCredentialsAsync(scope);
Then I can add to my request header to API the access_token returned in tokenResponse:
using(var client = new HttpClient()) {
client.SetBearerToken(tokenResponse.AccessToken);
...
// execute request
}
The downside is that I can't "impersonate" the current currently logged on IS on API side.

Getting access token within Claims Transformer in ASP.NET Core

I'm developing a set of applications including an Identity Server using IdentityServer4, a .NET Core MVC app, a .NET Core WebAPI.
As part of a asp.net core mvc application I am using AddOpenIdConnect to do authentication and doing options.SaveTokens = true.
However, as part of Claims Transformation, in TransformAsync I would like to be able to have access to the access token provided by the identityserver. This is to be able to call a permissions endpoint on the api to populate the principal with claims that I can use to do authorization on my controllers etc.
If I call HttpContext.GetTokenAsync("access_token") I get a stackoverflowexception due to the infinite loop created by authenticate being called, which then calls TransformAsync again.
Is this a sound approach in the first place? Typically, TransformAsync is where I would populate application permissions. Is there any way of accessing the token without triggering the authenticate again?
Would appreciate any help as we're a bit stumped! Thanks
Edit: I've seen suggestions around doing transformations in the OnTicketReceived
event. It looks like I'd have access to the token through the properties in there. Is this a better place to do it?
I came across the same problem. My solution was,
Override JwtBearerEvents.TokenValidated event called by IdentityServer4.AccessTokenValidation middleware.
private Task OnTokenValidated(TokenValidatedContext tokenValidatedContext)
{
tokenValidatedContext.HttpContext.Items["access_token"] = (tokenValidatedContext.SecurityToken as JwtSecurityToken).RawData;
return Task.CompletedTask;
}
This will utilize HttpContext.Items collection which is request scoped. Now you can retreive this access token in TransformAsync method, like below.
var access_token = _httpContextAccessor.HttpContext.Items["access_token"] as string;
Please note that you need to inject IHttpContextAccessor to access HttpContext in ClaimsTransformer.
It has been many years since this question was posted, but if you are still looking for a solution to the issue, you can get the access token in the OnTokenValidated event.
OnTokenValidated = tokenValidatedContext =>
{
var handler = new JwtSecurityTokenHandler();
// get access token
var jsonToken = handler.ReadJwtToken(tokenValidatedContext.TokenEndpointResponse.AccessToken);
var claims = new List<Claim>();
claims.Add(new Claim("customClaimType", "customClaimValue"));
var appIdentity = new ClaimsIdentity(claims);
tokenValidatedContext.Principal.AddIdentity(appIdentity);
return Task.CompletedTask;
}
Reference : Adding Custom Claims During Authentication
I think you can inject the IAuthenticationHandlerProvider service and use following:
Get the authentication handler by scheme name.
get the AuthenticateResult by invoking AuthenticateAsync
get the token from the authentication properties
var token = string.Empty;
var handler = await Handlers.GetHandlerAsync(context, scheme); // i.e. "OIDC"
var result = await handler.AuthenticateAsync();
if(result?.Succeeded == true) {
token = result?.Properties?.GetTokenValue(tokenName);
}
haven't tested it but i think it should work

Cannot access UserInfo endpoint in IdentityServer4 doc example from Client

I'm testing out IdentityServer4, going through the documentation in order to learn more about OAuth2, OpenId Connect and Claim-based authentication, all of which I'm new at. However, some of the example code behaves weirdly and I can't figure out why...
So from my understanding, when given permission to access user data, the client can reach out to the UserInfo endpoint, which contains data such as claims, etc.
In IdentityServer4 there's even a setting
GetClaimsFromUserInfoEndpoint
that the documentation recommends we set to true.
So I'm following the IdentityServer4 startup guides and everything works perfectly until a point. This Quickstart contains the example code provided, although I'm assuming that I'm missing something obvious and seeing the code is not required.
Based on the openId Configuration page of the running server, the userinfo endpoint is located at
http://localhost:5000/connect/userinfo and when I try to access it via the browser I'm seeing a navbar which claims I'm logged in, but the body of the page is a signin prompt. Looks weird but I'm assuming that this is because I'm logged in at localhost:5000 (IdentityServer4), but I'm not sending the userId token which I got for the client on localhost:5002.
So I wrote the following code on my client app:
public async Task<IActionResult> GetData()
{
var accessToken = HttpContext.Authentication.GetTokenAsync("access_token").Result;
HttpClient client = new HttpClient();
client.SetBearerToken(accessToken);
var userInfo = await client.GetStringAsync("http://localhost:5000/connect/userinfo");
return Content(userInfo);
}
Here I know that GetTokenAsync("access_token") should work as it's used in other places in the example project by the client app that connect to an API. However, the responce I'm getting is again the layout page of IdentityServer and a log in prompt.
Any idea what my mistake is and how to access the UserInfo endpoint?
Edit: removed thread-blocking so that I don't show strangers shameful test code
Ok, so it turns out that this code should have a simplified version, namely:
UserInfoClient uic = new UserInfoClient("http://localhost:5000", idToken);
var result = await uic.GetAsync();
return Content(JsonConvert.SerializeObject(result.Claims));
Yet, the problem persists, even the encapsulated code inside UserInfoClient hits the brick wall of "no user endpoint data, just the layout for the example website".
It's probably little late to answer, but for anyone who is still stumbling upon this, try this ---
var accessToken = await HttpContext.Authentication.GetTokenAsync("access_token");
var client = new HttpClient();
client.SetBearerToken(accessToken);
var userInfoClient = new UserInfoClient("http://localhost:5000/connect/userinfo");
var response = await userInfoClient.GetAsync(accessToken);
var claims = response.Claims;
You can also get the list of claims on the client app like -
var claims = HttpContext.User.Claims.ToList();
without calling the endpoint.