---
title: Mixed-mode authentication
slug: cms-12/docs/mixed-mode-authentication
docTags: 
createdAt: 2026-08-10T15:56:03.000Z
---

Mixed-mode authentication is achieved by configuring multiple authentication schemes:

:::hint{type="info"}
**Note**
By default, the authentication middleware will authenticate only the default scheme.
:::

This becomes 'a-scheme' in the following example because it is the first registered one.

```csharp
services
  .AddAuthentication()
  .AddCookie("a-scheme")
  .AddCookie("another-scheme");
```

You can change this by specifying a default scheme in the `AddAuthentication("another-scheme")` method directly or by configuring the schemes more granularly in the options class:

```csharp
services
  .AddAuthentication(options => {
    options.DefaultScheme = "another-scheme";
    options.DefaultAuthenticateScheme = "another-scheme";
  });
```

or:

```csharp
services
  .Configure<AuthenticationOptions>(options => {
    options.DefaultScheme = "another-scheme";
    options.DefaultAuthenticateScheme = "another-scheme";
  });
```

Users can be signed in to multiple schemes simultaneously but authenticated individually. Multiple cookie schemes are needed;  each cookie scheme keeps the user signed in to a scheme. For example, if you add an OpenID Connect provider, you specify the cookie scheme to use for keeping that user signed in:

```csharp
services
  .AddAuthentication()
  .AddCookie("a-scheme")
  .AddCookie("another-scheme")
  .AddOpenIdConnect("oidc", options => {
    options.SignInScheme = "another-scheme";
    options.SignOutScheme = "another-scheme";
     …
  });
```

Each scheme has a different purpose. For example, the authenticate scheme is used to authenticate the user, and the challenge scheme triggers a login flow for a specific scheme. Authenticating, signing-in, signing-out, and challenging methods have an override where one can explicitly specify which scheme to use. If not specified, the default scheme is used.

Example of how to use a specific scheme:

```csharp
public async Task<IActionResult> Logout() {
  await HttpContext.SignOutAsync("another-scheme");
  return View();
}
```

The `AuthorizeAttribute` can also specify which scheme to use:

```csharp
[Authorize(AuthenticationSchemes = "oidc")]
public async Task<IActionResult> Index() {}
```

:::hint{type="info"}
**Note**
The cookie scheme keeps the user signed in, but you authenticate with another scheme.
:::

More commonly, you want to spontaneously control which scheme to use with your business logic, such as checking if a specific cookie exists or if the request is to a particular URL:

```csharp
services
  .AddAuthentication(options => {
    options.DefaultScheme = "policy-scheme";
    options.DefaultChallengeScheme = " policy-scheme";
  })
  .AddCookie("a-scheme")
  .AddCookie("another-scheme")
  .AddOpenIdConnect("oidc", options => {
    options.SignInScheme = "another-scheme";
    options.SignOutScheme = "another-scheme";
  })
  .AddPolicyScheme("policy-scheme", null, options => {
    options.ForwardDefaultSelector = ctx => {
      if (ctx.Request.Path.StartsWithSegments("episerver", StringComparison.OrdinalIgnoreCase)) {
        return "oidc";
      }

      return "a-scheme";
    }
  });
```

See [Authorize with a specific scheme in ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/limitingidentitybyscheme) for information on which scheme to use.

## Configure CMS services

If  you call `AddCmsAspNetIdentity<TUser>()`, a cookie authentication scheme called `Identity.Application` is added. (This is the default name ASP.NET Identity uses.)

Optimizely Content Management System (CMS) expects several implementations for services also to be registered, most importantly,`SecurityEntityProvider` and `IQueryableNotificationUsers`.

- `SecurityEntityProvider` is used by CMS to provide users and roles to the UI when managing access rights to content, for example.
- `IQueryableNotificationUsers` is used when configuring content workflows.

If you do not call `AddCmsAspNetIdentity<TUser>()`, CMS registers two other implementations that sync user information from external authentication providers.

The following code samples show how to sync users and roles from external providers. You might need to choose one of these explicitly, or if you want users from implementations, you can intercept the service and then add users from the other implementation.

If you call `AddCmsAspNetIdentity<TUser>()`, the following code ensures that users and roles are returned from ASP.NET Identity and the synchronized users and roles from the external authentication provider.

In `startup.cs`:

