Developer forum

Forum » Development » LinkedIn Authentication

LinkedIn Authentication

Adrian Ursu Dynamicweb Employee
Adrian Ursu
Reply

Hi guys,

We need to create an external provider for LinkedIn authentication.

Has anybody made something similar before?

What would be the best starting point for developing it?

Thank you,

Adrian


Replies

 
Nicolai Pedersen Dynamicweb Employee
Nicolai Pedersen
Reply

You can see how we do it in the providers we use:

 

using Dynamicweb.Core;
using Dynamicweb.Data;
using Dynamicweb.Environment.Helpers;
using Dynamicweb.Extensibility.AddIns;
using Dynamicweb.Extensibility.Editors;
using Dynamicweb.Frontend;
using Dynamicweb.Logging;
using Dynamicweb.Security.UserManagement;
using Dynamicweb.Security.UserManagement.ExternalAuthentication;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Identity.Client;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Owin.Extensions;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Notifications;
using Microsoft.Owin.Security.OpenIdConnect;
using Owin;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Claims;
using System.Threading.Tasks;
using System.Web;

namespace Dynamicweb.Content.Social.Adapters.ExternalAuthentication.Azure
{
    /// <summary>
    /// The LoginProvider class represents external login provider for Azure B2C
    /// </summary>
    [AddInName("AzureB2C"), AddInDescription("Azure b2c login provider."), AddInIgnore(false)]
    public class LoginProvider : ExternalAuthentication.LoginProvider, ISignOut
    {
        #region Fields

        private const string LoggerIdentifier = "Azure authentication";

        // API Scopes
        private string ReadTasksScope => $"{ApiIdentifier}read";
        private string WriteTasksScope => $"{ApiIdentifier}write";
        private string ApiIdentifier => $"https://{Tenant}/tasks/";
        private string[] Scopes => new string[] { ReadTasksScope, WriteTasksScope };

        #endregion

        #region Properties

        private static ConcurrentDictionary<int, OpenIdConnectAuthenticationOptions> ProviderOptions = new ConcurrentDictionary<int, OpenIdConnectAuthenticationOptions>();

        /// <summary>
        /// Gets or sets the azure tenant.
        /// </summary>
        [AddInLabel("Tenant"), AddInParameter("Tenant"), AddInParameterEditor(typeof(TextParameterEditor), "inputClass=std")]
        public string Tenant { get; set; } = "fabrikamb2c.onmicrosoft.com";

        /// <summary>
        /// Gets or sets the azure AD id.
        /// </summary>
        [AddInLabel("Active directory id"), AddInParameter("ActiveDirectoryId"), AddInParameterEditor(typeof(TextParameterEditor), "inputClass=std")]
        public string ActiveDirectoryId { get; set; } = "fabrikamb2c.onmicrosoft.com";

        /// <summary>
        /// Gets or sets the default sign-in/sign-up policy.
        /// </summary>        
        [AddInLabel("SignUpSignInPolicyId"), AddInParameter("SignUpSignInPolicyId"), AddInParameterEditor(typeof(TextParameterEditor), "inputClass=std")]
        public string SignUpSignInPolicyId { get; set; } = "b2c_1_susi";

        /// <summary>
        /// Gets or sets the edit profile policy.
        /// </summary>        
        [AddInLabel("EditProfilePolicyId"), AddInParameter("EditProfilePolicyId"), AddInParameterEditor(typeof(TextParameterEditor), "inputClass=std")]
        public string EditProfilePolicyId { get; set; } = "b2c_1_edit_profile";

        /// <summary>
        /// Gets or sets the reset password policy.
        /// </summary>        
        [AddInLabel("ResetPasswordPolicyId"), AddInParameter("ResetPasswordPolicyId"), AddInParameterEditor(typeof(TextParameterEditor), "inputClass=std")]
        public string ResetPasswordPolicyId { get; set; } = "b2c_1_reset";

        /// <summary>
        /// Gets or sets the client ID.
        /// </summary>        
        [AddInLabel("Application ID"), AddInParameter("ApplicationID"), AddInParameterEditor(typeof(TextParameterEditor), "inputClass=std")]
        public string ApplicationId { get; set; } = "fdb91ff5-5ce6-41f3-bdbd-8267c817015d";

