Skip to content

services.AddDefaultIdentity(configureIdentityOptions, configureAuthenticationOptions) #44364

Description

@jfoshee

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions