In this documentation it gives a complete flow for a web application that calls a web API:
The web application executes a policy and the user completes the user experience.
Azure AD B2C returns an (OpenID Connect) id_token and an authorization code to the browser.
The browser posts the id_token and authorization code to the redirect URI.
The web server validates the id_token and sets a session cookie.
The web server asks Azure AD B2C for an access_token by providing it with the authorization code, application client ID, and
client credentials.
The access_token and refresh_token are returned to the web server.
The web API is called with the access_token in an authorization header.
The web API validates the token.
Secure data is returned to the web application.
Looking at 6. and using the code in the Azure-Samples repository active-directory-b2c-dotnet-webapp-and-webapi
, I cannot get the line
AuthenticationResult result = await confidentialClient.AcquireTokenByAuthorizationCode(Globals.Scopes, notification.Code).ExecuteAsync();
to return a refresh_token. It returns an IdToken and AccessToken but no RefreshToken.
By using my browser and Postman and following the steps in this document with the same B2C tenant and application I do get the refresh token as expected.
This question is similar to mine and the blog post mentioned in one of the answers provides a work around to the symptom of not having a refresh token but my question remains:
How can I get AcquireTokenByAuthorizationCode to return a refresh_token?
To get refresh token, your application should append offline_access as scope.
You mentioned like this msdn able to return you refresh token. It is because request already contain offline_access scope
&scope=openid%20offline_access
To get refresh token from active-directory-b2c-dotnet-webapp-and-webapi. You need to update Global.cs Scopes filed to include offline_access
public static string[] Scopes = new string[] { ReadTasksScope, WriteTasksScope, "offline_access" };
The offline_access scope is optional for web apps. It indicates that your app needs a refresh token for long-lived access to resources.
Go to web.config add below:
<add key ="api:OfflineAccessScope" value="offline_access "/>
And in Global.cs :
public static string OfflineAccessScope = ApiIdentifier + ConfigurationManager.AppSettings["api:OfflineAccessScope"];
public static string[] Scopes = new string[] { ReadTasksScope, WriteTasksScope, OfflineAccessScope};
Then the Globals.Scopes in AcquireTokenByAuthorizationCode will return refresh token.
Related
I am likely expecting or doing something that is not correct. Need some help in getting back to the right path.
Simple Usecase - How to configure a client so it can request a token with all the right scopes? currently I am running it via postman but actual client is going to be a react app using msal.
Setup:
App Registration in Azure.
API Permissions:
Microsoft.Graph --> email & User.Read
Exposed an API:
Scope URI: api://someguid
One Scope is Added : api//someguid/testscope
Net Core 6 API
AppSettings.Json
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Audience":"api//someguid"
"ClientId": "my-client-id",
"TenantId": "my-tenant-id"
},
"Graph": {
"BaseUrl": "https://graph.microsoft.com/v1.0",
"Scopes": "user.read,email"
}
}
Middleware
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(Configuration, Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi()
.AddMicrosoftGraph(Configuration.GetSection("Graph"))
.AddInMemoryTokenCaches();
This is how I am calling graph in Controller.
[Authorize]
public class AbcController: Controller
{
private readonly GraphServiceClient _graphClient;
public AbcController(GraphServiceClient graphClient)
{
_graphClient = graphClient;
}
[HttpGet("get-me")]
public async Task<ActionResult> GetSomeDetails()
{
var user = await _graphClient.Me.Request().GetAsync();
return null;
}
I run this via postman with Auth Code flow with PKCE, Here are the issues
When I set the Scope as : api//someguid/testscope
Call gets authenticated and the Token is acquired correctly in postman
The API get-me get authorized correctly
But the call _graphClient.Me.Request().GetAsync() throws throws a 500 error
Also, a direct call to https://graph.microsoft.com/v1.0/me in postman using the token
gives insufficient privilege error
When I set the scope as : api//someguid/testscope https://graph.microsoft.com/email
Call gets authenticated But the acquire token fails with incorrect scope
When I set the scope as : https://graph.microsoft.com/email https://graph.microsoft.com/user.read
Call gets authenticated and the acquire token is acquired
Direct call to https://graph.microsoft.com/v1.0/me works as expected
But now my API does not get Authorized and gives 401
Can someone suggest what am i missing in my setup or if i am doing something crazy wrong?
All i am looking to do is get my API authorized, and get the email address pulled from graph in the API, without explicitly re-acquiring the token or specifying my client secret in the API to build the graph client.
This was taken as an input to try and build my poc
https://learn.microsoft.com/en-us/azure/active-directory/develop/scenario-web-api-call-api-call-api?tabs=aspnetcore
Note that, one token can only be issued to one audience. You cannot acquire access token for multiple audience (Ex: custom API and MS Graph) in single call.
In your scenario, you need to make two separate requests for acquiring access tokens i.e., one for your API and other for Microsoft Graph.
I tried to reproduce the same in my environment via Postman and got below results
I registered one Azure AD application and added same API permissions as below:
Now I exposed one API named testscope same as you like below:
Make sure to select Single-page application while adding Redirect URIs to your application like below:
I acquired token successfully using Auth code flow with PKCE from Postman like below:
POST https://login.microsoftonline.com/<tenantID>/oauth2/v2.0/token
client_id:<appID>
grant_type:authorization_code
scope: api://someguid/testscope
code:code
redirect_uri: https://jwt.ms
code_verifier:S256
The above token won't work for calling Microsoft Graph /me endpoint and works only to authorize API based on its audience.
To check the audience of above token, decode it in jwt.ms like below:
To call /me endpoint for mail, you need to acquire token again with Microsoft graph scope without configuring client secret like below:
POST https://login.microsoftonline.com/<tenantID>/oauth2/v2.0/token
client_id:<appID>
grant_type:authorization_code
scope: https://graph.microsoft.com/.default
code:code
redirect_uri: https://jwt.ms
code_verifier:S256
The above token won't work for authorizing API whereas you can call Microsoft Graph based on its audience.
To check the audience of above token, decode it in jwt.ms like below:
When I call /me endpoint using above token, I got the results successfully with mail like below:
References:
Azure AD Oauth2 implicit grant multiple scopes by juunas
reactjs - azure msal-brower with nextJs: passing only one scope to the API
I'm working with a .Net Core Web API and a .Net Core Web MVC Application. They both use Azure AD B2C to authenticate users. But in order to get a response from a HttpRequest from the Web API I need to provide the JWT Access Token from B2C for the signed in user on my web MVC application. Is there a way to obtain this access token inside a controller using the authenticated "User".
I have tried accessing the claims of the signed in user but no luck there, I have also used jwt.ms to review that the B2C workflow works well and that the JWT token is being generated and it works as well. The MVC application is authenticating the user and the web API is working fine with a hardcoded token. I just need to obtain the access token from a signed in user rather than doing it hardcoded.
I expect to be able to get the B2C JWT access token so that I can later on pass it to the Web Api and be able to secure my requests.
After getting some help from the MS AzureADB2C.UI GitHub crew we were able to solve the issue. The issue was that the tokens aren't saved by default on the library, so we needed to configure OIDC to specify that the tokens have to be saved for future use within the application. And so here is the example code of the "Startup" configuration and the example of how to query the "JWT access token" from the controller.
Startup.cs:
services.Configure(AzureADB2CDefaults.OpenIdScheme, options => {
options.SaveTokens = true;
});
Controller:
string idToken = await HttpContext.GetTokenAsync("id_token");
More information on how was the issue solved can be found on the following link:
https://github.com/aspnet/AspNetCore/issues/11424
You can refer to this sample application.
It uses the ASP.NET Core Azure AD B2C middleware to authenticate the end user and MSAL.NET to acquire, cache, and refresh the access token.
The access token is acquired in the AzureADB2COpenIdConnectOptionsConfigurator class.
A code example for a controller method referencing the access token is here.
Is it the actual token string you need? If so, you can access the headers using the HttpContext within the controller? The HttpContext will have a collection of headers that were passed in
I am trying to create a new web application that will be hosted in AWS. This application needs to authenticate users from our Azure Active Directory via OAuth2. Here's what I have working so far and steps I used to get me there:
1) I can generate a "code" after user logs in from "login.microsoftonline.com". To accomplish this, I set up a new application in Azure AD that my web application directs users to for logging in. I have also set up the API application in Azure AD which I'm using as the "resource" parameter in the query string when I direct users to the login.microsoftonline.com endpoint
2) using the "code" generated from #1 above, I can generate my authorization token by calling the /token endpoint of my application. I'm able to do this by passing the same resource value (the url to my API that I ultimately want to use), the code, my client id, and my client secret
3) the token response from #2 above sends down token_type, expires_in, scope, access_token, refresh_token, and id_token properties all of which have values. I'm able to decode the id_token into a JWT and it is showing the correct user information for the logged in user in the claims object
4) HERE'S WHERE I'M STUCK I then try to make a call to my API application that is also registered in Azure AD using the access_token I obtain in #3 above passing that value in an "Authorization" header with that value being "Bearer xyz123......" If I don't put any authorization on the API application, I get results as I'd expect, if however, I put an [Authorize] attribute on the class or even the Get() method, I always get a 401 (unauthorized). I'm certain there's something else I need to wire up I'm just not sure what. I found this resource: https://learn.microsoft.com/en-us/azure/api-management/api-management-howto-protect-backend-with-aad but it talks of registering my API with api management and I don't think I need to do that (I definitely don't want to if I don't have to).
The api I'm creating will rely on being able to get the logged in user's identity which I'm assuming I can extract from the bearer token, I just don't know how...
any help and guidance will be greatly appreciated.
Edit to include working solution:
Here is what i used in my startup class per the accepted answer below. Note the "audience" value is the URL of the API end point that I'm calling that I need access to. The Tenant is a custom url that we have bound in our organization, if you don't supply a valid tenant value, you might get an exception that says "Response status code does not indicate success: 404 (Not Found)." You'll need the Azure Active Directory assembly which you can get from Nuget: Install-Package Microsoft.Owin.Security.ActiveDirectory
public partial class Startup
{
// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Audience = "https://myapi.azurewebsites.net/",
Tenant = "my.custom.url.com",
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = false
}
});
}
}
here are the post parameters I'm using to make the request from the application that needs access to receive my token.
body.Add(new KeyValuePair<string, string>("grant_type", "authorization_code"));
body.Add(new KeyValuePair<string, string>("code", code)); // code from response
body.Add(new KeyValuePair<string, string>("redirect_uri", "http://localhost:51015/redirect"));
body.Add(new KeyValuePair<string, string>("client_id", "xxxxxxx-8829-4294-b2c9-xxxxxxxxxx")); // client id of this application making the request
body.Add(new KeyValuePair<string, string>("client_secret", "PxxxxxxxxxxxxSnTJ4Uh63Voj+tkxxxxxxx="));
body.Add(new KeyValuePair<string, string>("resource", "https://myapi.azurewebsites.net/")); // same value goes here that is in the audience value of the api, this is also the same value that is passed in the resource parameter of the query string on the redirect to the login to obtain the "code" in the previous step
It depends on how you protect the web API. Normally, we can protect the web API using Azure AD using the code below:
public partial class Startup
{
// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Audience = ConfigurationManager.AppSettings["ida:Audience"],
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
TokenValidationParameters= new System.IdentityModel.Tokens.TokenValidationParameters {
ValidateIssuer=false
}
});
}
}
The Audience is the ClientId of app you register on the Azure portal. In this scenario, we use this app as both the client and resource. Then we canrequest the access token like request below:
POST: https://login.microsoftonline.com/{tenantId}/oauth2/token
resource={clientId}&client_id={clientId}&code={authorizationCode}&grant_type=authorization_code&redirect_uri={redirectUri}&client_secret={clientSecret}
This token should works for the authorization of web API which protect by the code above. More detail about protecting the web API, you can refer here.
Check in Fiddler for any additional details" related to the authentication failure, there could be details like "Invalid Audience", etc. You need to know the root cause of failure (401 error) to fix this.
I am using OWIN OpenID Connect Middleware to connect to Azure AD. I am able to authenticate the user successfully and redirect back to callback endpoint. I am a bit confused here as i am receiving only id_token & code in the response.
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
AuthenticationType = "Azure AD - TEST",
Caption = "azure AD",
SignInAsAuthenticationType = signInAsType,
ClientId = "some guid",
Authority = "https://sts.windows.net/idp",
ResponseType = OpenIdConnectResponseTypes.CodeIdToken,
RedirectUri = "https://localhost:44392/ExternalLogins/Callback/",
AuthenticationMode = AuthenticationMode.Active,
});
Callback Method :
[HttpPost]
[Route("ExternalLogins/Callback")]
[AllowAnonymous]
public async Task<IHttpActionResult> ExternalLoginCallback()
{
var content = await Request.Content.ReadAsStringAsync();
// I could see the content is a string with id_token, code , state etc.
//id_token is a JWT, so i can decode it and see the user claims and use them later
}
My Questions are :
Is Azure AD used for only authenticating the user ? What about authorizatoin ?
If i want to make calls to other APIs after authentication, how do i do that as i don't have access_token ?
I think i can exchange code with access_token but not sure which Azure endpoint i need to call to get access_token ?
What is the difference between AuthenticationMode.Active and AuthenticationMode.Passive ?
Azure AD can absolutely authorize a user and get your Access/Refresh tokens. It supports all oAuth 2.0 and OIDC flows.
You'll need to get an access token to make calls to an api. Let's say you want to call a /get endpoint on the MS Graph, you will stuff the access token into the body of the http request with the keyword Bearer ey... in front of it.
Additionally, you'll need to go into the Azure Portal and configure the delegated permissions you want to access.
The auth code is used to exchange for the access_token. I suggest checking out this protocol doc that shows you how to use all the endpoints. The short answer is you POST to the /token endpoint.
The difference between active and passive is a bit complex for a SO answer, I recommend reading this blog post about the differences.
I'll just add that if you want to see some sample code using Azure AD you can go to Azure AD Dev Guide or Azure AD code samples on Github.
I have an Owin based Web App and a backend Web API, they are authenticated against AAD and the workflow can be describe as below listed.
Web App authenticates end users against AAD using Federation Authentication.
Web App requests a JWT from AAD for accessing the backend Web API.
The main code for authenticating end users.
public void ConfigureAuth(IAppBuilder app)
{
// other code...
app.UseWsFederationAuthentication(new WsFederationAuthenticationOptions
{
Wtrealm = realm,
MetadataAddress = adfsMetadata
});
}
The main code for getting JWT for accessing the backend API:
internal async Task<string> GetAccessToken()
{
var authContext = new AuthenticationContext(authority);
var credential = new ClientCredential(clientId, appKey);
var result = await authContext.AcquireTokenAsync(apiId, credential);
// Here, what I wanted is to use the other overloaded method
// authContext.AcquireTokenAsync(apiId, credential, userAssertion);
// But to instantiate a UserAssertion instance, the only way is
// to use the constructor new UserAssertion(assertionString)
// and the assertionString should be in JWT format
// unfortunately, the assertionString from Ws-Federation auth is
// for sure in SAML2 format. So, the question is:
// Give I am using Ws-Federation auth protocal, How can I pass the
// user information in requesting a JWT to backend API resource?
return result.AccessToken;
}
Generally, the whole authentication workflow is OK, I can both authenticate end users and get JWT for accessing backedn APIs. But the problem is that there is no end user claims in the JWT. I am sure I should get users claims from the federation authentication result and then put them in the process of requesting the JWT. Unfortunately, with all methods, libraries and classes I didn't find a solution to do that.
BTW, https://github.com/Azure-Samples/active-directory-dotnet-webapp-webapi-openidconnect gives an example how to obtain a JWT with end user claims included, but the solution does not work with my scenario as I am using Federation authentication rather than OpenID Connect.
Edit
To make the question clear: in the web app, I would like to request a JWT token for accessing the backend web api by using the method AuthenticationContext.AcquireTokenAsync.
From my demo code, you can see I am using the AcquireTokenAsync(apiId, clientCredential) overloaded verion. But this version does not attach the end users claims inside. Actually what I needed is the AcquireTokenAsync(apiId, clientCredential, userAssertion) overloaded method.
However, to instantiate a UserAssertion, I need the user assertion string which is the AccessToken from user authentication result. Unfortunetaly, the UserAssertion class only accept JWT format assertion string, but the Ws-Federation authentication returns the SAML2 format assertion string, so I am not able to instantiate a UserAssertion instance.
So, my question is: given the condition that I am using Ws-Federation authentication protocol for authenticating an end user, in the backend how can I pass the user assertion information (it is in SAML2 format) to AAD for requesting a JWT for a backend api resource?
AAD provides "canned" claims. There are no claims rules to add other attributes to the token.
Refer: Supported Token and Claim Types.
If you want other attributes, you need to use the Graph API.