Table of Contents

Custom external authentication

Extending external authentication provider

External Authentication allows you to use external identity management service such as OKTA or Microsoft Entra ID for user authentication. Also you can make integration with social media platforms, and allow users to log in or create an account using e.g. their Google, Twitter or Facebook accounts.

DynamicWeb ships with a number of external login providers covering the most common external authentication services; Microsoft Entra, Google, Okta, AzureADB2C and GitHub.

But you're here because you need to connect to some other service not supported out of the box. And so this document provides step-by-step instructions on creating a custom external authentication provider that supports OpenID Connect (OIDC).

Note

DynamicWeb 10 supports both OAuth2.0 and OpenID Connect (OIDC) protocols for external authentication.

OIDC is generally the best choice today, as it is essentially an identity layer on top of OAuth 2.0. So while OAuth 2.0 is designed for authorization (granting third-party access to resources), OIDC adds the necessary components for authentication such as user identity verification and profile information. This makes it well-suited for login scenarios.

Base implementation

Create a class that inherits from the BaseOAuthLoginProvider located in Dynamicweb.ExternalAuthentication project.

The only property which must be overridden is ProviderScheme, as each provider should have its unique provider scheme name:

[AddInLabel("Provider scheme"), AddInParameter("ProviderScheme"), AddInParameterEditor(typeof(TextParameterEditor), "required;info=Note, the redirect Uri parameter of identity provider must be set as /signin-{provider-scheme}, e.g. /signin-google")]
public override string ProviderScheme { get; set; } = "google";

If your provider is used for frontend login, also add the ErrorPage property, which is needed to show error messages during login.

[AddInLabel("Authentication error page"), AddInParameter("ErrorPage"), AddInParameterEditor(typeof(PageSelectEditor), "")]
public override string? ErrorPage { get; set; }

Next you need to implement either of these options, depending on which authentication method is supported by the external login service you're looking to create a login provider for:

public override void SetAuthenticationOptions(OpenIdConnectOptions options)
{
    // Configuration logic here
}

As per the introduction we recommend that you use OpenID Connect (OIDC) if possible - and OAuth 2.0 if not.

Implementation Example

This is a example implementing a login provider for Google. It can be used to ensure all the relevant and important methods are implemented:

using System.Security.Claims;
using Dynamicweb.Extensibility.AddIns;
using Dynamicweb.Extensibility.Editors;
using Dynamicweb.ExternalAuthentication;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;

namespace CustomCode;

/// <summary>
/// Google external login provider
/// </summary>
[AddInName("Google"), AddInDescription("Google login provider")]
public sealed class GoogleLoginProvider : BaseOAuthLoginProvider, IUpdateOpenIdConnectOptions
{
    [AddInLabel("Provider scheme"), AddInParameter("ProviderScheme"), AddInParameterEditor(typeof(TextParameterEditor), "required;info=Note, the redirect Uri parameter of identity provider must be set as /signin-{provider-scheme}, e.g. /signin-google")]
    public override string ProviderScheme { get; set; } = "google";

    [AddInLabel("Client id"), AddInParameter("ClientId"), AddInParameterEditor(typeof(TextParameterEditor), "required")]
    public string? ClientId { get; set; }

    [AddInLabel("Client secret"), AddInParameter("ClientSecret"), AddInParameterEditor(typeof(TextParameterEditor), "")]
    public string? ClientSecret { get; set; }

    [AddInLabel("Authentication error page"), AddInParameter("ErrorPage"), AddInParameterEditor(typeof(PageSelectEditor), "")]
    public override string? ErrorPage { get; set; }

    void IUpdateOpenIdConnectOptions.SetAuthenticationOptions(OpenIdConnectOptions options)
    {
        // Specifies the cookie scheme used before persisting to application cookies
        options.SignInScheme = SignInManager.ExternalAuthenticationScheme;

        // Client credentials provided by your external identity provider
        options.Authority = "https://accounts.google.com";
        options.ClientId = ClientId;
        options.ClientSecret = ClientSecret;

        // Defines where the OIDC provider redirects after authentication. Must match the redirect URI registered with Google
        options.CallbackPath = $"/signin-{ProviderScheme}";
        options.ResponseType = OpenIdConnectResponseType.Code;
        
        // Defines requested user information. Google might return additional claims by default
        options.Scope.Add("openid");
        options.Scope.Add("email");
        
        // Attaches a custom handler for authentication failures
        options.Events.OnRemoteFailure = OnRemoteFailure;
        
        // OnTokenValidated is a critical customization point, as it modifies claims after Google returns credentials but before ASP.NET Core processes them. In this case we check for a claim with a type "name", maps it to the standard ClaimTypes.Name, and makes the user's name available via the User.Identify.Name in controllers.
        // We need to do this because the Dynamicweb Sign-in manager expects standard ClaimTypes.Name and ClaimTypes.Email claims, but Google uses a non-standard claim type for name, so we need to provide mappings to expected claims for consistent access.
        options.Events.OnTokenValidated = ctx =>
        {
            if (ctx.Principal?.Identity is ClaimsIdentity identity)
            {
                Claim? name = identity.Claims.SingleOrDefault(claim => claim.Type.Equals("name", StringComparison.OrdinalIgnoreCase));
                if (name is not null)
                    identity.AddClaim(new Claim(ClaimTypes.Name, name.Value));
            }

            return Task.CompletedTask;
        };
    }

}
To top