        /// <summary>
        /// Gets or sets the client secret.
        /// </summary>        
        [AddInLabel("Application key"), AddInParameter("ApplicationKey"), AddInParameterEditor(typeof(TextParameterEditor), "inputClass=std")]
        public string ApplicationKey { get; set; } = "X330F3#92!z614M4";

        /// <summary>
        /// Gets or sets the phone claim name.
        /// </summary>
        [AddInLabel("Phone claim name"), AddInParameter("PhoneClaimName"), AddInParameterEditor(typeof(TextParameterEditor), "inputClass=std")]
        public string PhoneClaimName { get; set; } = "Phone";

        /// <summary>
        /// Gets or sets the phone claim name.
        /// </summary>
        [AddInLabel("Customer impersonation id claim name"), AddInParameter("CustomerImpersonationIdClaimName"), AddInParameterEditor(typeof(TextParameterEditor), "inputClass=std;infoText=will be used to find user to impersonate with customer number specified by azure customer attribute")]
        public string CustomerImpersonationIdClaimName { get; set; } = "CustomerId";

        /// <summary>
        /// Gets or sets the page where the provider will be redirected
        /// </summary>
        [AddInLabel("Redirect page"), AddInParameter("RedirectPage"), AddInParameterEditor(typeof(PageSelectEditor), "")]
        public string RedirectPage { get; set; }

        /// <summary>
        /// Gets or sets value indicating 
        /// </summary>
        [AddInLabel("Logout from azure on extranet log-out"), AddInParameter("LogoutFromAzure"), AddInParameterEditor(typeof(YesNoParameterEditor), "")]
        public bool LogoutFromAzure { get; set; }

        /// <summary>
        /// Hides the "Create account with/without page" check box.
        /// </summary>
        [AddInLabel("CreateLocalAccountWithoutPage"), AddInParameter("CreateLocalAccountWithoutPage"), AddInParameterEditor("Dynamicweb.Extensibility.Editors.HiddenParameterEditor, Dynamicweb", "")]
        public override bool CreateLocalAccountWithoutPage { get; set; } = true;

        /// <summary>
        /// Hides the "Create account page" selector - user created automatically after login.
        /// </summary>
        [AddInLabel("CreateNewUserPage"), AddInParameter("CreateNewUserPage"), AddInParameterEditor("Dynamicweb.Extensibility.Editors.HiddenParameterEditor, Dynamicweb", "")]
        public override string CreateNewUserPage { get; set; }

        /// <summary>
        /// Groups assigned to the new user created by External Login account info.
        /// </summary>
        [AddInLabel("Groups for new users"), AddInParameter("NewUserGroups"), AddInParameterEditor("Dynamicweb.Extensibility.Editors.UserGroupParameterEditor, Dynamicweb", "multiple=true")]
        public override string NewUserGroups
        {
            get
            {
                return base.NewUserGroups;
            }

            set
            {
                base.NewUserGroups = value;
            }
        }

        /// <summary>
        /// Gets or sets the Log setting
        /// </summary>
        [AddInLabel("Debug"), AddInParameter("Debug"), AddInParameterEditor(typeof(YesNoParameterEditor), "")]
        public bool Debug { get; set; }

        #endregion

        #region ExternalLoginProvider methods

        //internal static ConcurrentDictionary<string, string> ImpersonationLogins { get; set; } = new ConcurrentDictionary<string, string>();

        private IAppBuilder owinApp;

        /// <summary>
        /// Returns url to redirect to after successful login.
        /// </summary>
        public override string ReturnUrl
        {
            get
            {
                return GetRedirectUri();
            }
        }

        /// <summary>
        /// registers the login provider.
        /// </summary>
        /// <param name="app">The application.</param>
        public override void RegisterProvider(IAppBuilder app)
        {
            // Required for Azure webapps, as by default they force TLS 1.2 and this project attempts 1.0
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
            OpenIdConnectAuthenticationOptions options = SetupOptions(new OpenIdConnectAuthenticationOptions($"{Name}_{ProviderID}"));
            ProviderOptions.AddOrUpdate(ProviderID, options, (id, value) => options);
            app.UseOpenIdConnectAuthentication(options);

            // To make sure the above `Use` is in the correct position:
            app.UseStageMarker(PipelineStage.MapHandler);
            owinApp = app;
        }

