.net core web API not getting authenticated even after generating bearer token from Azure AD using AzureADDefaults.BearerAuthenticationScheme - asp.net-core

I am developing a .net core Web API and I am trying to authenticate it using AZURE AD authentication.
I am following below configurations.:
1.In Startup.cs I have added authentication scheme as :AzureADDefaults.BearerAuthenticationScheme
services.AddAuthentication(AzureADDefaults.BearerAuthenticationScheme)
.AddAzureADBearer(options => { Configuration.Bind("AzureAd", options); });
2.In configure method of startup.cs I have added:
app.UseAuthentication();
3.In app.settings.json I have added following properties:
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"ClientId": "<MY client ID>",
"TenantId": "<My Tenant ID>",
"Issuer": "https://login.microsoftonline.com/<My Tenant ID>/v2.0",
"Domain": "<My Domain>",
"ConfigView": "MVC",
"CallbackPath": "/signin-oidc",
"ClientSecret": "<My Client Secret>"
}
I have added Authorize attribute on top of my controller
I have generated my Bearer token using following code:
static void Main(string[] args)
{
Program obj = new Program();
IRestResponse ARMtokenResponse = obj.GetARMAuthToken();
dynamic response = JsonConvert.DeserializeObject(ARMtokenResponse.Content);
Console.WriteLine(response["access_token"].ToString());
Console.ReadKey();
}
private IRestResponse GetARMAuthToken()
{
var client = new RestClient("https://login.microsoftonline.com/<MY TENANT ID>/oauth2/token"); //tenantid
client.Timeout = -1;
var request = new RestRequest(Method.POST);
request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
request.AddParameter("grant_type", "client_credentials");
request.AddParameter("client_id", "<My Client ID>");
request.AddParameter("client_secret", "<MY CLIENT SECRET>");
request.AddParameter("resource", "https://management.azure.com/");
IRestResponse response = client.Execute(request);
return response;
}
Further I am using this token generated, in postman/console app for calling the API but getting error in response header: Bearer error="invalid_token", error_description="The audience is invalid"
Please help me in this. I am stuck here

