Using .net Core 3.1 I followed the instructions in the official docs (Scaffold Identity in ASP.NET Core projects) to scaffold Identity onto my empty Web project.
I'm using claims-based authorization. In my Startup.cs file I created the following policy:
services.AddAuthorization(options =>
{
options.AddPolicy("Admin", policy =>
{
policy.RequireClaim("CanListUsers", true.ToString());
policy.RequireClaim("CanEditUsers", true.ToString());
});
});
and immediately after that I am gating certain Area pages, as follows:
services.AddRazorPages(options =>
{
options.Conventions.AuthorizeAreaPage("Identity", "/Account/Register", "Admin");
options.Conventions.AuthorizeAreaFolder("Identity", "/Account/Manage");
});
I want to shut down registration to "Admin" users only. And I want to allow only signed in users to manage accounts.
I can't gate the Registration page. I can still access it without a logged-in user. Why not?
On the other hand, strangely, if I remove the authorization convention /Account/Manage, the page will redirect anonymous users to Login. How is that happening?
I think I figured out both questions.
I can't gate the Registration page. I can still access it without a logged-in user. Why not?
The page was decorated with [AllowAnonymous]. 🤦 Clearly decorators have higher priority than Conventions set at configuration time.
On the other hand, strangely, if I remove the authorization convention /Account/Manage, the page will redirect anonymous users to Login. How is that happening?
I noticed that all the pages that should be accessible by not-logged-in users are decorated with [AllowAnonymous]. I'm thinking that the scaffolding, being an Identity template, assumes the implementer will eventually add
app.UseAuthentication();
app.UseAuthorization();
My thinking here is that these Middlewares automatically clamp down all pages, essentially strapping an [Authorize] attribute on all pages so that a page that can allow not-logged-in users needs to opt-out with a [AllowAnonymous].
These are not much more than educated guesses, so please feel free to correct me in the comments 👍
Related
In my current project I am using cookie authentication to secure my controllers.
However, for one particular controller (which will simply be used as API controller) I want the endpoints to be secured using Azure AD.
My use case is that the application serves as a website where users log in and the authentication is stored as a cookie. This already works. Now I want to extend a new controller that will be called via a Logic App.
But I only want the Logic App to be able to call this endpoint. So I created a system managed identity for the Logic App and now I want to secure this new API controller/endpoint.
I have read many articles explaining how to implement multiple schemes. But I don't understand how to implement cookie auth + this particular authentication method.
Perhaps a different method is required, thus I am asking it here. Preferably I don't want to edit the existing working code but rather have a [Authorize(Policy = "ManagedApp")] policy at the top of the new controller.
Any help is appreciated, I am pretty of stuck.
Current ConfigureServices method (irrelevant code removed)
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.LoginPath = "/signin";
options.LogoutPath = "/signout";
// Stuff to store the auth cookie
})
services.AddMvc();
}
From the information you provided, it seems to me that, in order to use policies such as [Authorize(Policy = "ManagedApp")], besides the AddAuthentication(... definition, you will still need to either include:
AddAuthorization
or AddPolicySchemes, depending on your preferences.
I would recommend following this tutorial, which explains both implementations: https://code-maze.com/dotnet-multiple-authentication-schemes/
Please mind that, in order to use exactly [Authorize(Policy = "ManagedApp")], you will need to register a policy named as "ManagedApp".
Concerning the development of front-end and API controllers alongside each other, I wrote this answer some time ago, which may be of interest for your scenario.
In order to authorize all your endpoints by default, you can, for example:
Have different (base) controllers for the front-end and API, and employ a specified Authorize attribute in each of them, then inheriting from those (Microsoft ref);
Or use RequireAuthorization on the endpoints definition, as Andrew Lock suggests here.
Hope this helps!
Authorization doesn't work on Razor pages outside of Areas/Identity, cannot figure out why:
Steps to reproduce:
Fresh app. ASP.NET Core Blazor WASM Hosted
Create page: Areas/Identity/Index2.cshtml
Create page: Areas/NewArea/NewPage.cshtml
Register and Log in.
Access Index2 and NewPage and break in OnGet method while debugging.
Index2: property User.Claims contains multiple claims.
NewPage: property User.Claims is empty
Why the authorization is not "propagated" into NewPage?
When I set #page "/Identity/NewPage" it works so it is somehow related to Identity Area (??).
EDITS:
(Placing page into Pages folder of the server project results the same (no auth))
Reason why do I care:
For quick loading speed I want my index page (Index.cshtml) to be razor page (not Blazor wasm). I need to display login status on index page.
More research:
I have found exactly the same (unresolved, but closed) issue in aspnetcore repo. https://github.com/dotnet/aspnetcore/issues/34080
This issue describe the same scenario I have: Authorise normal Razor Pages in a Blazor WebAssemby App? and there is also a solution:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
})
Another same issue on SO: Blazor WebAssembly with server side Razor page authorize problem
But I still don't understand why is this happening.
EDIT - Solution
The solution with changing DefaultAuthenticateScheme didn't work well after all - it break authorization for api controllers and wasm app.
This may be caused by my setup - I am not actually using server app for "hosting" the Blazor. Blazor app is just SPA using server app for auth and api.
Anyway: Instead of changing default schema for "everything" I change it only for particular .cshtml pages:
[AllowAnonymous]
[Authorize(AuthenticationSchemes = "Identity.Application")]
With that ↑ everything works. But you have to paste this into every .cshtml page... Or - you can configure it in Program.cs:
//add policy which uses ApplicationScheme
services.AddAuthorization(config =>
{
config.AddPolicy("RazorPagesAuthPolicy",
policyBuilder =>
{
policyBuilder.AddAuthenticationSchemes(IdentityConstants.ApplicationScheme);
policyBuilder.RequireAssertion(context => true);//has to be here
});
}
//...
services.AddAuthentication()//keep this
.AddIdentityServerJwt();
//...
services.AddRazorPages(cfg =>
{
cfg.Conventions.AddFolderApplicationModelConvention("/", mo =>
{//add authorize and allowAnonymous filters
mo.Filters.Add(new AuthorizeFilter("RazorPagesAuthPolicy"));
mo.Filters.Add(new AllowAnonymousFilter());
}
);
}
);
which I am not sure if it is way to go
As I've previously mentioned, you can either use cookies authentication or Jwt authentication in order to access the ClaimsPrincipal from the Server project. The solution proposed is fine, and it is used to configure the cookies middleware used by the identity system. Incidentally, it should be:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
}).AddIdentityServerJwt();
But I still don't understand why is this happening
Do you mean why the code above works ?
The code above works because it configures the cookies middleware to authenticate the user, tells the Identity System what scheme to use, and how to challenge the user. Understand this, the Server side of your app is not authenticated, unless you configure it to perform authentication, which differs from the authentication performed when you are re-directed from the WebAssembly Blazor App. I'll try to clarify this by giving you this example:
If you created a Blazor Server App with individual accounts, and then added client SignalR to enable a chat system in your app, and you want to secure your hub object with the Authorize attribute, an authenticated user to your Blazor Server App won't be able to access secured end points on the hub, unless he is authenticated on the hub as well. How to authenticate the user ? Simply by passing the application cookie to the hub. This is more or less simialr to the issue under discussion; that is, though the Identity folder and its content are authenticated (and authorized), the hosting Server App is not.
Note: Its hard to internalized this on first shot, but rest assured that the proposed code not only works but is also 100% kosher.
I've got an ASP.NET MVC application that I want to secure. I've added the [Authorize] attribute to the controllers, I've also added
filters.Add(new System.Web.Mvc.AuthorizeAttribute());
to the RegisterGlobalFilters method and finally, confirmed
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
is in the global.asax.cs, Application_start method.
I can still hit my controller and return the views fine without logging in, actually never have logged in, so sure nothing is cached anywhere.
Any ideas pointers to what I'm missing here?
Thanks in advance
J
Are your users coming from Azure AD?
You have to set an access policy in Startup.cs
services.AddAuthorization(options =>
{
options.FallbackPolicy = options.DefaultPolicy;
options.AddPolicy("<enter policy name>", policy =>
policy.RequireRole("<role from AD"));
You can add multiple roles to a single policy like ("Role 1","Role 2","etc")
And then in your controller you have to specify which policy should have access
[Authorize(Policy = "<policy name from Startup.cs")]
I'm working on a dotnet core/js application in which all of the frontend code including authentication page is produced with a JS framework where the bundled frontend is injected into a single Razor page. The frontend accesses all dotnet core logic from controller api endpoints. The authentication utilize bearer tokens and the [Authorize(role='whatever')] is used in several controllers.
For some reason, a single javascript plugin is injected into the app from the razor page. I basically need this plugin not to be visible before the user is authenticated. It's not a matter of "secret" content but the plugin just isn't useful before the user is authenticated. Is there a simple way for me to find out if the user is authorized within the CSHTML of the code as with the controller attributes? I've been searching around and found nothing very useful. Not an expert on c# or dotnet core (mostly been working on the frontend) and simplest possible solution would be appreciated. Don't know if I've missed any relevant information. If so, tell me!
Depending on the complexity of your condition, you have a few ways to authorize the user.
If you just want to verify that the user is authenticated, i.e. that they are successfully logged in, regardless of who the user is, then you can just check against User.Identity.IsAuthenticated:
#if (User.Identity.IsAuthenticated)
{
<p>User is signed in.</p>
}
If you want to verify that the user has a specific role, then you can also operate on the User principal object directly and just check for that specific role:
#if (User.IsInRole("role-name"))
{
<p>User is in role <em>role-name</em>.</p>
}
Otherwise, if you want to utilize the full capability of the authorization system in ASP.NET Core, you can also inject the IAuthorizationService and authorize the user against a configured authorization policy:
#inject IAuthorizationService _authorizationService
#if ((await _authorizationService.AuthorizeAsync(User, "policy-name")).Succeeded)
{
<p>User was authorized against policy <em>policy-name</em>.</p>
}
I tried searching everywhere on the web, but I can't seem to figure out this important part.
Basically, if we do a DB call each time when checking if a user belongs to a role - this will have negative effect on performance.
I saw code examples listing all user roles, e.g.
var roles = ((ClaimsIdentity)User.Identity).Claims
.Where(c => c.Type == ClaimTypes.Role)
.Select(c => c.Value);
the code can be used in controller action, it is also possible to fetch claims the same way in an Attribute Filter.
From this example I infer that Claims come into play (seems to be most performant solution).
I tried to find out if Authorize attribute with Roles verifies user's claims, but the official Microsoft documentation doesn't cover this bit.
AuthorizeAttribute class
Specifies that access to a controller or action method is restricted to users who meet the authorization requirement.
Properties:
Roles - Gets or sets the user roles that are authorized to access the controller or action method.
And that's the extent of what we have.
Both the Authorize attribute as e.g. User.IsInRole look at the User.Identity roles claims.
By default the Authority (where the user logs in) will add the roles from the AspNetUserRoles table as claims of type 'http://schemas.microsoft.com/ws/2008/06/identity/claims/role'. See WIF claimtypes members.
The client app will automatically take the information from the token / cookie and convert this as User.Identity. As the claim type matches, the role type claims are mapped as roles.
This means that the app doesn't need access to the user store. In most cases this is also not possible. So it is actually not about performance, but about accessibility. Usually apps don't have access to the Identity context. So UserManager is not an option.
There is however a drawback when using claims. The information is dated. When the user logs in a snapshot of the claims at that time are added to the Identity. If in the meantime claims (or roles) are updated in the database, then these changes are not noted. Only after the user logs in again, the changes become effective.
This means that claims are only suitable for pieces of information that do not (frequently) change unless you find a way to invalidate claims. But that would probably mean to access the database or call the authority.
That's why I wouldn't recommend the use of roles. As roles tend to be used for authorization, but you can't revoke access in the meantime. So until you solve that, you may want to consider an alternative.
Sticking to UserManager is not an alternative, because the context may not be available for all apps.
That's why resource-based authorization may be a solution for you. Please read my answer here for additional thoughts.
Open your Startup file and change this:
services.AddDefaultIdentity<IdentityUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
to this:
services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultUI()
.AddDefaultTokenProviders();
Then the Roles should start working.