Background and Motivation
Story: You maintain a .NET Framework web application that has many person-years of effort and business value. Rewriting at once is not an option. You have an opportunity to do new development in ASP .NET Core, but users should not be disrupted. Therefore it is highly desirable for authentication to work seamlessly across the .NET Framework app and the ASP .NET Core app. You maintain your own individual user accounts.
Documentation on sharing authentication cookies provides a starting point. Among other things you must use common string identifiers for:
- Cookie Name
- Authentication Scheme (formerly known as Authentication Type in .NET Framework)
The documentation suggest the magic string "Identity.Application" for the scheme without explaining why other strings won't work so easily.
This seems to be because the services.AddDefaultIdentity() extension sets the default Scheme to "Identity.Application" while setting up other useful defaults and services. Without looking at the code to AddDefaultIdentity this is mysterious.
This leads to:
- The consumer can easily name the cookie how they choose, but not the scheme
- Attempts to use a different scheme name will frustrate & fail, unless the code to
AddDefaultIdentity is reproduced
Proposed API
namespace Microsoft.Extensions.DependencyInjection;
public static class IdentityServiceCollectionUIExtensions
{
+ public static IdentityBuilder AddDefaultIdentity<TUser>(this IServiceCollection services,
+ Action<IdentityOptions>? configureIdentityOptions,
+ Action<AuthenticationOptions>? configureAuthenticationOptions)
+ where TUser : class
+ {
+ services
+ .AddAuthentication(authenticationOptions =>
+ {
+ authenticationOptions.DefaultScheme = IdentityConstants.ApplicationScheme;
+ authenticationOptions.DefaultSignInScheme = IdentityConstants.ExternalScheme;
+ configureAuthenticationOptions?.Invoke(authenticationOptions);
+ })
+ .AddIdentityCookies(o => { });
+ var identityBuilder = services
+ .AddIdentityCore<TUser>(identityOptions =>
+ {
+ identityOptions.Stores.MaxLengthForKeys = 128;
+ configureIdentityOptions?.Invoke(identityOptions);
+ })
+ .AddDefaultUI()
+ .AddDefaultTokenProviders();
+ return identityBuilder;
+ }
}
Usage Examples
// Configure Identity with Scheme and Cookie names shared with legacy application
builder.Services.AddDefaultIdentity<IdentityUser>(o => o.SignIn.RequireConfirmedAccount = true,
o => o.DefaultAuthenticateScheme = MySharedConstants.AuthenticationScheme)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.ConfigureApplicationCookie(cookieAuthOptions => cookieAuthOptions.Cookie.Name = MySharedConstants.CookieName);
Alternative Designs
A simpler design might be to just pass in the default scheme name, if that's the common scenario:
builder.Services.AddDefaultIdentity<IdentityUser>(o => o.SignIn.RequireConfirmedAccount = true,
defaultAuthenticateScheme: MySharedConstants.AuthenticationScheme)
However it seems awkward to pass the name of the scheme after the options lambda.
Risks
None that I'm aware of.
Background and Motivation
Story: You maintain a .NET Framework web application that has many person-years of effort and business value. Rewriting at once is not an option. You have an opportunity to do new development in ASP .NET Core, but users should not be disrupted. Therefore it is highly desirable for authentication to work seamlessly across the .NET Framework app and the ASP .NET Core app. You maintain your own individual user accounts.
Documentation on sharing authentication cookies provides a starting point. Among other things you must use common string identifiers for:
The documentation suggest the magic string
"Identity.Application"for the scheme without explaining why other strings won't work so easily.This seems to be because the
services.AddDefaultIdentity()extension sets the default Scheme to"Identity.Application"while setting up other useful defaults and services. Without looking at the code toAddDefaultIdentitythis is mysterious.This leads to:
AddDefaultIdentityis reproducedProposed API
namespace Microsoft.Extensions.DependencyInjection; public static class IdentityServiceCollectionUIExtensions { + public static IdentityBuilder AddDefaultIdentity<TUser>(this IServiceCollection services, + Action<IdentityOptions>? configureIdentityOptions, + Action<AuthenticationOptions>? configureAuthenticationOptions) + where TUser : class + { + services + .AddAuthentication(authenticationOptions => + { + authenticationOptions.DefaultScheme = IdentityConstants.ApplicationScheme; + authenticationOptions.DefaultSignInScheme = IdentityConstants.ExternalScheme; + configureAuthenticationOptions?.Invoke(authenticationOptions); + }) + .AddIdentityCookies(o => { }); + var identityBuilder = services + .AddIdentityCore<TUser>(identityOptions => + { + identityOptions.Stores.MaxLengthForKeys = 128; + configureIdentityOptions?.Invoke(identityOptions); + }) + .AddDefaultUI() + .AddDefaultTokenProviders(); + return identityBuilder; + } }Usage Examples
Alternative Designs
A simpler design might be to just pass in the default scheme name, if that's the common scenario:
However it seems awkward to pass the name of the scheme after the options lambda.
Risks
None that I'm aware of.