There are several issues in your codes :
You should acquire access token for web api , not acquiring access token for Azure Rest API (https://management.azure.com/) , your web api can't validate Azure Rest API's access token .
When acquiring token you are using Azure AD V1.0 endpoint , but when validating token you are using Azure AD V2.0 endpoint (Issuer) .
For Azure AD V1.0 , you can refer to code sample : Call a web API in an ASP.NET Core web app using Azure AD.
For Azure AD V2.0 , you can refer to code samples : Enable your Web Apps to sign-in users and call APIs with the Microsoft identity platform for developers , and follow the 4-WebApp-your-API scenario .

Related

How to configure Azure AD authentication in Hybrid ASP.NET Core MVC (backend) and Vuejs SPA (frontend)?

My application is a hybrid approach where use ASP.NET Core MVC as my backend. I have various controllers which my front end uses to pull data from our database and also to do API calls on MS Graph. I am using the following program.cs file to get the authentication initiated when a user first logs on to the site:
//authentication pipline
builder.Services.AddHttpContextAccessor();
var initialScopes = builder.Configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' ');
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(options =>
{
builder.Configuration.Bind("AzureAd", options);
options.Events = new OpenIdConnectEvents
{
//Tap into this event to add a UserID Claim to a new HttpContext identity
OnTokenValidated = context =>
{
//This query returns the UserID from the DB by sending the email address in the claim from Azure AD
string query = "select dbo.A2F_0013_ReturnUserIDForEmail(#Email) as UserID";
string connectionString = builder.Configuration.GetValue<string>("ConnectionStrings:DBContext");
string signInEmailAddress = context.Principal.FindFirstValue("preferred_username");
using (var connection = new SqlConnection(connectionString))
{
var queryResult = connection.QueryFirst(query, new { Email = signInEmailAddress });
var claims = new List<Claim>
{
new Claim("UserID", queryResult.UserID.ToString())
};
var appIdentity = new ClaimsIdentity(claims);
context.Principal.AddIdentity(appIdentity);
}
return Task.CompletedTask;
},
};
}).EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
.AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi"))
.AddInMemoryTokenCaches();
//Add Transient Services
builder.Services.AddTransient<IOneDrive, OneDrive>();
builder.Services.AddControllers(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
}).AddMicrosoftIdentityUI();
builder.Services.AddRazorPages().AddRazorPagesOptions(options =>
{
options.Conventions.AllowAnonymousToFolder("/Login");
options.Conventions.AuthorizeFolder("/");
options.Conventions.AuthorizeFolder("/files");
}).AddMicrosoftIdentityUI();
// Add the UI support to handle claims challenges
builder.Services.AddServerSideBlazor()
.AddMicrosoftIdentityConsentHandler();
builder.Services.AddRequiredScopeAuthorization();
In the Azure AD portal my application is registered as a web app. So when a user initially goes to the site they are redirected to https://login.microsoftonline.com/blahblah to get the login process started. This is automated by the Azure AD identity platform. Then once the login occurs they are redirected to localhost where the VueJS spa is loaded (localhost:43862). My spa uses various axios requests to the controllers and they pull data and vue router loads components. However, my issue is say the user needs to relog in because the cookie is expired or they logged out in another tab. The next axios request made by the expired session does not redirect the user to Azure login screen but instead results in an CORS error. So I need to get my axios requests to force the page redirect to Azure AD login screen (which probably is the worst idea since CORS policy is resulting in error) or have it return a redirect to localhost/login which is my own custom login screen with a button to Azure AD login and shouldnt impact CORS. So how do I intercept this Azure AD redirect to Azure AD login and replace with my own?
I have also tried to return a 401 error code so I could check for that in my axios request but to no avail it does nothing. If I put a breakpoint there it does hit this code but it does not change the status code of the response and I still get 302. My code for that was to try and add to the event :
OnRedirectToIdentityProvider = context =>
{
context.Response.StatusCode = 401;
return Task.CompletedTask;
}
My other ideas was maybe I should set my CORS policy to allow redirects from login.microsoft.com? Or would this be bad practice?
I can answer part of your question... First, for our API application which is protected by Azure AD, what the API should do is validating the request whether it contained a correct access token in the request header, if yes, give the response, if no, then give error like 401 or 403. A normal API application shouldn't have a UI to let users sign in. Anyway, if you want to expose an API in an MVC project, it's OK, but for API itself, it shouldn't have a UI.
Let's see sample below, I had a .net 6 web api project, and here's my program.cs:
using Microsoft.Identity.Web;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddMicrosoftIdentityWebApiAuthentication(builder.Configuration);
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
And it requires configurations in appsetting.json.
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"ClientId": "azure_ad_client_id",
"ClientSecret": "client_secret",
"Domain": "tenant_id",
"TenantId": "tenant_id",
//"Audience": "api://azure_ad_client_id_which exposed_api" // here I used the same azure ad app to expose API, so I can comment this property
},
And this is the Controller:
[ApiController]
[Route("[controller]")]
[Authorize]
public class WeatherForecastController : ControllerBase
{
[RequiredScope("Tiny.Read")]
[HttpGet]
public string Get()
{
return "world";
}
}
I had an Azure AD app, and I exposed an API like this:
I also add this API for the same Azure AD app.
Then let's do a test. When I call this API directly, I will get 401 error:
If I used an expired token within the request, I will also get 401 error:
But if I used a correct token(go to https://jwt.io to decode the token, we should see it containing correct scope, for me its "scp": "Tiny.Read",), I will get response:
And till now, the API part had finished. Let's see the client SPA. For SPA, you should integrate MSAL so that you can make your users to sign in via Azure AD, and generate the access token for calling MS graph API or your own API. The code for generating access token should be the same but you should set different scope for different API. In my scenario, my API required a scope Tiny.Read, then I should set in my client App.
Here's an screenshot for generating access token in react. You need to set the scope in your code.
Now you have the method to generate access token, you already know the API url. Then you can send request to call api, using AJAX, using fetch, or something else, sending an http request is ok. And in the calling api part, you also need to handle the response. If the response code is 401, then you need to do some logic, maybe redirect to the sign in page. And you said you had trouble here, you met CORS issue. I can't answer this part. I think it depends on how you redirect to Azure AD sign in page. I'm afraid you can take a look at this sample to learn how to sign in users and call graph api.

Azure b2c .Net core backend is always giving a 401, front react access token front end

I have a react front-end that sits behind a login with azure b2c, it allows the user to log in if they are registered in my tenant.
I then sent a access token to my backed which i receive using "react-aad-msal" :
signInAuthProvider.getAccessToken({
scopes: ["https://tenantname.onmicrosoft.com/api/scope_name"],
});
When i send this token via a bearer-auth header to my .net core 3.1 back-end i receive a 401.
I am using the addazureadbearer service:
services.AddAuthentication(AzureADDefaults.JwtBearerAuthenticationScheme)
.AddAzureADBearer(options => Configuration.Bind("AzureAd", options));
and my config section looks liek this:
"AzureAd": {
"Instance": "https://login.microsoftonline.com/tenantname",
"TenantId": "tenantid",
"ClientId": "clientid",
"Audience": "https://tenantname.onmicrosoft.com/api/api.access"
}
i believe it is doing some sort of cross check as i get a 401 not a error being able to connect to azure.
You need to Authenticate with b2c, not with AAD
{
"AzureAdB2C": {
"Instance": "https://<your tenant name>.b2clogin.com",
"ClientId": " your client id",
"Domain": "your tenant domain",
"TenantId": "your tenant id",
"SignUpSignInPolicyId": "your policy name"
}
Please refer to this github on .net core web API in b2c

Configure an asp.net core Web App to validate JWT token from ADFS

I'm using ADFS 2019 and the scenario is:
Client App (trusted, client id and client secret)
Web Api (acts both as a server and as a client)
Resource to access
My GOAL is:
By using postman get a token from ADFS and call a Web API launched locally that must validate this token. Once the token has been validated it must generate another token (on-behalf-of) to access the last resource.
I can successfully get the first token specifying:
- Grant Type: Client Credentials
- Access Token URL: https://MY-ADFS/adfs/oauth2/token
- Client ID
- Client Secret
How can i configure my asp.net core Web Application to validate and accept this token?
I have all the data:
Web App identifier (for the server), web app client id/secret (when it acts as a client) and ADFS metadata endpoint.
I'm trying to do something like this:
services
.AddAuthentication(o =>
{
o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Authority = "https://.../adfs";
options.Audience = "urn:microsoft:userinfo"; // taken from client token using jwt.io
options.MetadataAddress = "adfs metadata address";
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = "https://.../adfs/services/trust",
ValidAudiences = new List<string> { "web app id" },
};
But it does not work (unauthorized or internal server error).
All these application are in the same application group in ADFS.
Thank you.
Update 1:
If i've understood correctly the audience must be WHO validates the token. So it must be the Web Api identifier inside ADFS.
If i put this identifier in the audience variable i get: audience did not match.
The audience that is in the token that i'm sending with postman is indeed different: urn:microsoft:userinfo!
Update 2:
I've managed to access to the web api and get a nice and valid access token. Now the problem is that the audience of the token is like:
"aud": "microsoft:identityserver:web api id on ADFS"
That "microsoft:identityserver is a problem when i have to do the "on-behalf of".
It forces me in doing:
ClientCredential clientCredential = new ClientCredential("microsoft:identityserver:client ID", "secret");
Otherwise it does not validate the audience.
But doing so, when i do:
var result = await authenticationContext.AcquireTokenAsync("resource to access' id", clientCredential, userAssertion);
It tells me that it cannot find a resource with client id "microsoft:identity:client id", and that's true, because the resource on ADFS has a client ID WITHOUT the "microsoft:identity" part.

Azure AD User info with JWT Bearer token and ASP.NET Core 2 WebApi

I found a tutorial where I can sign in to my application with Azure AD credentials.
In my frontend I'm using Xamarin.Forms.
In my backend I'm using ASP.NET Core 2.0 WebApi.
Backend:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseAuthentication();
app.UseMvc();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Authority = String.Format(Configuration["AzureAd:AadInstance"], Configuration["AzureAD:Tenant"]);
options.Audience = Configuration["AzureAd:Audience"];
});
}
It's pretty simple.
In my frontend I'm filling in my credentials and asking for a access_token.
{
"token_type": "Bearer",
"scope": "user_impersonation",
"expires_in": "3600",
"ext_expires_in": "0",
"expires_on": "1507104075",
"not_before": "1507100175",
"resource": "my_resource",
"access_token": "my_access_token",
"refresh_token": "my_refresh_token"
}
The access_token i'm filling in the headers with Authorization set with bearer my_access_token.
My Api know's all my information because it will automaticly set claims with the info from my access_token. This info is provided by Azure AD. (fullname, firstname, lastname, ...)
But how can I get this information in my frontend?
You might want to check out the active-directory-dotnet-native-desktop sample on GitHub.
I shows how to use ADAL.NET in a desktop app, to get a token for a service. you will need to adapt it for your Xamarin forms client, but the principle is the same as far as authentication is concerned.
Also it contains a service and you would replace it by your own service and get a token for your web API by changing the resource ID to be the one of your application created using the ASP.NET wizard (you'll find it in the Azure portal as described in the readme.md of the sample)
the idea is that you first get a token using ADAL.Net line 92 of TodoListClient/MainWindow.xaml.cs
result = await authContext.AcquireTokenAsync(todoListResourceId, clientId, redirectUri, ...)
and then you use it as a bearer token line 121
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
If all the info you required is include in the access token, you can just decode the access token on the client. The access token is a JWT, it is easy to research code sample to decode the access token like following threads:
How to decode JWT Token?
Decoding and verifying JWT token using System.IdentityModel.Tokens.Jwt
And if you also require more user info, you can refresh the access token for the Microsoft Graph, and call the me endpoint of Microsoft Graph(refer here). And below is the document about how to refresh the access token via refresh token:
Refreshing the access tokens

Asp.Net Core 2.0 and Azure AD B2C for authentication on WebApp and API

I have an existing small app that I use for test, it is in Asp.Net Core 1.1 for both the Web App and the API, the authentication is done using Azure AD B2C.
I am trying to move it to .Net Core 2.0 but I can't figure how to get it working, I tried using both sample from GitHub Azure-Samples for Web App and API, but I have either an unauthorized or 500 error when trying to access the api, if you have a working example for calling a web api from a web app using 2.0 and protected by AD B2C it will be greatly appreciated.
Edit:
The sample I use to test are :
Web App : WebApp-OpenIDConnect-DotNet core2.0
Web Api : B2C-WebApi core2.0
, I changed the appsettings values to match my b2c directory.
For my asp.net core 1.1 test app I use the same samples as above but from the master branch, with the same value for appsettings.
Edit 2
by default, in startup.cs I have this :
services.AddAuthentication()
.AddJwtBearer(option => new JwtBearerOptions
{
Authority = string.Format("https://login.microsoftonline.com/tfp/{0}/{1}/v2.0/",
Configuration["Authentication:AzureAd:Tenant"], Configuration["Authentication:AzureAd:Policy"]),
Audience = Configuration["Authentication:AzureAd:ClientId"],
Events = new JwtBearerEvents
{
OnAuthenticationFailed = AuthenticationFailed
}
});
which gives me the following error:
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 GET http://localhost:44352/api/values/5
Microsoft.AspNetCore.Server.Kestrel:Error: Connection id "0HL89JHF4VBLM", Request id "0HL89JHF4VBLM:00000001": An unhandled exception was thrown by the application.
System.InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found.
if modified services.AddAuthentication like that
services.AddAuthentication(sharedOption =>
{
sharedOption.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
the error is now
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler:Information: Failed to validate the token xxx.
Microsoft.IdentityModel.Tokens.SecurityTokenInvalidSignatureException: IDX10500: Signature validation failed. No security keys were provided to validate the signature.
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignature(String token, TokenValidationParameters validationParameters)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateToken(String token, TokenValidationParameters validationParameters, SecurityToken& validatedToken)
at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.d__6.MoveNext()
I saw a pull request on the sample which correct this issue (Link), the services.AddAuthentication must be change to:
services.AddAuthentication(options =>
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(jwtOptions =>
{
jwtOptions.Authority = $"https://login.microsoftonline.com/tfp/{Configuration["Authentication:AzureAd:Tenant"]}/{Configuration["Authentication:AzureAd:Policy"]}/v2.0/";
jwtOptions.Audience = Configuration["Authentication:AzureAd:ClientId"];
jwtOptions.Events = new JwtBearerEvents
{
OnAuthenticationFailed = AuthenticationFailed
};
});
I got this example working both for Core 1.1 and Core 2.0, please add the Oath Authentication as below,
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddAzureAdB2C(options => Configuration.Bind("Authentication:AzureAdB2C", options))
You configuration options will be defined inside of the class "AzureAdB2CAuthenticationBuilderExtensions", which is found inside of the
azure b2c project
Looks like your token is not being update it from the Azure, are you able to get the token from your web app? could you please verify that you are not getting null
Did you register your api scopes on your azure b2c tenant web app?
"ApiScopes": "https://fabrikamb2c.onmicrosoft.com/demoapi/demo.read"
you have to set scope in your web api and allows to be read on the web app, please follow click the link in order to set it up