Inside an application that is running IdentityServer4, I want to get an access token so that that the app can access another service that requires authentication. I found this answer that addresses IdentityServer3. I want to know if there is a similar mechanism in IdentityServer4.
I've already tried searching the github repo for IssueClientToken but got no hits.
As was pointed out to me in this post, you can inject IdentityServerTools into your class and use the exposed methods to create access tokens for Identity Server extensibility points. For instance a short lived token for a resource api might look like this:
// generate short lived token
var accessToken = await _identityServerTools.IssueJwtAsync(500,
new Claim[] { new Claim(JwtClaimTypes.Audience, "resourceApi") });
Related
In our application, we authenticate users using AAD, but we authorize users using our own User and Role tables in our local SQL database.
With the old ADAL library, we were able to return users to our callback url, then from there we would grab the JWT token from the ADAL service, and send that token to our server. The server would then validate the token, decode it and grab the email address. We then used our SQL tables to return another JWT that contained the user's identity and all of their roles.
With MSAL, this still works if you use InteractionType.Popup. The response Observable from the loginPopup() method carries the AuthentiationResult, which has an idToken property and an accessToken property. You can easily grab the one you need and you're off to the races.
However, with InteractionType.Redirect we don't get the AuthenticationResult.
I have injected the msalService instance into the our callbackURL's component (called AuthCallbackComponent). I looked everywhere within the msalService for the tokens, but couldn't find them. I even looked in sessionStorage, where I've configured MSAL to cache the token. They are actually in there (under a couple of really funky keys), but not until later. Whether I use ngOnInit, ngAfterViewInit or ngAftercontentInit, the tokens are not there yet. It does work if I set a timeout of 1-2 seconds, but...no. You can never really rely on timeout delays being long enough for all of your users all the time.
We desire to use the Redirect workflow rather than the popup workflow, so it really would be ideal if we can just get the idToken from the MSAL instance.
I found this post here: Retrieve token using msal, which offers some possible solutions, but the suggestions aren't helpful. It talks about a custom MSAL Interceptor, but that seems wrong. That's typically the HTTP interceptor that adds your token to the headers of your service calls. It also says you can subscribe to the callback and "do something with the returned token", but assuming they mean the callback of the msalService.loginRedirect() method, well that is just wrong. It doesn't return anything at all.
Remember, that in the old ADAL library, this worked. Also it still works with InteractionType.Popup (which we can't use). I expect those tokens must be in the MSAL instance somewhere, or else there's a method we can override, a callback we can provide, etc.
Thanks for reading this longer post.
Buried deep within the 10K pages of Microsoft documntation on MSAL, I found this event reference:
https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-angular/docs/v2-docs/events.md#the-inprogress-observable
So I used their example code to come up with my solution, which was to inject the msalBroadcastService into my AuthCallbackComponent. There I subscribe to its msalSubject$, and when a LOGIN_SUCCESS event happens, I can get the accessToken and idToken from the event's payload. Here is some code:
ngOnInit(): void {
this.msalBroadcastService.msalSubject$.pipe(filter((msg: EventMessage) => msg.eventType === EventType.LOGIN_SUCCESS)).subscribe(msg => {
const idToken = (msg.payload as any).idToken;
// now I can call my service method, passing in the idToken
}
I'm working on an MVC web project which is using IdentityServer4 to authenticate users, the web app then uses an access token provided to a user by IdentityServer (authorization code flow) to call an API. The IdentityServer has been configured to use Azure AD as an external identity provider, which is the primary mechanism for users to login. That's all working great.
Once authenticated, I need to query the web app's database to determine:
If the user account is authorised to login
Retrieve claims about the user specific to the application
The IdentityServer docs (http://docs.identityserver.io/en/latest/reference/profileservice.html) suggest implementing the IProfileService interface for this, which I've done. I want the ProfileService to call the web app's API to retrieve the information about the user to avoid forcing the IdentityServer to need to know about/directly access the database. My problem however, is that calling the API though needs an access token.
Is it possible to retrieve the token for the current user inside the ProfileService's IsActiveAsync / GetProfileDataAsync methods? I can't find solid documentation that identifies if the token is even generated at that point. I'm also a total noob when it comes to authentication/authorization, it's a massive topic!
I had the idea of using the client credentials flow inside the ProfileService to call the API, just to populate that initial token. However, I don't know whether or not that's an absolutely terrible idea... or if there are any better concepts someone could refer me to that I could investigate.
Can anyone point me in the right direction?
Have a look at ITokenCreationService that is part of identityserver4. You can inject that service into your IProfileService implementation and then create a new bearer token with any claims you like.
For example:
protected readonly ITokenCreationService _tokenCreationService;
...
var token = new Token
{
AccessTokenType = AccessTokenType.Jwt,
Issuer = "https://my.identityserver.com",
Lifetime = (int)TimeSpan.FromMinutes(5).TotalSeconds,
Claims = GetClaimsNeededForApiCall()
};
string myToken = await _tokenCreationService.CreateTokenAsync(token);
...
This is not possible to retrieve the access_token for a user within ProfileService.
The profile service is called whenever IdentityServer needs to return claims about a user. This means if you try to generate a token for the user within ProfileService it will call the ProfileService again.
I'm having a little trouble following how API Access delegate permissions work with azure active directory. I feel like i'm probably misunderstanding a key aspect of how AAD works.
Here is my set up
I have a Web Application let’s call it WebApp. I have created
an AAD for the Web Application and registered with a AAD App ID. Let’s
call it App ID A
I have a Web Api let’s call it ApiService. I have also created an AAD for it and registered with a AAD App ID. Let’s all it App ID B.
In AAD App ID A, I have updated the clicked on the API Access ->
Required Permissions -> Add (App ID B ; Web API) permissions
I’ve updated the manaifest in the AAD App ID B, to give consent to
knownClientApplications to include the client ID of the Web App
I’ve also enable oauth2AllowImplicitFlow to be true for both App’s
manifest.
What I’m trying to do is, A user signs into the web application sign. When it signs in, the user is able to acquire a token for the specific Web App App ID A. The user should be able to use that token and have access the Api Service with App ID B. I thought by configuring the whole API Access -> Required Permissions within the Web Application it would give me delegate permission with the logged in user to communicate with the Api Service WebApi.
When I examine the JWT token, I notice that there is a claim for Microsoft Graph, but not for the ApiService. Shouldn’t I be seeing a claim?
When I try to use the token, it reacts with a 404 authentication error.
Any advice appreciated,
Thanks,
Derek
UPDATE
In response to #joonasw
I actually looked at the example you wrote when i started.
https://joonasw.net/view/aspnet-core-2-azure-ad-authentication
In the example, the web application is initialized with:
.AddOpenIdConnect(opts =>
{
Configuration.GetSection("OpenIdConnect").Bind(opts);
opts.Events = new OpenIdConnectEvents
{
OnAuthorizationCodeReceived = ctx =>
{
return Task.CompletedTask;
}
};
});
In the HomeController, there is code to retrieve the token for the graph api
private async Task<string> GetAccessTokenAsync()
{
string authority = _authOptions.Authority;
string userId = User.FindFirstValue("http://schemas.microsoft.com/identity/claims/objectidentifier");
var cache = new AdalDistributedTokenCache(_cache, _dataProtectionProvider, userId);
var authContext = new AuthenticationContext(authority, cache);
//App's credentials may be needed if access tokens need to be refreshed with a refresh token
string clientId = _authOptions.ClientId;
string clientSecret = _authOptions.ClientSecret;
var credential = new ClientCredential(clientId, clientSecret);
var result = await authContext.AcquireTokenSilentAsync(
"https://graph.microsoft.com",
credential,
new UserIdentifier(userId, UserIdentifierType.UniqueId));
return result.AccessToken;
}
From my understanding, when the user initially login to the web application it will trigger the OnAuthorizationCodeReceived() method where it will be using the clientId/clientSecret/resource of the web applicaiton. The token is stored in the distributed token cache under the key resource/client id.
In the example, GetAccessTokenAsync() is used to grab the token to access the graph API.
In my case, I was hoping to update that method to retrieve the token for the WebApi which has a different clientId/clientSecret/resoruce. In my case, it will AcquireTokenSilentAsync will throw an AdalTokenAcquisitionExceptionFilter because the token needed is not stored in the cache and in the AdalTokenAcquisitionExceptionFilter it will call try to reauthenticate
context.Result = new ChallengeResult();
which will redirect to the authentication page and then hits the AddOpenIdConnect() method. However, the openIdConnect is configured with the web app clientID/ClientSecret/Resource and will not store the new token properly. It will try to call GetAccessTokenAsync() again and the whole process will go in an infinite loop.
In the example, if you were to comment out the "Anthentication:resource" in app.settings, you will experience the same issue with the infinite loop. What happens is that you initially authenticate correctly with no resource specified. Then when you click on you try to get the token for microsoft graph which is a new resource, it can't find it in the cache and then tries to reauthenticate over and over again.
I also notice that the acquireAsyncAuthentication only returns a AuthenticationResult with a bearer tokentype. How would you get the refresh token in this case?
Any advice?
Thanks,
Derek
UPDATE (Solution)
Thanks to #jaanus. All you have to do is update the resource to the clientid of the web api and pass that into AcquireTokenSilentAsync. The web api id uri that you can get from the azure portal did not work.
Okay, so it seems there are multiple questions here. I'll try to make some sense of this stuff to you.
Adding the "Web App"'s client id to the "ApiService" knownClientApplications is a good idea.
It allows for consent to be done for both apps at the same time. This really only matters for multi-tenant scenarios though.
Now, your Web App will be acquiring access tokens at some point.
When it does, it must specify a resource parameter.
This parameter says to AAD which API you wish to call.
In the case of the "ApiService", you should use either its client id or Application ID URI (this is more common).
Depending on the type of your Web App, the access token is acquired a bit differently.
For "traditional" back-end apps, the Authorization Code Grant flow is usually used.
In this flow your back-end gets an authorization code after the user logs in, and your Web App can then exchange that code for the access token.
In the case of a front-end JavaScript app, you would use the Implicit Grant flow, which you have allowed (no need to enable it in the API by the way).
This one allows you to get access tokens directly from the authorization endpoint (/oauth2/authorize) without talking to the token endpoint as you usually have to.
You can actually get the access token right away after login in the fragment of the URL if you wish.
ADAL.JS makes this quite a lot easier for you if you are going in this route.
The reason you get the authentication error is because the access token is probably meant for Microsoft Graph API. You need to request an access token for your API.
An access token is always only valid for one API.
I have implemented an ASP.NET Core MVC Client using Hybrid flow, and I am wondering what the HttpContext.Authentication.GetTokenAsync("access_token") does.
If you need more background on my question:
The instructions for accessing an API from with an ASP.Net Core Client App Controller Action are generally as follows:
var accessToken = await HttpContext.Authentication.GetTokenAsync("access_token");
var client = new HttpClient();
client.SetBearerToken(accessToken);
var response = await client.GetAsync("http://localhost:5001/api/stuff");
There is magic in httpContext.Authentication.GetTokenAsync("access_token") :-)
I am wondering what this function might be doing. Is it decrypting the access token from a cookie in the MVC App Domain? ... from the ID4 Domain?
I am sorry but I have been unable to find sufficient documentation on what this does or finding the cookie the access token may be in. I have looked here:
https://learn.microsoft.com/en-us/aspnet/core/api/microsoft.aspnetcore.authentication.authenticationtokenextensions
Does anyone know what it does? A link to more thorough documentation is a totally appreciated answer.
TU!
You can store arbitrary tokens in your authentication cookie in and that method simply returns one with a given name. Actually setting that would have happened during the sign in process. So in short, it comes from the authentication cookie for your client application and would typically be set at the point of sign in using IdSrv4.
It's a way for the client webapp to confirm separately whether the user really did authenticate with IdentityServer (the user comes to your webapp, the webapp forwards the user to IdentityServer for authentication). The id_token comes back with the user to your webapp in a browser cookie, basically when your webapp sees that id token, it does an independent check with IdentityServer, and if it represents a valid user authentication, you'll get an access token back. You can use that access token to get user claims from the userinfo endpoint.
After all of the above, you can take one of the claims and use it as a key for your application's roles/permissions.
I have 2 web apis (A and B) on my b2c. Each one of them publishes their own permissions respectively (scopeA1, scopeA2) and (scopeB1, scopeB2).
On my web application (which already configured and have granted access permission on both apis and the 4 scopes), in order to get authorization code for both apis during authentication, I tried to set my OpenIdConnectAuthenticationOptionsin scope property to include the 4 scopes.
I got an error AADB2C90146: The scope 'scopeA1 scopeA2 scopeB1 scopeB2 openid offline_access' provided in request specifies more than one resource for an access token, which is not supported.
While if I specify only scopes for web api A or B, then it works as per this link
How can I get my web app to use both web apis even with granted permissions for both
Thanks for help
If the two web APIs are separate applications in Azure AD, then you need to request access tokens separately for them.
I'm not familiar with the sample you used as a starting point, but it looks like these lines are where you need to make your change:
// Retrieve the token using the provided scopes
ConfidentialClientApplication app = new ConfidentialClientApplication(authority, Startup.ClientId,
Startup.RedirectUri, credential,
new NaiveSessionCache(userObjectID, this.HttpContext));
AuthenticationResult result = await app.AcquireTokenSilentAsync(scope);
accessToken = result.Token;
You should create an app instance for each of your APIs, and acquire a token for each of them. Then, when you call the APIs somewhere else, use the correct access token in the Bearer authentication header.
I had the same issue and asked a similar question Extend MSAL to support multiple Web APIs
but i have not had an answer, basically to get around it in the short term i have made both my API's use the same authorization client ID + secret and therefore I can reuse the same scopes accross my APIS
its not what i want but if you want to use Azure AD B2C you need to get used to compromising for a while until the support is there
-- I would also say you are using an older version of MSAL which i am also using, im waiting until the version 1 release before upgrading again.
The github talks about using this format
https://github.com/AzureAD/microsoft-authentication-library-for-dotnet
Step 1: Add MSAL to your Solution/Project
Right click on your project > Manage packages.
Select include prerelease > search msal.
Select the Microsoft.Identity.Client package > install.
Step 2: Instantiate MSAL and Acquire a Token
Create a new PublicClientApplication instance. Make sure to fill in your
app/client id
PublicClientApplication myApp = new PublicClientApplication(CLIENT_ID);
Acquire a token
AuthenticationResult authenticationResult = await
myApp.AcquireTokenAsync(SCOPES).ConfigureAwait(false);
Step 3: Use the token!
The access token can now be used in an HTTP Bearer request.