I'm trying to understand how to delve into the automagic I get when I configure the hell out of asp.net. I'm currently translating a small api from asp.net web-api 2 to asp.net core. I'm not sure where the 403 is coming from in this configuration or how to fix it. Right now the majority of the api endpoint just need a valid token and do not need to check for any specific claim in the token. So for all my authenticated controllers I get a 403 response that should be a 200, when using a valid bearer token. Also right now I use asymmetric keys with Auth0 as the provider.
Startup.cs configure method I'm using to validate the JWT bearer tokens.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
//Middleware added here order matters
//TODO formatter settings https://docs.asp.net/en/latest/mvc/models/formatting.html
//samples to check
//https://auth0.com/docs/server-apis/webapi-owin
//https://github.com/auth0-samples/auth0-aspnetcore-webapi-rs256
var options = new JwtBearerOptions
{
Audience = Configuration["auth0:clientId"]
,Authority = $"https://{Configuration["auth0:domain"]}/"
,Events = new JwtBearerEvents() // just a pass through to log events
};
app.UseJwtBearerAuthentication(options);
// Very hacky to catch invaild tokens https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server/issues/191
// issue says the need for the required hack is fixed but it's been still happening. Issue about the fix https://github.com/aspnet/Security/issues/411
app.Use(next => async context => {
try
{
await next(context);
}
catch
{
// If the headers have already been sent, you can't replace the status code.
// In this case, throw an exception to close the connection.
if (context.Response.HasStarted)
{
throw;
}
context.Response.StatusCode = 401;
}
});
app.UseMvc();
// TODO global exception handling https://github.com/dotnet/corefx/issues/6398
app.UseSwaggerGen();
app.UseSwaggerUi();
}
}
It seems your token middleware is not executed to validate incoming requests.
Try setting the token middleware to run automaticly.
var options = new JwtBearerOptions
{
//other configurations..
AutomaticAuthenticate = true;
};
You can also use attributes to specify the authentication scheme in the controllers.
[Authorize(AuthenticationSchemes = "MyAuthenticationScheme")]
Read more about it here: Limiting identity by scheme
The problem was with the policy in the ConfigureServices section. The simplest policy is all I need at the moment.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc(c =>
{
// TODO implement this abstract class c.Filters.Add(typeof(ExceptionFilterAttribute));
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
c.Filters.Add(new AuthorizeFilter(policy));
c.Filters.Add(typeof(ValidateModelFilter));
});
Related
I have seen many same or similar questions, and tried all their answers if there was one, but none of those works for me.
I'm using this example from Microsoft's Github account as my project base.
It works well for just signing in users.
The project has 1 WebApi, 1 Angular App.
Then I followed this Microsoft example to add code to call Graph API.
Here is the controller code:
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class BillsController : ControllerBase
{
static readonly string[] scopeRequiredByApi = new string[] { "access_as_user" };
readonly ITokenAcquisition tokenAcquisition;
readonly WebOptions webOptions;
public BillsController(ITokenAcquisition tokenAcquisition,
IOptions<WebOptions> webOptionValue)
{
this.tokenAcquisition = tokenAcquisition;
this.webOptions = webOptionValue.Value;
}
[HttpGet]
[AuthorizeForScopes(Scopes = new[] { Constants.ScopeUserRead, Constants.ScopeMailRead })]
public async Task<IActionResult> Profile()
{
HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);
var subject = string.Empty;
try
{
// Initialize the GraphServiceClient.
Graph::GraphServiceClient graphClient = GetGraphServiceClient(new[] { Constants.ScopeUserRead, Constants.ScopeMailRead });
var me = await graphClient.Me.Request().GetAsync();
// Get user photo
var messages = await graphClient.Me.MailFolders.Inbox.Messages.Request().GetAsync();
subject = messages.First().Subject;
return Ok(subject);
}
catch (System.Exception ex)
{
throw ex;
}
}
private Graph::GraphServiceClient GetGraphServiceClient(string[] scopes)
{
return GraphServiceClientFactory.GetAuthenticatedGraphClient(async () =>
{
string result = await tokenAcquisition.GetAccessTokenForUserAsync(scopes);
return result;
}, webOptions.GraphApiUrl);
}
}
For Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// Setting configuration for protected web api
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddProtectedWebApi(Configuration);
services.AddWebAppCallsProtectedWebApi(Configuration, new string[] { Constants.ScopeUserRead, Constants.ScopeMailRead })
.AddInMemoryTokenCaches();
services.AddOptions();
services.AddGraphService(Configuration);
// Creating policies that wraps the authorization requirements
services.AddAuthorization();
services.AddDbContext<TodoContext>(opt => opt.UseInMemoryDatabase("TodoList"));
services.AddControllers();
// Allowing CORS for all domains and methods for the purpose of sample
services.AddCors(o => o.AddPolicy("default", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
}));
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
// Since IdentityModel version 5.2.1 (or since Microsoft.AspNetCore.Authentication.JwtBearer version 2.2.0),
// Personal Identifiable Information is not written to the logs by default, to be compliant with GDPR.
// For debugging/development purposes, one can enable additional detail in exceptions by setting IdentityModelEventSource.ShowPII to true.
// Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;
app.UseDeveloperExceptionPage();
}
else
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseExceptionHandler("/error");
app.UseCors("default");
app.UseHttpsRedirection();
app.UseCookiePolicy();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
On the Angular App, I added one button to call this Profile() controller action.
todo-view.component.ts
getEmails(): void {
this.service.getEmails().subscribe({
next: (emails: any) => {
alert(emails);
},
error: (err: any) => {
console.log("error happened~!");
console.log(err);
}
});
}
todo-view.component.html
<button (click)="getEmails();">Get Emails</button>
I added the below code into my Startup.cs and removed the AddWebAppCallsProtectedWebApi.
services.AddProtectedWebApiCallsProtectedWebApi(Configuration).AddInMemoryTokenCaches();
Now it throws me a different error message:
I was having the same issue with a react app. Since the AuthorizeForScopes is for returning views, it does not work for API solutions. I was able to add some configuration options to get it working.
The first thing I did was use a SQL cache. This helped stop the "No login hint" error when the site restarted. After that, the token would work fine until timeout, after which the token would get removed from the cache and the error would reappear.
For that, I started looking at the configuration settings. I changed my configuration to the following.
services
.AddWebAppCallsProtectedWebApi(new string[] { "User.Read" }, idOps =>
{
Configuration.Bind("AzureAd", idOps);
idOps.SaveTokens = true;
idOps.RefreshOnIssuerKeyNotFound = true;
idOps.SingletonTokenAcquisition = true;
idOps.UseTokenLifetime = true;
},
ops => Configuration.Bind("AzureAd", ops))
.AddDistributedTokenCaches()
.AddDistributedSqlServerCache(options =>
{
options.ConnectionString = Configuration.GetConnectionString("Site_DbContext");
options.SchemaName = "dbo";
options.TableName = "_TokenCache";
});
I haven't done much testing on it to find out the magic combination, but the sweet spot seems to be SingletonTokenAcquisition. With that set, it seems to be behaving like a hybrid cache. When first set, it pulls the token into memory and holds it so if it is removed from the database cache, it still has access to it.
The other settings may be necessary for the refreshing but I haven't tested that yet.
The one thing I did notice is the token doesn't get added back to the SQL cache until it refreshes so if something happens where the token is removed and the site goes down clearing the memory, the error may reappear, but this is the best solution I found so far. I was able to have my SPA running for 24 hours and was still able to pull new data.
I have a Xamarin.Forms application that I'm using to connect to an App Service backend, and I'm attempting to authenticate using Auzre B2C JWT tokens.
Through various tutorials I have managed to get B2C setup using microsoft accounts, and I am able to create users, change passwords, and generate access tokens.
My next step was to add the [Authorize] attribute to my controller and attempt to pass that token to my app service and authorize users, but no matter what I try I get a 401 Unauthorized response from my service.
I'm adding the JWT token to the Authorization header of my HttpClient, and it's getting to the service.
I can paste my token into https://jwt.ms/, and it correctly tells me what's in my token.
I've implemented this code in an attempt to figure out what's wrong.
ConfigureServices in startup.cs looks like this:
public void ConfigureServices(IServiceCollection services) {
services.AddAuthentication(options => {
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options => {
options.Audience = Configuration["Authentication:AzureAd:ClientId"];
options.Events = new JwtBearerEvents {
OnAuthenticationFailed = AuthenticationFailed
};
options.Authority = $"https://{tenant name}.b2clogin.com/{tenant id}/{Configuration["Authentication:AzureAd:Policy"]}";
options.Events = new JwtBearerEvents {
OnAuthenticationFailed = ctx =>
{
ctx.Response.StatusCode = StatusCodes.Status401Unauthorized;
message += "From OnAuthenticationFailed:\n";
message += FlattenException(ctx.Exception);
return Task.CompletedTask;
},
OnChallenge = ctx =>
{
message += "From OnChallenge:\n";
ctx.Response.StatusCode = StatusCodes.Status401Unauthorized;
ctx.Response.ContentType = "text/plain";
return ctx.Response.WriteAsync(message);
},
OnMessageReceived = ctx =>
{
message = "From OnMessageReceived:\n";
ctx.Request.Headers.TryGetValue("Authorization", out var BearerToken);
if (BearerToken.Count == 0)
BearerToken = "no Bearer token sent\n";
message += "Authorization Header sent: " + BearerToken + "\n";
return Task.CompletedTask;
},
OnTokenValidated = ctx =>
{
Debug.WriteLine("token: " + ctx.SecurityToken.ToString());
return Task.CompletedTask;
}
};
});
services.AddMvc();
}
Configure looks like this:
public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
IdentityModelEventSource.ShowPII = true;
} else {
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseMvc();
}
And I've also added this call to AuthenticationFailed, so I'll know if my authentication is working or not:
Task AuthenticationFailed(AuthenticationFailedContext arg) {
Console.WriteLine(arg.Exception.Message);
return Task.FromResult(0);
}
With my current setup I'm getting a 401 error from the server, and that's right after it hits the OnChallenge event wired up in Startup.cs. According to the link above, that's what gets called right before it returns a 401 to the user, so it seems like the service is receiving the proper token, and authenticating, but maybe I don't have the correct rights set up?
I'm not sure where to go from here, but any guidance would be appreciated.
Edit:
As mentioned in a comment below, I was able to curl my website using the access token generated after logging in through my app like this:
curl https://mywebsite.azurewebsites.net/api/Values -i --header "Authorization: Bearer [TOKEN]"
And that seems to work with no issue, so it seems like it's something with how I'm making a call to the controller through my app, not the authentication itself.
Edit 2 (solution):
So, as per Edit 1, I was correct in that it was just how I was adding the token to the authorization header. It wasn't my brightest moment, but I wasn't calling .Value on the claim that contained my Access Token. I was only calling .ToString() on the claim itself, so the "token" was actually the entire claim text "Access Token: ". I didn't think much of it at the time when I was debugging my service, because I didn't realize it shouldn't have that text there.
Once I corrected that issue the service started working as expected.
So, in the end, I guess it was all working as expected. I was, in fact, not sending the expected token, so I was ... unauthorized.
As requested the line of code that I had to change was this:
So, this won't be 100% applicable to most because I'm using a business library called CSLA, but the idea is the same regardless.
After my b2c call returns the token I store it in the ApplicationContext.User.Identity that's built into the CSLA library. That allows me to get the access token claim later. The important part to take away from this is that I'm storing the token some place that I can access it later when I want to add it to the authorization header.
Later, when I'm making the call with my httpclient I need to get that token, so originally, I was doing this:
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", ((ClaimsIdentity)ApplicationContext.User.Identity).Claims.FirstOrDefault(c => c.Type == "AccessToken").ToString());
This isn't correct. This was sending the "token" as with value "Access Token: [token value]. Essentially, it was adding the words "Access Token" to the token I needed to authenticate, and that was failing, because the words "Access Token" are not actually supposed to be part of the token you use to authenticate.
After I changed my call to this:
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", ((ClaimsIdentity)ApplicationContext.User.Identity).Claims.FirstOrDefault(c => c.Type == "AccessToken").Value);
It started getting only the token value, and when that was added to the authorization header, it worked just fine.
Edit 2 explains the answer to my problem.
I wasn't adding the token correctly to the authorization header, so the service wasn't able to authenticate the token, or rather, it saw the token as invalid.
I created a default ASP.NET Core (2.1) empty web application, and added JWT bearer authentication. The Startup.cs class looks like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
var keyByteArray = Convert.FromBase64String(Constants.JwtSecretKey);
var signinKey = new SymmetricSecurityKey(keyByteArray);
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = true,
ValidAudience = Constants.Audience,
ValidateIssuer = true,
ValidIssuer = Constants.Issuer,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = signinKey
};
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseAuthentication();
app.UseMvc();
}
The controller looks like this:
[Route("values")]
[ApiController]
public class ValuesController : Controller
{
[HttpGet("")]
public IActionResult Get()
{
return new StatusCodeResult(StatusCodes.Status200OK);
}
}
I would like my endpoint to return a 401 HTTP status code when the Authorization header is present but invalid (with an error message containing the failure reason) - but NOT when the header is missing. Is it possible to configure the middleware in such a way?
I tried fiddling with the OnAuthenticationFailed event from JwtBearerEvents, but couldn't get anything done with it.
options.Events = new JwtBearerEvents()
{
OnAuthenticationFailed = context =>
{
// Not fired when the Authorization header is "Bearer foo",
// but fired when the header is "Bearer foo.bar.baz"
return Task.CompletedTask;
}
};
According to this document, JwtBearerEvents only support the below 4 kind of event
OnAuthenticationFailed(this one only triggered after Token failed to be Authenticated, in your case I think you need a validation instead of authentication)
OnChallenge
OnMessageReceived
OnTokenValidated(this one however only triggered after Token is successfully validated, that's why I think it does not work for your case)
What you want should be something like OnTokenValidateFailed but it is not there, one workaround would be register the OnMessageReceived events and try to validate token there
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.Audience = "api1";
options.Events=new JwtBearerEvents(){
OnMessageReceived =context=>{
var header =context.Request.Headers["Authorization"];
//Your validation logic here
//if validate failed
//{
//context.Response.StatusCode=401;
//}
return Task.CompletedTask;
}
};
});
This way you can validate token by yourself however this approach might not short circuit the whole request pipeline and might conflict with other middleware(Not tested tested with other middleware but I assume this might happen)
Another workround would be like below
Add below code in your Config of start.cs in your API project
app.Use(async(context,next)=>{
var authHeader=context.Request.Headers["Authorization"];
//Your validation here
if(validation failed)
{
context.Response.StatusCode=401;
await context.Response.CompleteAsync();
}
else{
await next.Invoke();}
});
Add this before UseAuthenticate to overwrite default Authernticate middleware
First workaournd would trigger when it comes to JwtBearerToken authenticate while the second one will always be triggered since it is registered in the asp.net core middleware pipeline, so it is your choice to decidede to use which one
I am trying to provide an ActiveUser property to Serilog.
Unfortunately I cannot seem to find the correct spot to check for the current user.
In the below code httpContext.User.Identity.IsAuthenticated is always false?
But only when logging in with the bearer token
The bearer token login is working correctly insofar as the user is
authenticated to the controller methods, and the user needs to belong
to the correct roles in order to be authenticated. Though the user name is not correctly set - the claims are present, and IsAuthenticated is set to true.
If I use the cookie login, the user is set correctly, and the claims are set correctly, and the Serilog works correctly. This is true whether using the bearer token or a cookie to call in. Once the user is logged in with a cookie it always works.
When the bearer token is validated, the user is not immediately set?
The project is aspnetcore 2.0
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
... (other configuration items)
app.UseIdentityServer();
app.UseAuthentication();
app.Use(async (httpContext, next) =>
{
// HERE IsAuthenticated IS ALWAYS FALSE
// HERE THE CLAIMS ARE ALWAYS EMPTY, UNLESS
// I LOGIN USING THE COOKIE AS WELL - THEN IT WORKS
var userName = httpContext.User.Identity.IsAuthenticated
? httpContext.User.GetClaim("name")
: "(unknown)";
LogContext.PushProperty(
"ActiveUser",
!string.IsNullOrWhiteSpace(userName)
? userName
: "(unknown)");
await next.Invoke();
});
app.UseMvc(
routes =>
{
routes.MapRoute(
"default",
"{controller=Home}/{action=Index}/{id?}");
});
In my controller method, the User is set correctly, and is authenticated.
[Authorize]
[HttpGet("user")]
public object UserDetail()
{
// HERE THE CLAIMS ARE SET, IsAuthenticated IS ALWAYS TRUE
// AS THE USER MUST BE AUTHENTICATED TO GET HERE
Debug.Assert(this.User.Identity.IsAuthenticated == true)
edit
Digging into the problem further it would appear that the JWTBearer token is validated AFTER my middleware has already executed. The middleware needs to execute AFTER the token is validated.
TL;DR
(the full configuration)
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseIdentityServer();
app.UseAuthentication();
app.Use(async (httpContext, next) =>
{
var userName = httpContext.User.Identity.IsAuthenticated
? httpContext.User.GetClaim("email")
: "(unknown)";
LogContext.PushProperty("ActiveUser", !string.IsNullOrWhiteSpace(userName) ? userName : "(unknown)");
await next.Invoke();
});
app.UseMvc(
routes =>
{
routes.MapRoute(
"default",
"{controller=Home}/{action=Index}/{id?}");
});
}
(more configuration)
public void ConfigureServices(IServiceCollection services)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication()
.AddOpenIdConnect(
o =>
{
o.Authority = "https://localhost:44319";
o.ClientId = "api";
o.ClientSecret = "secret";
o.RequireHttpsMetadata = false;
o.ResponseType = "code id_token token";
o.GetClaimsFromUserInfoEndpoint = true;
})
.AddJwtBearer(
o =>
{
o.Authority = "https://localhost:44319";
o.Audience = "api";
o.RequireHttpsMetadata = false;
//o.SaveToken = true;
});
services.AddMemoryCache();
services.AddIdentity<ApplicationUser, ApplicationRole>(
x =>
{
x.Password.RequireNonAlphanumeric = false;
x.Password.RequireUppercase = false;
})
.AddEntityFrameworkStores<FormWorkxContext>()
.AddDefaultTokenProviders()
.AddIdentityServer();
// NB
services.Configure<IdentityOptions>(
options =>
{
options.ClaimsIdentity.RoleClaimType = ClaimTypes.Role;
options.ClaimsIdentity.UserNameClaimType = ClaimTypes.Name;
});
services.ConfigureApplicationCookie(
options =>
{
options.LoginPath = "/login";
options.LogoutPath = "/logout";
options.Events.OnRedirectToLogin = this.ProcessStatusCodeResponse;
});
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApis())
.AddInMemoryClients(Config.GetClients())
.AddAspNetIdentity<ApplicationUser>();
services.AddTransient<IEmailSender, EmailSender>();
services.AddMvc(
_ =>
{
_.Filters.Add(
new AuthorizeFilter(
new AuthorizationPolicyBuilder(
JwtBearerDefaults.AuthenticationScheme,
IdentityConstants.ApplicationScheme)
.RequireAuthenticatedUser()
.Build()));
_.Filters.Add(new ExceptionFilter());
_.ModelBinderProviders.Insert(0, new PartyModelBinderProvider());
_.ModelBinderProviders.Insert(0, new DbGeographyModelBinder());
_.ModelMetadataDetailsProviders.Add(new KeyTypeModelMetadataProvider());
})
.AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<Startup>())
.AddJsonOptions(json => json.SerializerSettings.Converters.Add(new DbGeographyJsonConverter()));
}
Copying my answer from your other related question in case anyone comes across this and wonders what's going on:
Since you have multiple authentication schemes registered and none is
the default, authentication does not happen automatically as the
request goes through the pipeline. That's why the HttpContext.User
was empty/unauthenticated when it went through your custom middleware.
In this "passive" mode, the authentication scheme won't be invoked
until it is requested. In your example, this happens when the request
passes through your AuthorizeFilter. This triggers the JWT
authentication handler, which validates the token, authenticates and
sets the Identity, etc. That's why the User is populated correctly
by the time it gets to your controller action.
I have replicated this issue when logging in using a principal set up as follows:
var principal = new ClaimsPrincipal(new ClaimsIdentity(claims));
Then I login with SignInAsync. This too leads to User.Identity.Name having a value but the User.Identity.IsAuthenticated not being set to true.
Now when I add the authenticationType parameter to ClaimsIdentity like this:
var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, "local"));
The IsAuthenticated is now set to true.
I am not entirely sure how your sign in would work and you could mention this authenticationType somewhere or you could pass it along while creating the JWT. That is the way I had done it.
Update ok just noticed your comment about the Name not shown either, but you can still try setting the authenticationType. Also as far as your claims are right, you should be able to extract the principle using AuthenticateAsync. Once you can access the principle from the Context.User object, you can always customize the an authentication scheme to force in the principal.
Update 2 In your case, inside your AddJwtBearer, try including this:
o.Events.OnTokenValidated = async (context) => {
context.Principal = new ClaimsPrincipal(new ClaimsIdentity(context.Principal.Claims, "local"));
};
Authenticate the user explicitly in your custom middleware by adding the following line of code:
var result = await context.Request.HttpContext.AuthenticateAsync(JwtBearerDefaults.AuthenticationScheme);//AuthenticationOptions.DefaultAuthenticateScheme)
if (result.Succeeded)
{
//context.User.AddIdentity(result.Principal);
context.User = result.Principal;
}
I'm using the WindowsAzureActiveDirectoryBearerAuthenticationOptions middleware in a web api project and the important parts of my Startup.cs look like this:
public static void ConfigureApp(IAppBuilder appBuilder)
{
HttpConfiguration config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
appBuilder.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = "xx-xx-xx",
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
{
ValidAudience = "yy-yy-yy",
ValidateAudience = true
}
});
config.Filters.Add(new AadAuthorizeAttribute());
appBuilder.UseWebApi(config);
}
The problem is that if I try to access http://localhost/api/404route (which does not exist) I get a 404 when I should have gotten a 401 (since the request from browser does not have any bearer token, etc. and is unauthenticated). If I go to a route that exists, I get 401 as expected. I believe this is because the AadAuthorizeAttribute triggers the middleware execution, which does not happen when webapi cannot find the controller/action.
How do I trigger the authentication for any request even if the route does not exist while using this simple middleware (preferably don't want to write my own)?
Authentication middleware is always run. But it won't throw 401s, that's just not its job. It only checks for identity and adds it to the request if any are found.
What you need is something like this:
app.Use(async (ctx, next) =>
{
if (ctx.Authentication.User.Identity.IsAuthenticated == false)
{
ctx.Response.StatusCode = 401;
return;
}
await next();
});
Put this after your authentication middleware, and it'll send back a 401 for any unauthenticated requests, going to a valid path or not.