Table of Contents

Hybrid

Use AddHybridAuthentication when the same application serves browser users and API clients: for example a backend for frontend that also exposes an API, or a service whose Swagger UI is used from a browser. It combines OpenID Connect and cookie with JWT bearer; both produce the same AppPrincipal. Start with the overview for the services every scenario registers.

How a request is authenticated

The default authentication scheme is the Arc4uScheme policy scheme. It selects the handler for each request:

flowchart LR
    Request[Request] --> Policy{"Authorization header starts with 'Bearer '?"}
    Policy -- yes --> Jwt["JWT bearer (identity type OAuth2)"]
    Policy -- no --> Oidc["Cookie + OpenID Connect (identity type Cookies)"]

The default challenge scheme is JWT bearer: an unauthenticated request to a protected endpoint gets a 401, not a redirection to the sign-in page. Browser pages that must redirect to the identity provider use the forced OpenID Connect middleware.

Configuration

AddHybridAuthentication reads the same sections as AddOidcAuthentication (OpenID Connect and cookie) into HybridAuthenticationSectionOptions, plus:

Key Type Default Description
Authentication:OAuth2SettingsSectionPath string Authentication:OAuth2.Settings Section of the JWT bearer settings, see OAuth2 settings. The section is required: without an audience (or ValidateAudience: false) the registration throws ConfigurationException: Audiences field is not defined.
Authentication:BasicAuthenticationSectionPath string Authentication:Basic Optional Basic authentication settings, see Accept Basic credentials. Nothing is registered when the section is missing.

appsettings.json

{
  "Application.configuration": { "ApplicationName": "MyApp" },
  "Caching": {
    "Default": "Volatile",
    "Caches": [
      {
        "Name": "Volatile",
        "Kind": "Memory",
        "IsAutoStart": true,
        "Settings": { "SizeLimitInMB": 10 }
      }
    ]
  },
  "Authentication": {
    "DefaultAuthority": { "Url": "https://login.example.com/my-tenant/v2.0" },
    "CookieName": ".MyApp.Cookies",
    "OpenId.Settings": {
      "ClientId": "my-client-id",
      "ClientSecret": "<client-secret>",
      "Audiences": [ "api://my-api" ],
      "Scopes": [ "openid", "profile", "offline_access", "api://my-api/access" ]
    },
    "OAuth2.Settings": { "Audiences": [ "api://my-api" ] },
    "ClaimsMiddleWare": {
      "ForceOpenId": { "ForceAuthenticationForPaths": [ "/swagger*" ] }
    },
    "DataProtection": {
      "EncryptionCertificate": { "Store": { "Name": "MyAppCertificate" } },
      "CacheStore": { "CacheKey": "DataProtection", "CacheName": "Volatile" }
    },
    "TokenCache": { "CacheName": "Volatile" }
  }
}

Code

using Arc4u.OAuth2.Extensions;
using Arc4u.OAuth2.Middleware;
using Arc4u.OAuth2.Token;
using Arc4u.OAuth2.TokenProviders;

// ... the Arc4u services of the overview ...
builder.Services.AddTransient<ITokenRefreshProvider, RefreshTokenProvider>();
builder.Services.AddKeyedTransient<ITokenProvider, OidcTokenProvider>(OidcTokenProvider.ProviderName);

builder.Services.AddHybridAuthentication(builder.Configuration);
builder.Services.AddForceOpenId(builder.Configuration);
builder.Services.AddAuthorization();

var app = builder.Build();

app.UseAuthentication();
app.UseForceOpenId();
app.UseAuthorization();

The code overload AddHybridAuthentication(Action<HybridAuthenticationOptions>) takes the options of the OpenID Connect code overload plus OAuth2SettingsOptions, which is required.

ValidateAudience and ValidateAuthority of the options (or of the Authentication section) control the checks of the OpenID Connect access token, see Disable the audience check. ValidateAuthority (true by default) also validates the issuer of the bearer tokens, as AddJwtAuthentication does. The audience of the bearer tokens is controlled by OAuth2.Settings:ValidateAudience.

Common scenarios

Send browser users to the sign-in page

UseForceOpenId() challenges the OpenID Connect scheme for unauthenticated requests whose path matches ForceAuthenticationForPaths, even when the endpoint does not require authorization (static Swagger UI files, for example). After the sign-in, the user comes back to the requested path and query string. AddForceOpenId(configuration) reads Authentication:ClaimsMiddleWare:ForceOpenId; pass sectionName to use another section, or configure it from code with AddForceOpenId(options => ...).

Key Type Default Description
ForceAuthenticationForPaths string array empty Paths matched from the start of the request path, ignoring case. * matches any characters (/swagger*). The middleware does nothing when the list is empty.
RedirectUrlForAuthority string empty Absolute base URL of the application as users see it (behind a reverse proxy). After the sign-in the user is sent to the requested path on this base URL instead of the URL of the request.

Call UseForceOpenId() after UseAuthentication(). It also works with AddOidcAuthentication.

Call the API from the browser session

A page served by the application can call its own API with the session cookie: without a Bearer header, the request uses the cookie. The access token is in the BootstrapContext of the identity, as with JWT bearer; app.UseOpenIdBearerInjector() after UseAuthentication() also puts it in the Authorization header (see Use the user's access token).

Troubleshooting

Browser requests get a 401 instead of the sign-in page

The default challenge scheme is JWT bearer. Add the path to ForceAuthenticationForPaths, or challenge the OpenIdConnect scheme yourself (Results.Challenge(authenticationSchemes: ["OpenIdConnect"])).

"Audiences field is not defined." at startup

The Authentication:OAuth2.Settings section is missing or has no audience. It is required in hybrid mode.

See also