        /// <summary>
        /// Login azure b2c.
        /// </summary>
        public override void Login()
        {
            if (!Context.Current.Request.QueryString.AllKeys.Contains("LoginError"))
            {
                if (!IsSignedIn())
                {
                    OpenIdConnectAuthenticationOptions options;
                    if (ProviderOptions.TryGetValue(ProviderID, out options))
                    {
                        options.MetadataAddress = GetMetadataAddress();
                        options.RedirectUri = GetRedirectUri();
                        options.PostLogoutRedirectUri = ReturnUrl;
                        HttpClient httpClient = new HttpClient(ResolveHttpMessageHandler(options))
                        {
                            Timeout = options.BackchannelTimeout,
                            MaxResponseContentBufferSize = 10485760
                        };
                        var configRetriever = new OpenIdConnectConfigurationRetriever();
                        options.ConfigurationManager = new ConfigurationManager<OpenIdConnectConfiguration>($"{options.MetadataAddress}?p={SignUpSignInPolicyId}", configRetriever, httpClient);

                        AuthenticationProperties properties = new AuthenticationProperties(new Dictionary<string, string>() { { "p", SignUpSignInPolicyId } }) { RedirectUri = GetRedirectUri() };
                        HttpContext.Current.GetOwinContext().Authentication.Challenge(properties, $"{Name}_{ProviderID}");
                    }
                }
                else
                {
                    Context.Current.Response.Redirect(RedirectUrl);
                }
            }
        }

        /// <summary>
        /// Edit current frontend user from azure.
        /// </summary>
        public static void EditProfile()
        {
            var provider = GetUserLogOnProvider();
            if (provider != null)
            {
                provider.EditProfileInternal();
            }
        }

        /// <summary>
        /// Resets password for azure provider.
        /// </summary>
        public static void ResetPassword(int providerId)
        {
            var provider = GetProviderById(providerId);
            if (provider != null)
            {
                provider.ResetPasswordInternal();
            }
        }

        /// <summary>
        /// Signs out from the azure AD
        /// </summary>
        public void SignOut()
        {
            SignOutInternal();
        }

        public override ExternalLoginResult GetLoginResult()
        {
            var loginResult = new ExternalLoginResult();

            var loginInfo = HttpContext.Current.GetOwinContext().Authentication.GetExternalLoginInfo();

            if (Debug)
            {
                if (loginInfo != null)
                {
                    LogManager.System.GetLogger(LogCategory.Security, LoggerIdentifier).Debug($"Current login info: {ObjectToString.Output(loginInfo)}");
                }
                else
                {
                    LogManager.System.GetLogger(LogCategory.Security, LoggerIdentifier).Debug("External login info object is null. Authentication failed.");
                }
            }
            if (loginInfo != null)
            {
                loginResult.Success = true;
                loginResult.ProviderID = ProviderID;
                loginResult.DefaultUserName = loginInfo.DefaultUserName;
                loginResult.ProviderKey = loginInfo.Login.ProviderKey;

                foreach (Claim claim in loginInfo.ExternalIdentity.Claims)
                {
                    if (claim.Type.Equals("emails", StringComparison.OrdinalIgnoreCase))
                    {
                        loginResult.Email = claim.Value;
                        if (string.IsNullOrWhiteSpace(loginResult.DefaultUserName) || loginResult.DefaultUserName.Equals("unknown", StringComparison.OrdinalIgnoreCase))
                        {
                            loginResult.DefaultUserName = loginResult.Email;
                        }
                    }
                    else if (claim.Type.Equals("extension_" + PhoneClaimName, StringComparison.OrdinalIgnoreCase))
                    {
                        if (!string.IsNullOrEmpty(claim.Value))
                        {
                            loginResult.Phone = claim.Value;
                        }
                    }
                }
                if (Debug)
                {
                    LogManager.System.GetLogger(LogCategory.Security, LoggerIdentifier).Debug($"Before handling user");
                }
                User externalUser = ExternalLogin.GetUserByExternalLogOn(ProviderID, loginInfo.Login.ProviderKey);
                if (externalUser == null)
                {
                    externalUser = User.GetCurrentUser(PagePermissionLevels.Frontend);
                    if (externalUser == null)
                    {
                        if (Debug)
                        {
                            LogManager.System.GetLogger(LogCategory.Security, LoggerIdentifier).Debug($"Before creating user");
                        }
                        externalUser = CreateNewUser(loginResult);
                    }
                    if (Debug)
                    {
                        LogManager.System.GetLogger(LogCategory.Security, LoggerIdentifier).Debug($"Before adding external log on");
                    }
                    externalUser.AddExternalLogOn(ProviderID, loginInfo.Login.ProviderKey, loginResult.DefaultUserName);
                }
                if (Debug)
                {
                    LogManager.System.GetLogger(LogCategory.Security, LoggerIdentifier).Debug($"Before updating user");
                }
                SetUserProperties(externalUser, loginInfo, loginResult);
            }
            else
            {
                LogManager.System.GetLogger(LogCategory.Security, LoggerIdentifier).Error("External login info object is null. Authentication failed.");
                loginResult.ErrorText = Context.Current.Request.QueryString.AllKeys.Contains("LoginError")
                    ? Converter.ToString(Context.Current.Request["LoginError"])
                    : "external login error";
            }

            return loginResult;
        }

