Reputation: 104
I have a blazor webassembly project using identityserver and facebook authentication. When calling an api controller from webassembly everything works great after setting up an httpclient with .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>()
and mapping the useridclaimtype to nameidentifier. The user is available through the UserManager.GetUserAsync
.
Now I try to add a pure server controller with view that I want the user to be able to browse to directly from the browser. However when browsing directly to a server view there seems to be no authentication despite me setting up:
app.UseIdentityServer();
app.UseAuthentication();
app.UseAuthorization();
and
services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
services.AddAuthentication()
.AddIdentityServerJwt().AddFacebook(facebookOptions =>
{
facebookOptions.AppId = Configuration["id"];
facebookOptions.AppSecret = Configuration["idpwd"];
});
services.Configure<IdentityOptions>(options => options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier);
I had kind of expected the middleware to do the authentication dance automatically for me with this setup. But even though I log in first and carry a .AspNetCore.Identity.Application cookie, the user is still not available through the usermanager. I only get a blank identity with no name and no claims, and if I add an Authorize
attribute to the controller I get a 401.
Am I missing some secret ingredient to get this working? Do I have the wrong expectations on what the middleware should do for me?
After playing around a little and comparing a standard asp.net core project that works and my project. It seems that this is the line that kills the serverside controllers:
services.AddAuthentication().AddIdentityServerJwt()
If I don't add AddIdentityServerJwt()
, the controllers behave well and redirect me when I need authorization, and log me in properly. Can anyone explain to me why that is? Is the AddIdentityServerJwt()
middleware incompatible with Controllers that returns views?
It seems to me that adding AddIdentityServerJwt() adds the requirement that an authorization header with a bearer token is required for every request, which the blazor clientside httpclient provides. But when a browser makes a call directly, the bearer token is missing and the pipeline doesn't try to authenticate the user to get one either.
Upvotes: 1
Views: 1644
Reputation: 1255
What's happening
The specific mixing of the AuthorizeFilter on your controller endpoint and the usage of the AddIdentityServerJwt on your authentication builder is probably the culprit here. AddIdentityServerJwt adds the Jwt authentication scheme as the default authentication scheme indicated by the passage of the authentication scheme name in the AddAuthentication method. This means that the handler associated with this Jwt authentication scheme (JwtBearerHandler) will be the class that's emitting the 401 through its HandleChallengeAsync method. Notice there's no redirect for further action in this method which is why you don't see a redirect occurring.
Why is this happening?
The HandleChallengeAsync method on the JwtBearerHandler is invoked because of the AuthorizeFilter on your controller endpoint. The method that executes the AuthorizeFilter is the OnAuthorizationAsync method (which is invoked right after the initialization of the filter). Since there's no policy specified in the Authorization attribute put on your controller (there was nothing passed in as a parameter in [Authorize]
), the default authorization policy ends up being used. The default policy basically checks if any of the authentication schemes have successfully authenticated you (by looking at the identities in the HttpContext), and return a response for a challenge if not. This challenge defaults to the challenge of the default authentication scheme which is why HandleChallengeAsync is called on the JwtBearerHandler.
You can learn more about policies here, and more about the AuthorizationFilter (and filters in general) here.
Upvotes: 3