All docs
V20.2
20.2
20.1
The page you are viewing does not exist in version 20.1. This link will take you to the root page.
19.2
The page you are viewing does not exist in version 19.2. This link will take you to the root page.
19.1
The page you are viewing does not exist in version 19.1. This link will take you to the root page.
18.2
The page you are viewing does not exist in version 18.2. This link will take you to the root page.
18.1
The page you are viewing does not exist in version 18.1. This link will take you to the root page.
17.2
The page you are viewing does not exist in version 17.2. This link will take you to the root page.
.NET Standard 2.0+
.NET Framework 4.5.2+
The page you are viewing does not exist in the .NET Framework 4.5.2+ platform documentation. This link will take you to the parent topic of the current section.
.NET Standard 2.0+
.NET Core 3.0+

How to: Use Active Directory and OAuth2 Authentication Providers in Blazor Applications

  • 7 minutes to read

This topic demonstrates how to extend your Blazor application with external authentication methods such as Windows Authentication and OAuth providers (Google, Azure, and GutHub).

Windows Authentication

  1. In the MySolution.Blazor.Server\Properties\launchSettings.json file, set windowsAuthentication to true:

    {
      "iisSettings": {
        "windowsAuthentication": true,
        // ...
        }
      },
      // ...
    }
    
  2. In the Blazor application project, add the following class. This class is a middleware service that implements authentication logic:

    using Microsoft.AspNetCore.Authentication;
    using Microsoft.AspNetCore.Http;
    using System;
    using System.Security.Claims;
    using System.Security.Principal;
    using System.Threading.Tasks;
    // ...
    internal class WindowsSignInMiddleware {
        private readonly RequestDelegate next;
        public WindowsSignInMiddleware(RequestDelegate next) {
            this.next = next;
        }
        public async Task Invoke(HttpContext context) {
            string requestPath = context.Request.Path.Value.TrimStart('/');
            string returnUrl = context.Request.Query["ReturnUrl"];
            string schemeName = context.Request.Query["schemeName"];
            if (String.IsNullOrEmpty(returnUrl)) {
                returnUrl = "/";
            }
            if (requestPath.StartsWith("api/challenge") && schemeName == "Windows") {
                var user = context.User;
                if (user is WindowsPrincipal wPrincipal) {
                    var props = new AuthenticationProperties() {
                        RedirectUri = returnUrl,
                        Items = {
                        { "returnUrl", returnUrl },
                        { "scheme", "Windows" },
                        }
                    };
                    var id = new ClaimsIdentity("Windows");
                    id.AddClaim(new Claim(ClaimTypes.Name, wPrincipal.Identity.Name));
                    id.AddClaim(new Claim(ClaimTypes.NameIdentifier, wPrincipal.Identity.Name));
                    await context.SignInAsync(new ClaimsPrincipal(id), props);
                    context.Response.Redirect(returnUrl);
                }
                else {
                    await context.ChallengeAsync("Windows");
                }
            }
            else {
                await next(context);
            }
        }
    }
    
  3. In the MySolution.Blazor\Startup.cs file, call the UseMiddleware method to add WindowsSignInMiddleware to the application's request pipeline:

    using Microsoft.AspNetCore.Builder;
    // ...
    public class Startup {
        // ...
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
            // ...
            app.UseMiddleware<WindowsSignInMiddleware>();
        }
    }
    
  4. In the same file, configure the IIS Server to use Windows Authentication:

    public class Startup {
        // ...
        public void ConfigureServices(IServiceCollection services) {
            // ...
            services.Configure<IISServerOptions>(options => {
                options.AuthenticationDisplayName = "Windows";
            });
        }
    }
    

