I am currently trying to learn how to build a secure api using bearer token, I keep getting this error (InvalidOperationException: The AuthorizationPolicy named: 'Bearer' was not found.) and I am not sure why. I am using asp.net-core 2.0 and trying to use the jwt auth middleware.
Here is my startup class, any help would be greatly appreciated!
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
const string TokenAudience = "ExampleAudience";
const string TokenIssuer = "ExampleIssuer";
private RsaSecurityKey key;
private TokenAuthOptions tokenOptions;
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
var keyParams = RSAKeyUtils.GetRandomKey();
key = new RsaSecurityKey(keyParams);
tokenOptions = new TokenAuthOptions()
{
Audience = TokenAudience,
Issuer = TokenIssuer,
SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.RsaSha256Signature)
};
services.AddDbContext<VulnerabilityContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddScoped<LoggingActionFilter>();
services.AddScoped<VulnsService>();
services.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(o =>
{
o.Authority = "https://localhost:54302";
o.Audience = tokenOptions.Audience;
o.RequireHttpsMetadata = false;
});
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//app.UseSession();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseAuthentication();
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
You get this error because authentication schemes and authorization policies are not the same thing. Let's see what each of them are.
Authentication schemes
They are the different methods of authentication in your application. In the code you posted, you have one authentication scheme which is identified by the name Bearer and the options you specified.
It is possible to have several authentications schemes set up in one single application:
You could authenticate users with cookies or JWT bearer tokens authentication
You could even accept JWT tokens from different sources; in this case, you would need to call the AddJwtBearer method twice. It is also important to note that the name of the authentication scheme is supposed to be unique, so you'd need to use the overload that takes the name and the options configuration delegate
Authorization policies
When a user is authenticated in your application, it doesn't mean it can access every single feature in it. You might have different access levels where administrators have special rights that no one else does; this is expressed in ASP.NET Core using authorization policies. I highly suggest that you read the official documentation on authorization as I think it's great.
An authorization policy is made of two things:
a unique name
a set of requirements
Taking the example of administrators mentioned above, we can create a fictional authorization policy:
Name: Administrators
Requirements: Must be authenticated and have a role claim with the Administrators value
This would be expressed this way in code:
services.AddAuthorization(options =>
{
options.AddPolicy("Administrators", new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.RequireClaim("role", "Administrators")
.Build());
});
You could then apply this policy on some specific controllers or actions in your application by decorating them with an [Authorize(Policy = "Administrators")] attribute. MVC would then, during the request, run the requirements against the current user and determine whether they can access the specific feature.
My guess is that you added such an attribute on one of your actions/controllers, but you didn't register an authorization policy names Bearer in the authorization system.
If your goal is to prevent non-authenticated users to access some actions, you could apply an [Authorize] attribute. Doing so would run the default policy which, by default, only requires the user to be authenticated.
I'm not working with policies and this error happened to me when I forgot to indicate the roles in the authorize attribute.
I had this:
[Authorize("Administrator")] // if you don't specify the property name Roles it will consider it as the policy name
Fixed it by changing it to:
[Authorize(Roles = "Administrator")]
Adding the AuthenticationSchemes to the controller class works for me:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
Related
Im new to .NET Core and I'm trying to setup Role based authorization in a .NET Core 3.1 project. I believe I clicked on every tutorials and threads talking about it online. My problem is that it seems to be working very easily on the tutorials, but it doesn't work for me. According to tutorials I have found, all I would have to do is assign a role to a user in a database, then use [Authorize(Roles="roleName")] before a Controller's Action. When I do that I always get a 403 error for a user having the specified role. When I use userManager.GetRolesAsync(user), I see that the user has the role. When I make a request to this action with [Authorize], it works when the user is logged in, as expected.
I checked in debug mode ClaimsPrincipal.Identity for the current user and I found out that RoleClaimType = "role". I checked the claims of the current user and found out that it doesn't have a claim with a type "role". Is this how [Authorize(Roles="...")] works? Does it look a the claims? If so, how do I had a claim for the user's role? The only way for a user to login in this application is with a Google account. So how am I supposed to add a claim if they are managed by the Google login?
Here's my code in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<ApplicationUser>()
.AddRoles<ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
services.AddAuthentication()
.AddGoogle(options =>
{
IConfigurationSection googleAuthNSection =
Configuration.GetSection("Authentication:Google");
options.ClientId = googleAuthNSection["ClientId"];
options.ClientSecret = googleAuthNSection["ClientSecret"];
})
.AddIdentityServerJwt();
services.AddControllersWithViews();
services.AddRazorPages();
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
if (!env.IsDevelopment())
{
app.UseSpaStaticFiles();
}
app.UseRouting();
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseAngularCliServer(npmScript: "start");
}
});
}
Here's an exemple of an Action of a Controller
[Authorize(Roles = "Admin")]
[HttpGet("userinformations")]
public async Task<UserInformations> GetCurrentUserInformations()
{
string strUserId = this.User.FindFirstValue(ClaimTypes.NameIdentifier);
ApplicationUser user = await userManager.FindByIdAsync(strUserId);
string[] roles = (await userManager.GetRolesAsync(user)).ToArray();
UserInformations userInfo = new UserInformations()
{
UserName = user.UserName,
FirstName = user.FirstName,
LastName = user.LastName,
Email = user.Email,
Organization = user.idDefaultOrganisation.HasValue ? user.DefaultOrganization.OrganizationName : "",
Claims = this.User.Claims.Select(c => $"{c.Type} : {c.Value}").ToArray(),
Roles = roles
};
return userInfo;
}
When I make a request to this Action without [Authorize(Roles = "Admin")], I can see that the current user has the role Admin, but when I add it, I get a 403 error.
What am I doing wrong? I feel like I'm missing one line somewhere or something like that because it all seems so simple in the tutorials I found.
Your assumption was correct, when you specify the [Authorize(Roles = "<role>")] attribute, ASP will create a RolesAuthorizationRequirement behind the scene.
Then the authorization handler will call this.HttpContext.User.IsInRole(<role>) to evaluate the policy.
In your case, the call is this.HttpContext.User.IsInRole("Admin")
The method User.IsInRole will look into a claim named "http://schemas.microsoft.com/ws/2008/06/identity/claims/role" and compare its value to "Admin"
The ASP Authorization pipeline is not hooked to your UserManager logic, the basic API will only observe and validate the JWT token claims.
You should probably create your own AuthorizationHandler that checks if the user is indeed Admin
Or the less formal way using RequireAssertion :
services.AddAuthorization(options => options.AddPolicy("Admininstrators", builder =>
{
builder.RequireAssertion(async context =>
{
string strUserId = context.User.FindFirstValue(ClaimTypes.NameIdentifier);
var user = await userManager.FindByIdAsync(strUserId);
string[] roles = (await userManager.GetRolesAsync(user)).ToArray();
return roles.Contains("Admin");
};
});
[Authorize("Admininstrators")]
[HttpGet("userinformations")]
public async Task<UserInformations> GetCurrentUserInformations()
{
...
}
I finally found a working solution.
I tried adapting #MichaelShterenberg 's code using RequireAssertion, but I couldn't get it to work because I had to query my database and I was not able to use UserManager with this solution.
I ended up finding a solution based on this part of his answer :
You should probably create your own AuthorizationHandler that checks if the user is indeed Admin
I followed the answer of this thread : Dependency Injection on AuthorizationOptions Requirement in DotNet Core
I am trying to add some custom role based authorisation, but I am unable to
get the Startup configured to call my AuthorizationHandler.
I found some related information on GitHub: here.
Is this a bug or not ?
I am using ASP.NET Core 3.1 and my initializaion is as follows:
1: This retrieves the url/roles from the database using Dapper ORM:
private List<UrlRole> GetRolesRoutes()
{
var urlRole = DapperORM.ReturnList<UrlRole>("user_url_role_all");
return urlRole.Result.ToList();
}
2: In my Startup, I get the url/roles and store the result in a global variable:
public Startup(IConfiguration configuration, IWebHostEnvironment env)
{
this.environment = env;
UrlRoles = GetRolesRoutes();
}
3: My Configuration is: Note the UrlRoles which is passed along
public void ConfigureServices(IServiceCollection services)
{
// .. snip
services.AddAuthorization(o =>
o.AddPolicy(_RequireAuthenticatedUserPolicy,
builder => builder.RequireAuthenticatedUser()));
services.AddAuthorization(options =>
{
options.AddPolicy("Roles", policy =>
policy.Requirements.Add(new UrlRolesRequirement(UrlRoles)));
});
services.AddSingleton<AuthorizationHandler<UrlRolesRequirement>, PermissionHandler>();
}
5: My Handler: which is not being called
public class PermissionHandler : AuthorizationHandler<UrlRolesRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, UrlRolesRequirement urlRolesRequirement)
{
var pendingRequirements = context.PendingRequirements.ToList();
foreach (var requirement in pendingRequirements)
{
}
return Task.CompletedTask;
}
}
6: My Requirement class:
public class UrlRolesRequirement : IAuthorizationRequirement
{
private List<UrlRole> UrlRoles { get; }
public UrlRolesRequirement(List<UrlRole> urlRoles)
{
UrlRoles = urlRoles;
}
}
When I debug the ASP.NET Core AuthorizationHandler, I never see that my custom Requirement as being a requirement, which I configured in the Startup. I expected to see the requirement, and if the requirement is present then the "callback" will happen I assume. But for some reason my configuration fails to add the requirement.
public virtual async Task HandleAsync(AuthorizationHandlerContext context)
{
if (context.Resource is TResource)
{
foreach (var req in context.Requirements.OfType<TRequirement>())
{
await HandleRequirementAsync(context, req, (TResource)context.Resource);
}
}
}
Anybody in 2022 having this problem with asp.net core on dotnet 6+ ?
The problem I had with my code was that, I registered my handlers without specifying their interface type of "IAuthorizationHandler".
For example:
Instead of doing this: builder.Services.AddScoped<MustHaveAdminManagementScopeHandler>();
Do this:
builder.Services.AddScoped<IAuthorizationHandler, MustHaveAdminManagementScopeHandler>();
Also, do not register multiple handlers as Singletons. I see the Microsoft docs recommending this, but for some reason, registering multiple IAuthorizationHandler as singletons cause an exception.
Without telling ASP.NET Core to do so, it will not use your configured policy to authorize anything. Authorization policies are so that you can predefine complex authorization conditions so that you can reuse this behavior when you need it. It however does not apply by default, and it couldn’t considering that you already configure two policies: Which of those should apply? All of them? Then why configure separate policies?
So instead, no policy is being used to authorize a user unless you explicitly tell the framework that. One common method is to use the [Authorize] attribute with the policy name. You can put that on controller actions but also on controllers themselves to make all its actions authorize with this policy:
[Authorize("Roles")] // ← this is the policy name
public class ExampleController : Controller
{
// …
}
If you have a policy that you want to use most of the times to authorize users, then you can configure this policy as the default:
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
}
This for example will define a policy that requires an authenticated users as the default. So whenever you use the [Authorize] attribute without specificing an explicit policy, then it will use that default policy.
This all will still require you to mark your routes somehow that you require authorization. Besides using the [Authorize] attribute, you can also do this in a more central location: The app.UseEndpoints() call in your Startup class.
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}")
.RequireAuthorization("Roles");
This is the default route template for controllers but with a call to RequireAuthorization which will basically require the Roles authorization policy on all routes that match this route template.
You can use also use this place to configure different default authorization policies for your different routes: By splitting up your route template, you can have multiple calls to MapControllerRoute with different route templates that all specify their own authorization policy.
I was thinking that instead of decorating each and every controller or action, I rather wanted to have some pre-configuration map in a DB, and then in the pipeline verify the users role or roles which are allocated when the user authenticates. When the user then tries to access a url, the users role gets verified and access is granted or rejected.
You could move the logic how exactly a user is authorized into the authorization handler that verifies your requirement. You would still enable the policy that has this requirement for all the routes you want to test though.
However, I would generally advise against this: Authorization requirements are meant to be simple, and you usually, you want to be able to verify them without hitting a database or something other external resource. You want to use the user’s claims directly to make a quick decision whether or not the user is authorized to access something. After all, these checks run on every request, so you want to make this fast. One major benefit of claims based authorization is that you do not need to hit the database on every request, so you should keep that benefit by making sure everything you need to authorize a user is available in their claims.
Here is a tested solution, which enables runtime configuration changes.
Also relieving the burden of decorating each class or action.
In the Startup Add the Role Authorization Requirement, and also register the
RoleService which will be responsible for ensuring a particular role is authorised to access a particular URL.
Here is the Startup.cs where we configure the requirement and also the role service:
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddRequirements(new UrlRolesRequirement())
.Build();
});
services.AddSingleton<IUserService, UserService>(); // authenticate
services.AddSingleton<IUserRoleService, UserRoleService>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<IAuthorizationHandler, PermissionHandler>(); // authorise
The role IUserRoleService: - the UserRoleService implementation
validates a users claimed role (a JWT claim) against a configuration map consisting of url/role entries that are allowed, by looking in either in cached map or retrieving the data from the database.
A typical url(path) to role map has the following format, retrieve from a database, then cached (if lookups fail, the data is retrieved from the database):
/path/to/resource ROLE
public interface IUserRoleService
{
public bool UserHasAccess(ClaimsPrincipal user, string path);
}
The Permission handler:
public class PermissionHandler : IAuthorizationHandler
{
private readonly IUserRoleService userRoleService;
private readonly IHttpContextAccessor contextAccessor;
public PermissionHandler(IUserRoleService userRoleService, IHttpContextAccessor contextAccessor)
{
this.userRoleService = userRoleService;
this.contextAccessor = contextAccessor;
}
public Task HandleAsync(AuthorizationHandlerContext context)
{
var pendingRequirements = context.PendingRequirements.ToList();
foreach (var requirement in pendingRequirements)
{
if (!(requirement is UrlRolesRequirement)) continue;
var httpContext = contextAccessor.HttpContext;
var path = httpContext.Request.Path;
if (userRoleService.UserHasAccess(context.User, path))
{
context.Succeed(requirement);
break;
}
}
return Task.CompletedTask;
}
}
The RolesRequirement - just a POCO
public class UrlRolesRequirement : IAuthorizationRequirement
{
}
Here is a partial implementation of the UserRoleService which validates the JWT role claimed.
private bool ValidateUser(ClaimsPrincipal user, string path)
{
foreach (var userClaim in user.Claims)
{
if (!userClaim.Type.Contains("claims/role")) continue;
var role = userClaim.Value;
var key = role + SEPARATOR + path;
if (urlRoles.ContainsKey(key))
{
var entry = urlRoles[key];
if (entry.Url.Equals(path) && entry.Role.Equals(role))
{
return true;
}
}
}
Console.WriteLine("Access denied: " + path);
return false;
}
I had an immediate 403 response, my custom authorization handler code was never reached. Turns out I forgot to inject them (Scoped is fine). That solved the issue for me.
I have two applications, one written in VB.Net using Asp.Net Web Forms (app1). The second is in C# using Asp.Net Core MVC (app2). I want to create a web API that authenticates a user trying to access either app and shares that authorization token between the apps. If the user goes from app1 to app2, the JWT token will be deemed valid and that user will effectively be logged on. If the same user signs out at any point, it removes the JWT token and will require a login again.
I have built a web api that runs with identity and entity framework that already creates JWT tokens if you have an account in identity and successfully performs authorization in itself. I am struggling with getting app2 to accept that JWT and somehow dissect it to see what roles the user has in app2. Here is my current Startup.cs page with how I've wired the JwtBearer:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
var authManager = Configuration.GetSection("AuthenticationManager");
var key = TextEncodings.Base64Url.Decode(authManager.GetSection("AudienceSecret").Value);
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.Authority = authManager.GetSection("Address").Value;
options.Audience = "my secret audience";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false
};
});
services.AddControllersWithViews();
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// 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.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
Is there a smart way to redirect for login and have app2's controller actions have my [Authorize] attribute just look at the JWT from the API?
Thanks!
You may want to consider something like IdentityServer for this. It can handle these sorts of scenarios out of box -- it's a pretty well-proven .Net solution with extensive documentation and sample code. https://identityserver.io/
The remote user uses the API for which authorization is needed, and passes it successfully.
Created an Asp Core Web API project, I use Identity to control access, deleted a user through UserManager.DeleteAsync:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new Info { Title = "API", Version = "v1" });
});
services.AddDbContext<CommonDBContext>(options => options.UseNpgsql(Configuration.GetConnectionString("PostgreSQL")));
services.AddScoped<IFamilyRepository, FamilyRepository>()
.AddScoped<IProfileRepository, ProfileRepository>();
services.AddIdentity<UserEntity, RoleEntity>(options =>
{
options.SignIn.RequireConfirmedEmail = false;
options.User.RequireUniqueEmail = true;
})
.AddUserManager<UserManagerExtensions>()
.AddEntityFrameworkStores<CommonDBContext>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "API");
});
app.UseMiddleware<TestMiddelwwre>();
app.UseAuthentication();
app.UseMvc();
}
}
Expected Result: A user who has been deleted cannot use the action with [Authorize] attributes.
Actual result: the user successfully passes Authentication and Authorization
The default authorization middleware validates the access token supplied - be it a cookie or bearer token - and simply decodes the information encoded therein, which can be out of sync with changes to the user that occurred after the token was issued. It does not check the current state of the user back in the database. If you want this functionality, you will have to add it to the authorization middleware. Please note that this means an additional trip to the database, so use it wisely - might want to invoke this only on certain critical endpoints and leave the default policy everywhere else, but thats up to you.
When you configure your authorization, you can add an authorization policy that requires a user that "is not deleted" (in my example i added a policy called "ActiveUserPolicy"), or edit the default policy to require this.
One way to achieve this is via authorization requirements & requirement handler. The main advantage of this is that it allows dependency injection into the requirement handlers, so you can do whatever you want with registered services. Here is a short example.
You will need an authorization requirement class, lets say ActiveUserRequirement. It simple needs to extend the IAuthorizationRequirement interface
public class ActiveUserRequirement: IAuthorizationRequirement {}
Then you will need the handler
public class ActiveUserRequirementHandler : AuthorizationHandler<ActiveUserRequirement>
{
private readonly UserManager<IdentityUser> _userManager;
// UserManager<TUser> available via dependency injection. U can inject anything you want
public ActiveUserRequirementHandler(UserManager<IdentityUser> userManager)
{
this._userManager = userManager;
}
/// <inheritdoc />
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ActiveUserRequirement requirement)
{
var userFromDb = await _userManager.GetUserAsync(context.User);
// Check whatever qualifies as a deleted user in your usecase.
if (userFromDb != null && userFromDb.EmailConfirmed) // user exists in db, & we confirmed their email
{
context.Succeed(requirement);
}
else
{
context.Fail();
// calling .Fail() is not recommended, the framework assumes a requirement has failed automatically if no handler explicitly says it passed.
// this allows multiple requirement handlers to have their own way of determining whether a requirement has passed
}
}
}
Now all you need is to register this with your authorization middleware.
// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
....
services.AddAuthorization(options =>
{
options.AddPolicy("ActiveUserPolicy", policyBuilder =>
{
policyBuilder.RequireAuthenticatedUser();
policyBuilder.AddRequirements(new ActiveUserRequirement());
});
});
services.AddSingleton<IAuthorizationHandler, ActiveUserRequirementHandler>();
}
With this setup, you can add a [Authorize(Policy = "ActiveUserPolicy")] attribute to your controllers, or actions.
If you with this to work simply with [Authorize], then you might need to change the default authorization policy. yo can simply set this up as follows:
services.AddAuthorization(options =>
{
options.DefaultPolicy =
new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddRequirements(new ActiveUserRequirement())
.Build();
}
Make sure you call app.UseAuthorization() right after app.AddAuthentication() to ensure the authorization middleware is applied.
Also read Policy based authorization from the official docs to learn more on this.
following this tutorialI've encountered a problem in the Startup.cs file:
(need to scroll down a bit, sorry)
the issue is with default identity, getting the following error:
"IServiceCollection does not contain a definition for AddDefaultIdentity and no accessible extension method AddDefaultIdentity accepting a first argument of type" IServiceCollection could be found(are you missing a using directive or an assembly reference?)"
I looked up the documentation, but I'm missing what error I'm making,
I've seen a bunch of cases similar to mine, but their solution (included) doesn't seems to work. I can us some help, thanks in advance.
"my" code is HERE if you want to take a look
You shouldn't add identity if you use Jwt autentication...Note: AddDefaultIdentity extension method is used to add the default UI service for Razor Pages and MVC. And it also requires you to add StaticFiles.
Note also the additional code and its arrangement in the Configure
method
Try this in your startup class:
public class Startup
{
//add
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().AddNewtonsoftJson();
services.AddTransient<IJwtTokenService, JwtTokenService>();
//Setting up Jwt Authentication
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Jwt:Issuer"],
ValidAudience = Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
};
});
services.AddResponseCompression(opts =>
{
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "application/octet-stream" });
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseResponseCompression();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBlazorDebugging();
}
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(routes =>
{
routes.MapDefaultControllerRoute();
});
app.UseBlazor<Client.Startup>();
}
}
}
Hope this helps...
Update 1:
* Update your Startup class with the code above
* Annotate your SampleDataController controller like this:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[Route("api/[controller]")]
public class SampleDataController : Controller
{
// ..
}
Run your application, and then post a get http request in Postman or Fiddler for the url:
api/SampleData/WeatherForecasts
The response should contain the created JwtToken
Summary of the flow of execution: Posting a get request to your Web Api. The request to the route point WeatherForecasts is redirected to the Token controller whose purpose is to create a Jwt token and return it to the caller. Note that this controller does not verify the identity of the the user on whose behalf this request was send...
TO DO:
Create a service to store the Jwt token:
This service can use Blazor extensions for LocalStorage and SessionStorage to store and retrieve Jwt Tokens. This service may contain methods such as IsAutenticated, GetToken, etc.
Note: That you may pass the Jwt Token from the server to Blazor with more details about the user as a cookie.
Create a Login Component to login the user, if he is not already logged in and tries to access secure resource
Note: If the user is already authenticated, he's not redirected to the Login form. Instead we issue an http request to the server, in order to retrieve the rsources needed in Blazor, if that is the case.
Note: How do we know if our user is authenticated ? We query our IsAutenticated method. If the user is authenticated, if retrieve the Jwt Token and add it to the headers collection passed with our HttpClient call.
More to come...
Do you see it ?