        #endregion

        #region Azure api extensions
        internal void EditProfileInternal()
        {
            if (IsSignedIn())
            {
                HttpContext.Current.GetOwinContext().Set("Policy", EditProfilePolicyId);

                OpenIdConnectAuthenticationOptions options;
                if (ProviderOptions.TryGetValue(ProviderID, out options))
                {
                    options.RedirectUri = GetRedirectUri();
                    options.PostLogoutRedirectUri = ReturnUrl;
                }

                // Set the page to redirect to after editing the profile
                var authenticationProperties = new AuthenticationProperties { RedirectUri = GetRedirectUri() };
                HttpContext.Current.GetOwinContext().Authentication.Challenge(authenticationProperties, $"{Name}_{ProviderID}");

                return;
            }
        }

        internal void ResetPasswordInternal()
        {
            // Let the middleware know you are trying to use the reset password policy (see OnRedirectToIdentityProvider)
            HttpContext.Current.GetOwinContext().Set("Policy", ResetPasswordPolicyId);

            // Set the page to redirect to after changing passwords
            var authenticationProperties = new AuthenticationProperties { RedirectUri = GetRedirectUri() };
            HttpContext.Current.GetOwinContext().Authentication.Challenge(authenticationProperties, $"{Name}_{ProviderID}");

            return;
        }

        internal void SignOutInternal()
        {
            // To sign out the user, you should issue an OpenIDConnect sign out request.
            if (IsSignedIn())
            {
                var returnUrl = $"{LinkHelper.GetHttpDomain()}/Admin/Public/ExtranetLogoff.aspx?redirect={ReturnUrl}";
                foreach (int providerId in ProviderOptions.Keys)
                {
                    ProviderOptions[providerId].PostLogoutRedirectUri = returnUrl;
                }
                var authenticationProperties = new AuthenticationProperties(new Dictionary<string, string>() { { "post_logout_redirect_uri", returnUrl } });
                IEnumerable<AuthenticationDescription> authTypes = HttpContext.Current.GetOwinContext().Authentication.GetAuthenticationTypes();
                foreach (AuthenticationDescription authType in authTypes)
                {
                    HttpContext.Current.GetOwinContext().Authentication.SignOut(authenticationProperties, authType.AuthenticationType);
                }
                HttpContext.Current.Request.GetOwinContext().Authentication.GetAuthenticationTypes();
            }
        }

        #endregion

        #region Private methods

        private bool IsBackendLogin()
        {
            return Context.Current.Request.RawUrl.Contains("ExternalAuthentication.aspx");
        }

        private string GetRedirectUri()
        {
            var domain = LinkHelper.GetHttpDomain();
            var result = domain;

            if (IsBackendLogin())
            {
                result = $"{domain}/Admin/Access/ExternalAuthentication.aspx";
            }
            else if (!string.IsNullOrWhiteSpace(RedirectPage))
            {
                result = $"{domain}{OutputReplacer.ReplaceUrl(RedirectPage)}";
            }
            return result;
        }

        private bool IsSignedIn()
        {
            return HttpContext.Current?.GetOwinContext()?.Authentication?.GetExternalLoginInfo() != null;
        }