Google, Azure, and GitHub Providers

  1. Register your application in the corresponding developer account and obtain the Client ID and Application Secret token:

  2. Add the following NuGet packages to the Blazor application project:

  3. In the platform-agnostic Module, create the following classes:

    • OAuthPermissionPolicyUserLoginInfo: contains information on providers that a user uses to log in to the application;
    • OAuthPermissionPolicyUser: extends the default PermissionPolicyUser type with a collection of OAuthPermissionPolicyUserLoginInfo objects.
    using DevExpress.Persistent.BaseImpl;
    using DevExpress.Persistent.BaseImpl.PermissionPolicy;
    using DevExpress.Xpo;
    using System.ComponentModel;
    // ...
    [Browsable(false)]
    public class OAuthPermissionPolicyUserLoginInfo : BaseObject {
        private string loginProviderName;
        private OAuthPermissionPolicyUser user;
        private string providerUserKey;
        public OAuthPermissionPolicyUserLoginInfo(Session session) : base(session) { }
        public string LoginProviderName {
            get { return loginProviderName; }
            set { SetPropertyValue(nameof(LoginProviderName), ref loginProviderName, value); }
        }
        public string ProviderUserKey {
            get { return providerUserKey; }
            set { SetPropertyValue(nameof(ProviderUserKey), ref providerUserKey, value); }
        }
        [Association("User-LoginInfo")]
        public OAuthPermissionPolicyUser User {
            get { return user; }
            set { SetPropertyValue(nameof(User), ref user, value); }
        }
    }
    
    public class OAuthPermissionPolicyUser : PermissionPolicyUser {
        public OAuthPermissionPolicyUser(Session session) : base(session) { }
        [Browsable(false)]
        [Aggregated, Association("User-LoginInfo")]
        public XPCollection<OAuthPermissionPolicyUserLoginInfo> UserLogins {
            get { return GetCollection<OAuthPermissionPolicyUserLoginInfo>(nameof(UserLogins)); }
        }
    }
    
  4. In the Blazor application project, create the OAuthPermissionPolicyUserExtensions static class that extends OAuthPermissionPolicyUser with methods that create and process information from the corresponding OAuthPermissionPolicyUserLoginInfo objects.

    using DevExpress.Data.Filtering;
    using DevExpress.ExpressApp;
    using DevExpress.ExpressApp.Blazor.Services;
    // ...
    public static class OAuthPermissionPolicyUserExtensions {
        public static bool IsAuthenticationStandardEnabled(this OAuthPermissionPolicyUser user, IObjectSpace os) {
            if(os == null) {
                return false;
            }
            var userLoginInfo = os.FindObject<OAuthPermissionPolicyUserLoginInfo>(CriteriaOperator.And(
                new BinaryOperator(nameof(OAuthPermissionPolicyUserLoginInfo.LoginProviderName), 
                SignInMiddlewareDefaults.DefaultClaimsIssuer),
                new BinaryOperator("User.Oid", user.Oid)
            ));
            if(userLoginInfo != null) {
                return true;
            }
            else {
                if(os.GetObjectsCount(typeof(OAuthPermissionPolicyUserLoginInfo), 
                new BinaryOperator("User.Oid", user.Oid)) == 0) {
                    user.CreateUserLoginInfo(os, SignInMiddlewareDefaults.DefaultClaimsIssuer, null);
                    return true;
                }
            }
            return false;
        }
        public static void CreateUserLoginInfo(this OAuthPermissionPolicyUser user, 
        IObjectSpace os, string providerName, string providerUserKey) {
            var userLoginInfo = os.CreateObject<OAuthPermissionPolicyUserLoginInfo>();
            userLoginInfo.ProviderUserKey = providerUserKey;
            userLoginInfo.LoginProviderName = providerName;
            userLoginInfo.User = user;
            os.CommitChanges();
        }
    }
    
  5. In the Blazor application project, create the AuthenticationStandard descendant and override its Authenticate(IObjectSpace) method to process OAuthPermissionPolicyUser.

    using System;
    using DevExpress.ExpressApp;
    using DevExpress.ExpressApp.Security;
    // ...
    public class CustomAuthentication : AuthenticationStandard {
        public CustomAuthentication(Type userType, Type logonParametersType) :
            base(userType, logonParametersType) {
        }
        public override object Authenticate(IObjectSpace objectSpace) {
            OAuthPermissionPolicyUser result = 
                (OAuthPermissionPolicyUser)base.Authenticate(objectSpace);
            if(result.IsAuthenticationStandardEnabled(objectSpace)) {
                return result;
            }
            throw new AuthenticationException(result.UserName);
        }
    }
    

    When a user logs in, the application tries to find the OAuthPermissionPolicyUserLoginInfo object for a corresponding provider name and user key (note that the user key can be the same for different providers). If this object does not exist and a user logs in for the first time, the application creates a new OAuthPermissionPolicyUserLoginInfo object and associated OAuthPermissionPolicyUser.

  6. In the Blazor application project, create the AuthenticationStandardProviderV2 descendant and return a CustomAuthentication instance in the overridden CreateAuthentication method.

    using System;
    using DevExpress.ExpressApp.Security;
    using Microsoft.Extensions.Options;
    // ...
    public class CustomAuthenticationProvider : AuthenticationStandardProviderV2 {
        public CustomAuthenticationProvider(IOptions<AuthenticationStandardProviderOptions> options, 
        IOptions<SecurityOptions> securityOptions) :
            base(options, securityOptions){
        }
        protected override AuthenticationBase CreateAuthentication(Type userType, 
        Type logonParametersType) {
            return new CustomAuthentication(userType, logonParametersType);
        }
    }
    
  7. Open the MySolution.Blazor\Startup.cs file and set the UserType property to the OAuthPermissionPolicyUser type in the AddXafSecurity service:

    public class Startup {
        // ...
        public void ConfigureServices(IServiceCollection services) {
            // ...
            services.AddXafSecurity(options => {
                // ...
                options.UserType = typeof(OAuthPermissionPolicyUser);
            }
        }
    }
    
  8. In the MySolution.Blazor\Startup.cs file, implement authentication logic for external providers in the options.Events.Authenticate functional delegate:

    using DevExpress.Persistent.BaseImpl.PermissionPolicy;
    using DevExpress.Data.Filtering;
    using DevExpress.ExpressApp;
    using System.Security.Principal;
    using System.Security.Claims;
    // ...
    public class Startup {
        // ...
        public void ConfigureServices(IServiceCollection services) {
            // ...
            services.AddXafSecurity(options => {
                // ... 
            })
            .AddExternalAuthentication<HttpContextPrincipalProvider>(options => {
                options.Events.Authenticate = (objectSpace, externalUser) => {
                    bool autoCreateUserByExternalProviderInfo = true;
                    switch(externalUser.Identity.AuthenticationType) {
                        case SignInMiddlewareDefaults.DefaultClaimsIssuer:
                            return ProcessStandardLogin(objectSpace, externalUser);
                        default:
                            return ProcessExternalLogin(objectSpace, externalUser, autoCreateUserByExternalProviderInfo);
                    }
                    OAuthPermissionPolicyUser ProcessStandardLogin(IObjectSpace os, IPrincipal _externalUser) {
                        var user = objectSpace.FindObject<OAuthPermissionPolicyUser>(new BinaryOperator(nameof(OAuthPermissionPolicyUser.UserName), externalUser.Identity.Name));
                        if(user != null && user.IsAuthenticationStandardEnabled(os)) {
                            return user;
                        }
                        return null;
                    }
                    OAuthPermissionPolicyUser ProcessExternalLogin(IObjectSpace os, IPrincipal _externalUser, bool autoCreateUser) {
                        var userIdClaim = ((ClaimsPrincipal)_externalUser).FindFirst("sub") ??
                        ((ClaimsPrincipal)_externalUser).FindFirst(ClaimTypes.NameIdentifier) ??
                        throw new Exception("Unknown user id");
                        var providerUserId = userIdClaim.Value;
                        var userLoginInfo = os.FindObject<OAuthPermissionPolicyUserLoginInfo>(CriteriaOperator.And(
                                new BinaryOperator(nameof(OAuthPermissionPolicyUserLoginInfo.LoginProviderName), _externalUser.Identity.AuthenticationType),
                                new BinaryOperator(nameof(OAuthPermissionPolicyUserLoginInfo.ProviderUserKey), providerUserId)
                            ));
                        if(userLoginInfo != null) {
                            return userLoginInfo.User;
                        }
                        else {
                            if(autoCreateUser) {
                                var user = CreatePermissionPolicyUser(os, _externalUser);
                                if(user != null) {
                                    user.CreateUserLoginInfo(os, _externalUser.Identity.AuthenticationType, providerUserId);
                                }
                                return user;
                            }
                        }
                        return null;
                    }
                    OAuthPermissionPolicyUser CreatePermissionPolicyUser(IObjectSpace os, IPrincipal _externalUser) {
                        string userName = _externalUser.Identity.Name;
                        var user = os.CreateObject<OAuthPermissionPolicyUser>();
                        user.UserName = _externalUser.Identity.Name;
                        //For backward compatibility
                        user.SetPassword(Guid.NewGuid().ToString());
    
                        user.Roles.Add(os.FindObject<PermissionPolicyRole>(new BinaryOperator(nameof(PermissionPolicyRole.Name), "Default")));
                        os.CommitChanges();
                        return user;
                    }
                };
            })
            .AddAuthenticationProvider<AuthenticationStandardProviderOptions, CustomAuthenticationProvider>();
        }
    }
    

    When a user uses an external provider to log on, the code below creates a new OAuthPermissionPolicyUser with the same name, autogenerated password, and Default Role. This prevents users from using this user name and an empty password to log on with the standard authentication.

  9. In the same file, extend the default cookie-based authentication scheme with the following external schemes:

    using Microsoft.AspNetCore.Authentication.Cookies;
    using Microsoft.AspNetCore.Authentication.OpenIdConnect;
    using Microsoft.AspNetCore.Authentication.AzureAD.UI;
    using Microsoft.AspNetCore.Authentication.OAuth;
    using Microsoft.IdentityModel.Tokens;
    using System.Text.Json;
    using System.Net.Http;
    using System.Net.Http.Headers;
    using Microsoft.AspNetCore.Authentication;
    // ...
    public class Startup {
        // ...
        public void ConfigureServices(IServiceCollection services) {
            // ...
            services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
            .AddCookie(options => options.LoginPath = "/LoginPage")
            .AddGoogle(options => {
                Configuration.Bind("Authentication:Google", options);
                options.AuthorizationEndpoint += "?prompt=consent";
                options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            })
            .AddAzureAD(options => {
                Configuration.Bind("Authentication:AzureAd", options);
                options.CookieSchemeName = CookieAuthenticationDefaults.AuthenticationScheme;
            })
            .AddOAuth("GitHub", "GitHub", options => {
                Configuration.Bind("Authentication:GitHub", options);
                options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "id");
                options.ClaimActions.MapJsonKey(ClaimTypes.Name, "login");
                options.Events = new OAuthEvents {
                    OnCreatingTicket = async context => {
                        var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
                        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
                        request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    
                        var response = await context.Backchannel.SendAsync(request, context.HttpContext.RequestAborted);
                        response.EnsureSuccessStatusCode();
                        var json = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
                        context.RunClaimActions(json.RootElement);
                    }
                };
            });
    
            services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options => {
                options.Authority = options.Authority + "/v2.0/"; 
                options.TokenValidationParameters = new TokenValidationParameters {
                    NameClaimType = "preferred_username"
                };
                options.TokenValidationParameters.ValidateIssuer = false; 
            });
        }
    }
    

