I'm about to migrate an ASP.NET Core MVC web app from 1.1 to 2.0. The app uses AzureAd for identity management.
In 1.1 I have handled the openidconnect events (like OnTokenReceiver, OnAuthorizationCodeReceived, OnRemoteFailure, etc) in Startup.cs (in Configure()), where I was able to use Dependency Injection. I have injected a lot of services, like EF db context and used them in the event handlers.
After upgrading to 2.0 I had to migrate the whole authentication to AzureAdAuthenticationBuilderExtensions's ConfigureAzureOptions class (which implements IConfigureNamedOptions<OpenIdConnectOptions> interface) where (as I saw) DI cannot be used.
So now only this is in Startup's ConfigureServices:
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddAzureAd(options => Configuration.Bind("AzureAd", options))
.AddCookie();
I used this guide for the migration: https://learn.microsoft.com/en-us/aspnet/core/migration/1x-to-2x/identity-2x#authentication-middleware-and-services
Has somebody an idea how can be services used in openidconnect events?
UPDATE: I was able to fix this with the help of #Balah's answer. Basically the solution was to use the generic .AddOpenIdConnect() instead of creating an extension named .AddAzureAd().
A small addition to the answer: As the authentication part was moved from Configure() to ConfigureServices() where DI isn't enabled and services are not yet registered, the way to get those services after all is this:
var scopeFactory = services
.BuildServiceProvider()
.GetRequiredService<IServiceScopeFactory>();
var scope = scopeFactory.CreateScope();
var provider = scope.ServiceProvider;
var dbContext = provider.GetRequiredService<ApplicationDbContext>();
var graphSdkHelper = provider.GetRequiredService<IGraphSDKHelper>();
var memoryCache = provider.GetRequiredService<IMemoryCache>();
...
Keep in mind, that you have to add those services above this code!
You'll find that those events that you mentioned, have been moved into (the aptly named) Events property on the authentication options.
Accessing any services registered in the DI container can be done via the HttpContext.RequestServices property, like so:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect(o =>
{
o.Events.OnAuthorizationCodeReceived = async ctx =>
{
var db = ctx.HttpContext.RequestServices.GetService<DbContext>();
await ...
};
});
You might need to add services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); because that's what I have too. But I suspect the above will work without it.
Here's an article that covers it nicely.
I'm using .net core 3.1 and I was facing a similar issue.
I think it can be made a bit cleaner by moving the authentication logic to a separate handler class as we want to keep the Startup.cs as tight as possible.
public class AzureAdOpendIdHandler : IConfigureNamedOptions<OpenIdConnectOptions>
{
public void Configure(string name, OpenIdConnectOptions options)
{
options.ClientId = _azureOptions.ClientId;
options.UseTokenLifetime = true;
// The callback path located in AzureAd settings should match the callback path setup up in Azure portal
options.CallbackPath = _azureOptions.CallbackPath;
options.RequireHttpsMetadata = false;
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.TokenValidationParameters = new TokenValidationParameters
{
// Ensure that User.Identity.Name is set correctly after login
NameClaimType = JwtRegisteredClaimNames.Email,
ValidateIssuer = false,
};
options.Events = new OpenIdConnectEvents
{
OnTokenValidated = async context =>
{
var dbContext = (HighEloDbContext)context.HttpContext.RequestServices.GetService(typeof(HighEloDbContext));
var acc = dbContext.Accounts.First(x => x.EmailAddress == userEmail);
...
},
OnAuthenticationFailed = context =>
{
context.Response.Redirect("/Error");
context.HandleResponse(); // Suppress the exception
return Task.CompletedTask;
},
};
}
public void Configure(OpenIdConnectOptions options)
{
Configure(Options.DefaultName, options);
}
}
And here is how my Starup.cs looks like:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages().AddMvcOptions(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
services.AddControllersWithViews().AddRazorRuntimeCompilation();
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => { Configuration.Bind(nameof(AzureAdConfig), options); });
//here comes registration of services, DAL contexts etc.
services.AddSingleton<IConfigureOptions<OpenIdConnectOptions>, AzureAdOpendIdHandler>();
}
Please notice that it works with .AddAzureAD
Related
I have implemented a custom IAuthorizationPolicyProvider following the documentation provided here, but when I debug and reach the handler and look into the context.User object, I see that the properties like IsAuthenticated or context.User.IsInRole are false/empty. My application is configured with jwt token authorization, and I have confirmed that the token does in fact contain values in the roles payload data, but it doesn't seem to authenticating before it reaches the handler for me to use those values. Can someone help me understand the order of operations, or how I might be able to step through the authentication actually happening?
I have both authentication and authorization in my Startup.cs:
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
services.AddAuthorization(options =>
{
var defaultAuthorizationBuilder = new AuthorizationPolicyBuilder("Bearer");
defaultAuthorizationBuilder = defaultAuthorizationBuilder.RequireAuthenticatedUser();
options.DefaultPolicy = defaultAuthorizationBuilder.Build();
}
services.AddSingleton<IAuthorizationPolicyProvider, MyCustomPolicyProvider>();
services.AddSingleton<IAuthorizationHandler, MyCustomHandler>();
After much tinkering, I want to share what I learned that answers this and the broader question of how implementing a custom policy provider works. When using AddAuthentication and AddAuthorization in the Startup.cs, this serves as the policy setup that will be used when you set up the default authorization policy provider, for example like so:
public YourAuthorizationPolicyProvider(IOptions<AuthorizationOptions> options)
{
this.BackupPolicyProvider = new DefaultAuthorizationPolicyProvider(options);
}
For me, I use this fallback policy provider as the default:
public Task<AuthorizationPolicy> GetDefaultPolicyAsync()
{
return this.BackupPolicyProvider.GetDefaultPolicyAsync(); //this is the default policy established in Startup.cs
}
(Note: something I found confusing and not a lot of clear documentation on: DefaultPolicy vs FallbackPolicy, especially because it appears that, at the time of writing this, GetFallbackPolicyAsync recently became a method that requires implementation when implementing IAuthorizationPolicyProvider. DefaultPolicy: when the [Authorize] attribute is applied, this is the policy to be used. When no policy is provided, then GetFallbackPolicyAsync is called.)
What I was missing when I was building my custom policies was specifying in the AuthorizationPolicyBuilder which authentication scheme I wanted the policy to use. I realize now it's in the docs, but it's not called out specifically so I missed it:
public Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
{
if (//some check on your policy name)
{
var policy = new AuthorizationPolicyBuilder(//what scheme to use for authentication);
// your requirements
return Task.FromResult(policy.Build());
}
}
The question & current answer gave me excellent direction on how to configure this myself. Sharing a full example of how a custom policy provider can be configured. In my case, I wanted to decorate actions with [AuthorizePermission("Read")] and have the permission checked by a custom AuthorizationHandler.
CustomPolicyProvider.cs
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options;
namespace MyApp.Authentication.Policies
{
public class CustomPolicyProvider : IAuthorizationPolicyProvider
{
public DefaultAuthorizationPolicyProvider OriginalPolicyProvider { get; set; }
public CustomPolicyProvider(
IOptions<AuthorizationOptions> options)
{
OriginalPolicyProvider = new DefaultAuthorizationPolicyProvider(options);
}
public Task<AuthorizationPolicy> GetDefaultPolicyAsync() => OriginalPolicyProvider.GetDefaultPolicyAsync();
public Task<AuthorizationPolicy?> GetFallbackPolicyAsync() => OriginalPolicyProvider.GetFallbackPolicyAsync();
public Task<AuthorizationPolicy?> GetPolicyAsync(string policyName)
{
if (policyName.StartsWith(AuthSettings.Policies.Permission, StringComparison.OrdinalIgnoreCase))
{
var permission = policyName[AuthSettings.Policies.Permission.Length..];
if (!string.IsNullOrWhiteSpace(permission))
{
var policy = new AuthorizationPolicyBuilder(AuthSettings.Schemes.All);
policy.AddRequirements(new PermissionRequirement(permission));
return Task.FromResult<AuthorizationPolicy?>(policy.Build());
}
}
return OriginalPolicyProvider.GetPolicyAsync(policyName);
}
}
}
Program.cs
builder.Services.AddScoped<IAuthorizationHandler, PermissionAuthorizationHandler>();
builder.Services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser()
.AddAuthenticationSchemes(AuthSettings.Schemes.All)
.Build();
options.AddPolicy(AuthSettings.Policies.Scopes.General, policy =>
{
policy.RequireAuthenticatedUser();
policy.AddAuthenticationSchemes(AuthSettings.Schemes.All);
policy.RequireScope("api");
});
options.AddPolicy(AuthSettings.Policies.Scopes.Identity, policy =>
{
policy.RequireAuthenticatedUser();
policy.AddAuthenticationSchemes(AuthSettings.Schemes.All);
policy.RequireScope("api.identity");
});
});
builder.Services.AddSingleton<IAuthorizationPolicyProvider, CustomPolicyProvider>();
I have searched for a solution online but to no avail - I am trying to access logged-in AD user through HttpContext in .NET Core 2.2 for an intranet application...
I saw a similar problem (link below)and have implemented their solution but my context is still null:-
Link to similar problem
appsettings.json
StartUp.cs
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddAuthentication(HttpSysDefaults.AuthenticationScheme);
services.Configure<IISServerOptions>(options =>
{
options.AutomaticAuthentication = true;
});
services.Configure<IISOptions>(options =>
{
options.ForwardClientCertificate = false;
});
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
services.AddAuthorization(options =>
{
options.AddPolicy("ADGroup", policy =>
policy.Requirements.Add(new UserHelper.CheckADGroupRequirement(Configuration["SecuritySettings:ADGroup"])));
});
services.AddSingleton<IAuthorizationHandler, UserHelper.CheckADGroupHandler>();
}
I have implemented the CheckADGroupHandler in the same way as the solution in the link
Program.cs
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
Screenshot showing context with null value:-
HomeController.cs
[Authorize(Policy = "ADGroup")]
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
}
I am missing something but can't figure out what - any pointers appreciated...
According to the documentation for Windows Authentication with HTTP.sys, you're missing the call to UseHttpSys() in your Program.cs:
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseHttpSys(options =>
{
options.Authentication.Schemes =
AuthenticationSchemes.NTLM |
AuthenticationSchemes.Negotiate;
options.Authentication.AllowAnonymous = false;
})
.Build();
Windows Authentication is much easier to implement if you're running your app behind IIS, but if you're using HTTP.sys because you cannot use IIS, then it's worth noting that if you're able to upgrade to ASP.NET 3.0, you can now use Windows Authentication with Kestrel.
It turned out to be an issue within IIS Express...combined with upgrading Framework to
.NET Core 3.0
I inherited an ASPnetzero application that uses both a Web API and a MVC front-end. The API authenticated via Bearer and the front-end via AbpIdentity (Cookies). A couple of days ago I got brave and decided to update my nuGet packages. The update came with an upgrade from .netCore v1 to v2. But I had some difficulties with the authentication after the JwtBearer middleware became obsolete. I could authenticate using the cookies, but not using Bearer Tokens.
I tried almost everything. Using multiple authentication methods meant that only one worked at a time.
In Startup.cs I had the following (snippets):
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddAbpIdentity<Tenant, User, Role>()
.AddUserManager<UserManager>()
.AddRoleManager<RoleManager>()
.AddSignInManager<SignInManager>()
.AddClaimsPrincipalFactory<UserClaimsPrincipalFactory>()
.AddDefaultTokenProviders();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
AuthConfigurer.Configure(app, _appConfiguration);
}
This is however a self-answer question and I'm hoping that I'll help anyone with similar or equal cases, since I've put together my own solution. The idea was to get the application working with a Bearer token (only when using the API) and cookies (only when using the MVC).
I also had a challenge as the MVC did XHR calls to the API to get data to display on the front-end. This meant that the API also needed to work with Cookies (but only for MVC users).
So I finally figured it out and it required quite a bit of transformation. The result was that:
API users only authenticated with a Bearer Token
MVC users authenticated with Cookies and the same authentication was used for the API calls in the application after they logged in.
All of the changes were made in Startup.cs, and I also commented out the reference to the AuthConfigure.cs file, which is now obsolete. I am open to any improvements or suggestions to the solution.
The important pieces in the Startup.cs file:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddAuthentication()
.AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters()
{
// The application URL is used for the Issuer and Audience and is included in the appsettings.json
ValidIssuer = _appConfiguration["Authentication:JwtBearer:Issuer"],
ValidAudience = _appConfiguration["Authentication:JwtBearer:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_appConfiguration["Authentication:JwtBearer:SecurityKey"]))
};
});
// Activate Cookie Authentication without Identity, since Abp already implements Identity below.
services.ConfigureApplicationCookie(options => options.LoginPath = "/Account/Login");
// Add the Authentication Scheme Provider which will set the authentication method based on the kind of request. i.e API or MVC
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<IAuthenticationSchemeProvider, CustomAuthenticationSchemeProvider>();
// Some of these extensions changed
services.AddAbpIdentity<Tenant, User, Role>()
.AddUserManager<UserManager>()
.AddRoleManager<RoleManager>()
.AddSignInManager<SignInManager>()
.AddClaimsPrincipalFactory<UserClaimsPrincipalFactory>()
.AddDefaultTokenProviders();
//…
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// app.UseAuthentication is critical here
app.UseAuthentication();
app.UseAbp(); //Initializes ABP framework.
app.UseCors("CorsPolicy");
//…
//AuthConfigurer.Configure(app, _appConfiguration);
//…
}
}
public class CustomAuthenticationSchemeProvider : AuthenticationSchemeProvider
{
private readonly IHttpContextAccessor httpContextAccessor;
public CustomAuthenticationSchemeProvider(
IHttpContextAccessor httpContextAccessor,
IOptions<AuthenticationOptions> options)
: base(options)
{
this.httpContextAccessor = httpContextAccessor;
}
private async Task<AuthenticationScheme> GetRequestSchemeAsync()
{
var request = httpContextAccessor.HttpContext?.Request;
if (request == null)
{
throw new ArgumentNullException("The HTTP request cannot be retrieved.");
}
// For API requests, use authentication tokens.
var authHeader = httpContextAccessor.HttpContext.Request.Headers["Authorization"].FirstOrDefault();
if (authHeader?.StartsWith("Bearer ") == true)
{
return await GetSchemeAsync(JwtBearerDefaults.AuthenticationScheme);
}
// For the other requests, return null to let the base methods
// decide what's the best scheme based on the default schemes
// configured in the global authentication options.
return null;
}
public override async Task<AuthenticationScheme> GetDefaultAuthenticateSchemeAsync() =>
await GetRequestSchemeAsync() ??
await base.GetDefaultAuthenticateSchemeAsync();
public override async Task<AuthenticationScheme> GetDefaultChallengeSchemeAsync() =>
await GetRequestSchemeAsync() ??
await base.GetDefaultChallengeSchemeAsync();
public override async Task<AuthenticationScheme> GetDefaultForbidSchemeAsync() =>
await GetRequestSchemeAsync() ??
await base.GetDefaultForbidSchemeAsync();
public override async Task<AuthenticationScheme> GetDefaultSignInSchemeAsync() =>
await GetRequestSchemeAsync() ??
await base.GetDefaultSignInSchemeAsync();
public override async Task<AuthenticationScheme> GetDefaultSignOutSchemeAsync() =>
await GetRequestSchemeAsync() ??
await base.GetDefaultSignOutSchemeAsync();
}
In the web API, I'm securing with Jwt Auth, I have the following ConfigureServices Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(this.Configuration.GetConnectionString("DefaultConnection")));
// Some additional application dependencies here with AddTransient()...
services.AddMvc();
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "xxxxx";
options.RequireHttpsMetadata = false;
options.Audience = "xxxxx";
options.BackchannelHttpHandler = BackChannelHandler;
});
}
Here, BackChannelHandler is a public property of the Startup.cs:
public static HttpMessageHandler BackChannelHandler { get; set; }
The reason why I'm using this property is that when I'm doing integration testing using xUnit, I'm using in-memory TestServer from Microsoft.AspNetCore.TestHost. So, I need to register backchannel handler in TestFixture class like
testIdentityServer = new TestServer(identityServerBuilder);
My.Project.Startup.BackChannelHandler = testIdentityServer.CreateHandler();
testApiServer = new TestServer(apiServerBuilder);
While this is working fine, I would like to override either the services.AddAuthentication() or AddJwtBearer() in the DI container, so I can inject the authentication middleware configuration for testing purposes instead of using a public static property just like how I would do with any other dependency. When I try to do this using:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "xxxxx";
options.RequireHttpsMetadata = false;
options.Audience = "xxxxx";
options.BackchannelHttpHandler = testIdentityServer.CreateHandler();
});
in TestFixture.cs I get the error: authentication scheme with "Bearer token" already exists.
How can I do this with correctly ASP.NET Core?
The behaviour you encounter is normal as you can't have 2 handlers for the same authentication scheme. Here, AddJwtBearer adds a handler for the authentication scheme Bearer, which value comes from JwtBearerDefaults.AuthenticationScheme.
The lambda expression you pass to the AddJwtBearer is registered as a named option configuration, the name it's registered against being the authentication scheme.
So here's what you could do in your test project:
services.PostConfigure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.BackchannelHttpHandler = testIdentityServer.CreateHandler();
});
Doing this will not change the authentication schemes that are already registered in the container but will only modify the options associated with the JWT handler.
Is it possible to use ASP.NET Identity without Entity Framework and Entity Framework migrations? The rest of my application will be using a Micro ORM for data access. However, the application is using the built in ASP.NET Identity Individual User accounts.
My goal is to still be able to use the built in UserManager and LoginManager classes and additionally retrieve a list of the Users using the Micro ORM and do away with anything to do with EF/Migrations. Is this possible? It doesn't seem like it is since the original database structure is created by Applying the initial migration.
If someone has a good technique for doing this, please share.
First you need to create a custom user Store:
public class UserStore : IUserStore<IdentityUser>,
IUserClaimStore<IdentityUser>,
IUserLoginStore<IdentityUser>,
IUserRoleStore<IdentityUser>,
IUserPasswordStore<IdentityUser>,
IUserSecurityStampStore<IdentityUser>
{
// interface implementations not shown
}
Then you need to register it into the dependency injection container:
// Add identity types
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddDefaultTokenProviders();
// Identity Services
services.AddTransient<IUserStore<ApplicationUser>, CustomUserStore>();
services.AddTransient<IRoleStore<ApplicationRole>, CustomRoleStore>();
This is documented here.
Asp.Net Identity has abstracted away the stores it needs and documentation on their stores is here;
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity-custom-storage-providers
This is an example of a store;
public class InMemoryUserStore<TUser> :
IUserStore<TUser>,
IUserLoginStore<TUser>,
IUserClaimStore<TUser>,
IUserPasswordStore<TUser>,
IUserSecurityStampStore<TUser>,
IUserTwoFactorStore<TUser>,
IUserEmailStore<TUser>,
IUserLockoutStore<TUser>,
IUserAuthenticatorKeyStore<TUser>,
IUserTwoFactorRecoveryCodeStore<TUser>,
IUserPhoneNumberStore<TUser> where TUser: MemoryIdentityUser
{
...
}
You can also have your own User object, and it doesn't have to inherit from anything.
public class MemoryIdentityUser
{
private List<MemoryUserClaim> _claims;
private List<MemoryUserLogin> _logins;
private List<MemoryUserToken> _tokens;
...
}
Asp.Net Identity is an engine and as such is opinionated. It is that opinion that drove the abstractions of the stores. I wish the Asp.Net Identity docs has full sequence diagrams as to how it interacts with the stores. At a minimum a few reference sequences that have to be honored.
The store has some quirks where it has required methods that are only to mutate private data in the implementation and then followed up by update calls that assume you will commit that data to persistent storage.
You might want to check out this project;
https://github.com/ghstahl/AspNetCore.2.InMemoryIdentity
You can see what you need to do without the burden of having a database.
Hooking it up;
// My user is custom, so I made ApplicationUser inherit
public class ApplicationUser : MemoryIdentityUser
{
}
Startup.cs;
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IUserStore<ApplicationUser>>(provider =>
{
return new InMemoryUserStore<ApplicationUser>();
});
services.AddIdentity<ApplicationUser>(Configuration)
.AddDefaultTokenProviders();
// Add application services.
services.AddTransient<IEmailSender, EmailSender>();
services.AddMvc();
}
In AddIdentity, the following illustrates to the extent you can bring in your own implementations
public static class InMemoryIdentityServiceCollectionExtensions
{
public static IdentityBuilder AddIdentity<TUser>(this IServiceCollection services, IConfiguration configuration)
where TUser : class => services.AddIdentity<TUser>(configuration,null);
public static IdentityBuilder AddIdentity<TUser>(this IServiceCollection services, IConfiguration configuration,Action<IdentityOptions> setupAction)
where TUser : class
{
// Services used by identity
var authenticationBuilder = services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
})
.AddCookie(IdentityConstants.ApplicationScheme, o =>
{
o.LoginPath = new PathString("/Account/Login");
o.Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = SecurityStampValidator.ValidatePrincipalAsync
};
})
.AddCookie(IdentityConstants.ExternalScheme, o =>
{
o.Cookie.Name = IdentityConstants.ExternalScheme;
o.ExpireTimeSpan = TimeSpan.FromMinutes(5);
})
.AddCookie(IdentityConstants.TwoFactorRememberMeScheme,
o => o.Cookie.Name = IdentityConstants.TwoFactorRememberMeScheme)
.AddCookie(IdentityConstants.TwoFactorUserIdScheme, o =>
{
o.Cookie.Name = IdentityConstants.TwoFactorUserIdScheme;
o.ExpireTimeSpan = TimeSpan.FromMinutes(5);
});
// Hosting doesn't add IHttpContextAccessor by default
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
// Identity services
services.TryAddScoped<IUserValidator<TUser>, UserValidator<TUser>>();
services.TryAddScoped<IPasswordValidator<TUser>, PasswordValidator<TUser>>();
services.TryAddScoped<IPasswordHasher<TUser>, PasswordHasher<TUser>>();
services.TryAddScoped<ILookupNormalizer, UpperInvariantLookupNormalizer>();
// No interface for the error describer so we can add errors without rev'ing the interface
services.TryAddScoped<IdentityErrorDescriber>();
services.TryAddScoped<ISecurityStampValidator, SecurityStampValidator<TUser>>();
services.TryAddScoped<IUserClaimsPrincipalFactory<TUser>, UserClaimsPrincipalFactory<TUser>>();
services.TryAddScoped<UserManager<TUser>, AspNetUserManager<TUser>>();
services.TryAddScoped<SignInManager<TUser>, SignInManager<TUser>>();
if (setupAction != null)
{
services.Configure(setupAction);
}
return new IdentityBuilder(typeof(TUser), services);
}
}
There are a bunch of IUserStore implementations out there, with every type of backing database. I copied my InMemoryUserStore from another project that was using MongoDB as a backing DB.