        private void SetUserProperties(User currentUser, ExternalLoginInfo loginInfo, ExternalLoginResult loginResult)
        {
            var userChanged = false;
            string secondaryUserExternalProviderKey = null;
            if (currentUser.ExternalID != loginInfo.Login.ProviderKey)
            {
                currentUser.ExternalID = loginInfo.Login.ProviderKey;
                userChanged = true;
            }
            foreach (Claim claim in loginInfo.ExternalIdentity.Claims)
            {
                if (claim.Type.Equals(ClaimTypes.GivenName, StringComparison.OrdinalIgnoreCase))
                {
                    if (!string.IsNullOrEmpty(claim.Value))
                    {
                        currentUser.FirstName = claim.Value;
                        userChanged = true;
                    }
                }
                else if (claim.Type.Equals(ClaimTypes.Surname, StringComparison.OrdinalIgnoreCase))
                {
                    if (!string.IsNullOrEmpty(claim.Value))
                    {
                        currentUser.LastName = claim.Value;
                        userChanged = true;
                    }
                }
                else if (claim.Type.Equals(ClaimTypes.StreetAddress, StringComparison.OrdinalIgnoreCase))
                {
                    if (!string.IsNullOrEmpty(claim.Value))
                    {
                        currentUser.Address = claim.Value;
                        userChanged = true;
                    }
                }
                else if (claim.Type.Equals(ClaimTypes.PostalCode, StringComparison.OrdinalIgnoreCase))
                {
                    if (!string.IsNullOrEmpty(claim.Value))
                    {
                        currentUser.Zip = claim.Value;
                        userChanged = true;
                    }
                }
                else if (claim.Type.Equals(ClaimTypes.StateOrProvince, StringComparison.OrdinalIgnoreCase))
                {
                    if (!string.IsNullOrEmpty(claim.Value))
                    {
                        currentUser.State = claim.Value;
                        userChanged = true;
                    }
                }
                else if (claim.Type.Equals(ClaimTypes.Country, StringComparison.OrdinalIgnoreCase))
                {
                    if (!string.IsNullOrEmpty(claim.Value))
                    {
                        currentUser.Country = claim.Value;
                        userChanged = true;
                    }
                }
                else if (claim.Type.Equals("extension_" + PhoneClaimName, StringComparison.OrdinalIgnoreCase))
                {
                    if (!string.IsNullOrEmpty(claim.Value))
                    {
                        currentUser.Phone = claim.Value;
                        userChanged = true;
                    }
                }
                else if (claim.Type.Equals("extension_" + CustomerImpersonationIdClaimName, StringComparison.OrdinalIgnoreCase))
                {
                    if (!string.IsNullOrEmpty(claim.Value))
                    {
                        User secondaryUser = User.GetUser(CommandBuilder.Create("SELECT * FROM [AccessUser] WHERE [AccessUserCustomerNumber] = {0} AND [AccessUserType] IN ({1})", claim.Value, User.GetUserTypes(false)));
                        if (secondaryUser != null)
                        {
                            //secondaryUserExternalProviderKey = claim.Value;
                            var secondaryUsers = currentUser.GetUsersICanSetAsSecondary();
                            var secondaryUserIds = new HashSet<int>(secondaryUsers.Select(user => user.ID));
                            if (!secondaryUserIds.Contains(secondaryUser.ID))
                            {
                                secondaryUserIds.Add(secondaryUser.ID);
                                currentUser.UpdateSecondaryUsers(secondaryUserIds, true);
                            }
                            //if (!ExternalLogin.IsExistingExternalLogOn(ProviderID, claim.Value, secondaryUser.ID))
                            //{
                            //    secondaryUser.AddExternalLogOn(ProviderID, claim.Value, $"Azure impersonation by {loginInfo.DefaultUserName}");
                            //}
                        }
                    }
                }
            }
            if (userChanged)
            {
                currentUser.Save();
            }
            //ImpersonationLogins[loginInfo.Login.ProviderKey] = secondaryUserExternalProviderKey;
        }

        internal static LoginProvider GetUserLogOnProvider()
        {
            LoginProvider loginProvider = null;
            var currentUser = User.GetCurrentUser(PagePermissionLevels.Frontend);
            if (Converter.ToInt32(currentUser?.ExternalLogOnProviderID) > 0)
            {
                loginProvider = GetProviderById(currentUser.ExternalLogOnProviderID);
            }
            return loginProvider;
        }

        private static LoginProvider GetProviderById(int loginProviderId)
        {
            var provider = Provider.GetProviderById(loginProviderId);
            if (provider?.ExternalLogOnProvider != null && provider?.ExternalLogOnProvider is LoginProvider)
            {
                return (LoginProvider)provider.ExternalLogOnProvider;
            }

            return null;
        }