```csharp
services.TryIntercept<SecurityEntityProvider>((s, inner) =>
  new YourSecurityEntityProvider(
    inner,
    s.GetRequiredService<SynchronizingRolesSecurityEntityProvider>()));

/// <summary>
/// A security entity provider that aggregates the default provider with <see cref="SynchronizingRolesSecurityEntityProvider"/>
/// by decorating the default registered implementation. The entities are only aggregated when the default provider
/// is not <see cref="SynchronizingRolesSecurityEntityProvider"/>.
/// </summary>
public class YourSecurityEntityProvider: SecurityEntityProvider {
  private readonly SecurityEntityProvider _innerSecurityEntityProvider;
  private readonly SynchronizingRolesSecurityEntityProvider _synchronizingSecurityEntityProvider;

  /// <inheritdoc />
  public YourSecurityEntityProvider(
    SecurityEntityProvider innerSecurityEntityProvider,
    SynchronizingRolesSecurityEntityProvider synchronizingSecurityEntityProvider) {
    _innerSecurityEntityProvider = innerSecurityEntityProvider;
    _synchronizingSecurityEntityProvider = synchronizingSecurityEntityProvider;
  }

  /// <inheritdoc />
  public override async Task<IEnumerable<string>> GetRolesForUserAsync(string userName) {
    if (_innerSecurityEntityProvider is SynchronizingRolesSecurityEntityProvider) {
      return await _synchronizingSecurityEntityProvider.GetRolesForUserAsync(userName);
    }

    var results = await Task.WhenAll(
      _synchronizingSecurityEntityProvider.GetRolesForUserAsync(userName),
      _innerSecurityEntityProvider.GetRolesForUserAsync(userName));

    return results.SelectMany(x => x);
  }

  /// <inheritdoc />
  public override async Task<(IEnumerable<string> users, int totalCount) > FindUsersInRoleAsync(string roleName, string usernameToMatch, int startIndex, int maxRows) {
    if (_innerSecurityEntityProvider is SynchronizingRolesSecurityEntityProvider) {
      return await _synchronizingSecurityEntityProvider.FindUsersInRoleAsync(roleName, usernameToMatch, startIndex, maxRows);
    }

    var results = await Task.WhenAll(
      _synchronizingSecurityEntityProvider.FindUsersInRoleAsync(roleName, usernameToMatch, startIndex, maxRows),
      _innerSecurityEntityProvider.FindUsersInRoleAsync(roleName, usernameToMatch, startIndex, maxRows));

    return (results.SelectMany(x => x.users), results.Sum(x => x.totalCount));
  }

  /// <inheritdoc />
  public override async Task<IEnumerable<SecurityEntity>> SearchAsync(string partOfValue, string claimType) {
    return (await SearchAsync(partOfValue, claimType, 0, int.MaxValue)).entities;
  }

  /// <inheritdoc />
  public override async Task<(IEnumerable<SecurityEntity> entities, int totalCount)> SearchAsync(string partOfValue, string claimType, int startIndex, int maxRows) {
    if (_innerSecurityEntityProvider is SynchronizingRolesSecurityEntityProvider) {
      return await _synchronizingSecurityEntityProvider.SearchAsync(partOfValue, claimType, startIndex, maxRows);
    }

    var results = await Task.WhenAll(
      _synchronizingSecurityEntityProvider.SearchAsync(partOfValue, claimType, startIndex, maxRows),
      _innerSecurityEntityProvider.SearchAsync(partOfValue, claimType, startIndex, maxRows));

    return (results.SelectMany(x => x.entities), results.Sum(x => x.totalCount));
  }
}
```

## Synchronize users and roles from an external authentication provider

```csharp
services
  .AddAuthentication()
  .AddCookie("a-scheme")
  .AddCookie("another-scheme", options => {
    options.Events.OnSignedIn = async ctx => {
      if (ctx.Principal?.Identity is ClaimsIdentity claimsIdentity) {
        var synchronizingUserService = ctx
          .HttpContext
          .RequestServices
          .GetRequiredService<ISynchronizingUserService>();

        await synchronizingUserService.SynchronizeAsync(claimsIdentity);
      }
    };
  })
  .AddOpenIdConnect("oidc", options => {
    options.SignInScheme = "another-scheme";
    options.SignOutScheme = "another-scheme";…
  });
```
