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 ?
Related
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/
I am a complete newbie to React and MSAL. I created an ASP.Net Core MVC WebApplication which connects to a Database in Azure. It uses .Net Core 3 and Identity UI. Now I have to create a react webapplication which uses the API of my MVC WebApplication.
I have a requirement to use our existing Azure AD to authenticate and to create a React Webapplication as an User Interface. My problem is where and how to start. The React Application should do the login process. The roles of the users should be managed in the existing database. Before the requirement the MVC Application handled the registration and login via identity UI and stored users and roles in the azure database. Now the users should authenticate via azure ad.
As far as I understand msal, the react application should redirect to the azure ad login and will aquire a token and the azure service will after successful login redirect back to the react app.
How does the react app give the token to my MVC Webapplication? How do i have to change the controllers that a token is passed and validated? do i have to setup some things for MSAL or authentication in the startup.cs?
I created a controller for testing purposes:
public class TestAPIController : Controller
{
private readonly IBaseRepository<Articles> _BaseRepository;
public TestAPIController(IBaseRepository<Articles> _BaseRepository)
{
this._BaseRepository = _BaseRepository;
}
// GET
[HttpGet]
[Route("GetArticles")]
public async Task<IActionResult> GetArticles()
{
return Ok(JsonConvert.SerializeObject(await _BaseRepository.GetAllAsync()));
}
}
My startup.cs looks like this:
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.AddDbContext<BaseContext>(opts => opts.UseSqlServer(Configuration.GetConnectionString("My_ConnectionString")));
//CookiePolice
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.SignIn.RequireConfirmedEmail = true;
options.Stores.MaxLengthForKeys = 128;
})
.AddEntityFrameworkStores<BaseContext>()
.AddDefaultUI()
.AddDefaultTokenProviders();
services.ConfigureApplicationCookie(options =>
{
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
options.LoginPath = $"/Identity/Account/Login";
options.LogoutPath = $"/Identity/Account/Logout";
options.AccessDeniedPath = $"/Identity/Account/AccessDenied";
options.SlidingExpiration = true;
options.Cookie.Name = Common.Common_Ressources.LoginCookieName;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3_0)
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizeAreaFolder("Identity", "/Account/Manage");
options.Conventions.AuthorizeAreaPage("Identity", "/Account/Logout");
});
}
// 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");
app.UseHsts();
}
app.UseRouting();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthentication();
app.UseCookiePolicy();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllers();
endpoints.MapRazorPages();
});
}
}
Is there a small piece of code that will show me how the react app should aquire the token and pass it to the MVC Application? Could you please provide some details that i have to know before implementing this? I hope i can find the best way to achieve the goal of the requirement. Every little piece of code could help me understand it a little better.
Thanks in advance
There are two main steps.
1.Protect your webapi(server) with azure ad. You can refer to this sample(TodoListService).
2.Get access token from your react app(client), then access webapi with the token.
Here is a complete video tutorial and source code on how to use MSAL with React to call Microsoft Graph.
The only different in your case will be that instead of calling Microsoft Graph, you will call your own API.
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.
I'm trying to use Keycloak as an OpenID provider.
Because of compliance reasons I cannot use RSA and must use ECDSA keys to sign the tokens.
I'm using ES256 to sign the tokens generated by Keycloak.
I've created a simple asp.net core server that requires authentication for all controllers.
This is an example of the startup.cs file:
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)
{
IdentityModelEventSource.ShowPII = true;
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddAuthentication(options => {
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options => {
options.RequireHttpsMetadata = false;
options.Authority = "[keycloak address]";
options.Audience = "hello";
});
services.AddSingleton<IAuthorizationHandler, DebugAuthorizationHandler>();
}
// 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.UseAuthentication();
app.UseMvc();
}
}
I also have a client that performs authentication with Keycloak, receives the access token, and then calls the asp.net core server with the access token.
The call fails in the server with the following error code:
info: Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler[1] Failed to validate the token.
Microsoft.IdentityModel.Tokens.SecurityTokenSignatureKeyNotFoundException: IDX10501: Signature validation failed.
The same code succeeds when using RS256 to sign the tokens.
Has anyone experienced a similar issue?
After digging further into the issue, it looks like Keycloak does not return the JWT signature in a standard way.
They currently have an open issue about it.
For more info look here
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)]