        private User CreateNewUser(ExternalLoginResult loginResult)
        {
            User user = null;
            if (!string.IsNullOrWhiteSpace(loginResult.Email))
            {
                user = User.GetUserByEmailAddress(loginResult.Email);
            }
            else
            {
                user = User.GetUserByUserName(loginResult.DefaultUserName);
            }
            if (user == null)
            {
                user = new User();
                user.UserName = loginResult.DefaultUserName;
                user.Email = loginResult.Email;
                user.Name = loginResult.DefaultUserName;
                user.ExternalID = loginResult.ProviderKey;

                if (!user.IsUserNameValid())
                {
                    user.UserName = user.Email;
                }
                if (IsBackendLogin())
                {
                    user.AllowBackend = true;
                    var stupidPassword = loginResult.ProviderKey;
                    if (string.IsNullOrEmpty(stupidPassword))
                    {
                        stupidPassword = Guid.NewGuid().ToString();
                    }
                    if (stupidPassword.Length == 32 || stupidPassword.Length == 128)
                    {
                        stupidPassword = stupidPassword.Remove(stupidPassword.Length - 1);
                    }
                    user.Password = stupidPassword;
                }
            }

            user.UserType = UserType.Default;

            foreach (var groupId in NewUserGroups.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(id => Converter.ToInt32(id)))
            {
                user.AddToGroup(groupId);
            }

            user.Save();
            return user;
        }

        private static HttpMessageHandler ResolveHttpMessageHandler(OpenIdConnectAuthenticationOptions options)
        {
            HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler();

            if (options.BackchannelCertificateValidator != null)
            {
                var webRequestHandler = handler as WebRequestHandler;
                if (webRequestHandler == null)
                {
                    throw new InvalidOperationException();
                }

                webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate;
            }

            return handler;
        }

        private OpenIdConnectAuthenticationOptions SetupOptions(OpenIdConnectAuthenticationOptions options)
        {
            options.AuthenticationMode = AuthenticationMode.Passive;
            //options.Authority = GetMetadataAddress(true);
            options.MetadataAddress = GetMetadataAddress();
            // These are standard OpenID Connect parameters
            options.ClientId = ApplicationId;
            // Specify the claim type that specifies the Name property.
            options.TokenValidationParameters = new TokenValidationParameters
            {
                NameClaimType = "name",
                ValidAudience = options.ClientId,
                AuthenticationType = DefaultAuthenticationTypes.ExternalCookie
            };
            // Specify the scope by appending all of the scopes requested into one string (separated by a blank space)
            options.Scope = $"openid email profile offline_access {ReadTasksScope} {WriteTasksScope}";
            // Specify the callbacks for each type of notifications
            options.Notifications = new OpenIdConnectAuthenticationNotifications
            {
                RedirectToIdentityProvider = OnRedirectToIdentityProvider,
                AuthorizationCodeReceived = OnAuthorizationCodeReceived,
                AuthenticationFailed = OnAuthenticationFailed,
            };
            return options;
        }

        private string GetMetadataAddress(bool forToken = false)
        {
            // Generate the metadata address using the tenant and policy information
            if (Tenant.EndsWith("b2clogin.com"))
            {
                if (forToken)
                {
                    return $"https://{Tenant}/tfp/{ActiveDirectoryId}/{SignUpSignInPolicyId}/";
                }
                return $"https://{Tenant}/{ActiveDirectoryId}/v2.0/.well-known/openid-configuration";
            }
            return $"https://login.microsoftonline.com/tfp/{Tenant}/{SignUpSignInPolicyId}/v2.0/.well-known/openid-configuration";
        }