Access External Authentication Provider Actions

Actions for additional authentication schemes registered in AuthenticationBuilder are below the Log In button. To customize these Actions, follow the steps below:

  1. Create a Window Controller in the Blazor Module project.
  2. In the OnActivated method, get AdditionalLogonActionsController.
  3. Use the Actions property to access the collection of the Controller's Actions.

    using System.Linq;
    using DevExpress.ExpressApp;
    using DevExpress.ExpressApp.Blazor.SystemModule;
    // ...
    public class AdditionalLogonActionsCustomizationController : WindowController {
        protected override void OnActivated() {
            base.OnActivated();
            AdditionalLogonActionsController additionalLogonActionsController = 
                Frame.GetController<AdditionalLogonActionsController>();
            if(additionalLogonActionsController != null) {
                var action = additionalLogonActionsController.Actions.Where(action => 
                    action.Id == "AzureAD").FirstOrDefault();
                if(action != null) {
                    action.Caption = "Azure";
                    action.ImageName = "AzureIcon";
                }
            }
        }
    }
    
  4. Override the Blazor application's CreateLogonWindowControllers method and add AdditionalLogonActionsCustomizationController to the collection of Controllers activated for the Log On page:

    using DevExpress.ExpressApp.Blazor;
    using System.Collections.Generic;
    using DevExpress.ExpressApp;
    // ...
    public partial class MainDemoBlazorApplication : BlazorApplication {
        // ...
        protected override List<Controller> CreateLogonWindowControllers() {
            var result = base.CreateLogonWindowControllers();
            result.Add(new AdditionalLogonActionsCustomizationController());
            return result;
        }
    }