        /*
         *  On each call to Azure AD B2C, check if a policy (e.g. the profile edit or password reset policy) has been specified in the OWIN context.
         *  If so, use that policy when making the call. Also, don't request a code (since it won't be needed).
         */
        private Task OnRedirectToIdentityProvider(RedirectToIdentityProviderNotification<Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
        {
            var policy = notification.OwinContext.Get<string>("Policy");

            if (!string.IsNullOrEmpty(policy) && !policy.Equals(SignUpSignInPolicyId))
            {
                //public const string Email = "email";
                //public const string OfflineAccess = "offline_access";
                //public const string OpenId = "openid";
                //public const string OpenIdProfile = "openid profile";
                //public const string UserImpersonation = "user_impersonation";
                notification.ProtocolMessage.Scope = "openid";

                //public const string Code = "code";
                //public const string CodeIdToken = "code id_token";
                //public const string CodeIdTokenToken = "code id_token token";
                //public const string CodeToken = "code token";
                //public const string IdToken = "id_token";
                //public const string IdTokenToken = "id_token token";
                //public const string None = "none";
                //public const string Token = "token";
                notification.ProtocolMessage.ResponseType = "id_token";

                notification.ProtocolMessage.IssuerAddress = notification.ProtocolMessage.IssuerAddress.ToLower().Replace(SignUpSignInPolicyId.ToLower(), policy.ToLower());
            }
            else
            {
                policy = SignUpSignInPolicyId;
            }

            if (notification.ProtocolMessage.IssuerAddress.EndsWith($"?p={policy}", StringComparison.OrdinalIgnoreCase) || notification.ProtocolMessage.IssuerAddress.IndexOf($"?p={policy}?", StringComparison.OrdinalIgnoreCase) >= 0)
            {
                notification.ProtocolMessage.IssuerAddress = notification.ProtocolMessage.IssuerAddress.ToLower().Replace($"?p={policy.ToLower()}", string.Empty);
                notification.ProtocolMessage.Parameters.Add("p", policy);
            }

            return Task.FromResult(0);
        }

        /*
         * Catch any failures received by the authentication middleware and handle appropriately
         */
        private Task OnAuthenticationFailed(AuthenticationFailedNotification<Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
        {
            // Handle the error code that Azure AD B2C throws when trying to reset a password from the login page 
            // because password reset is not supported by a "sign-up or sign-in policy"
            if (notification.ProtocolMessage.ErrorDescription != null && notification.ProtocolMessage.ErrorDescription.Contains("AADB2C90118"))
            {
                LogManager.System.GetLogger(LogCategory.Security, LoggerIdentifier).Error($"Auth failed:{ObjectToString.Output(notification.ProtocolMessage)}");
            }
            else
            {
                LogManager.System.GetLogger(LogCategory.Security, LoggerIdentifier).Error($"Auth failed:{ObjectToString.Output(notification.Exception)}");
            }
            notification.HandleResponse();
            var returnUrl = notification.Options.PostLogoutRedirectUri;
            if (string.IsNullOrWhiteSpace(notification.Options.PostLogoutRedirectUri))
            {
                returnUrl = notification.Options.RedirectUri;
            }
            var host = new Uri(returnUrl).GetLeftPart(UriPartial.Authority);
            var errorRedirect = $"{host}/Admin/Public/Social/ExternalLogin.aspx?providerID={ProviderID}";
            errorRedirect = $"{errorRedirect}&returnUrl={returnUrl}";
            errorRedirect = $"{errorRedirect}&LoginError={HttpUtility.UrlEncode(notification.Exception.Message)}";
            notification.Response.Redirect(errorRedirect);

            return Task.FromResult(0);
        }

        /*
         * Callback function when an authorization code is received 
         */
        private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification notification)
        {
            // Extract the code from the response notification
            var code = notification.Code;

            if (Debug)
            {
                LogManager.System.GetLogger(LogCategory.Security, LoggerIdentifier).Debug($"Notification received: {ObjectToString.Output(notification)}");
            }
            var builder = ConfidentialClientApplicationBuilder
                .Create(ApplicationId)
                .WithRedirectUri(notification.Options.PostLogoutRedirectUri)
                .WithClientSecret(ApplicationKey);
            if (Tenant.EndsWith("b2clogin.com"))
            {
                builder = builder
                .WithB2CAuthority(GetMetadataAddress(true));
            }
            else
            {
                builder = builder
                .WithAuthority(GetMetadataAddress(true), false);
            }
            IConfidentialClientApplication cca = builder.Build();
            try
            {
                AuthenticationResult result = cca.AcquireTokenByAuthorizationCode(Scopes, code).ExecuteAsync().Result;

                if (Debug)
                {
                    LogManager.System.GetLogger(LogCategory.Security, LoggerIdentifier).Debug($"Token result:{ObjectToString.Output(result)}");
                }
            }
            catch (Exception ex)
            {
                LogManager.System.GetLogger(LogCategory.Security, LoggerIdentifier).Error(ObjectToString.Output(ex));
                throw;
            }
        }

        #endregion
    }
}
 
Adrian Ursu Dynamicweb Employee
Adrian Ursu
Reply

Hi Nicolai,

Thank you very much for the info.

In the meantime, we have managed to make progress on it.

Thank you,

Adrian

 

You must be logged in to post in the forum