0

We have integrated Azure B2C Multifactor Authentication logic using Openid connect.

We are able to view Login page and able to authenticate user via Azure MFA. Our problem is when we are trying to access Claims we found that Claim has SiItecore user details instead of Authenticated user information.

Does anyone has any solution on this?

1 Answer 1

0

The solution is you can set to set "Require Login" to specific restricted pages and you can create a virtual user and then sign in to see your pages.

You can then use following property to verify whether user is authenticated or not.

Sitecore.Context.User.IsAuthenticated

So , to create user you can use something like :

#region Namespace References
using Sitecore.Pipelines.HttpRequest;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
#endregion

namespace AuthenticationHelper.Pipelines
{  
  public class B2CUserResolver : HttpRequestProcessor
{
    #region Custom Methods
    public override void Process(HttpRequestArgs args)
    {
        if (Sitecore.Context.User.IsAuthenticated) { return; }

        var loginResultUrl = Settings.SignInCallbackUrl;
        if (loginResultUrl.ToLower().EndsWith(args.Url.FilePathWithQueryString.ToLower()))
        {
            //We need to get the token and other post data that B2C sent us
            var idToken = HttpContext.Current.Request.Form["id_token"];
            var idstate = HttpContext.Current.Request.Form["state"];

            if (string.IsNullOrEmpty(idToken)) { return; }
            //Validate the token
            var validToken = B2CHelper.ValidateToken(idToken);
            if (validToken == null)
            {
                if (!String.IsNullOrWhiteSpace(idstate))
                {
                    Sitecore.Web.WebUtil.Redirect(idstate, false);
                    args.AbortPipeline();
                }
                else
                {
                    return;
                }
            }

            Dictionary<string, string> tokenClaims = new Dictionary<string, string>();
            foreach (var c in validToken.Claims)
            {
                tokenClaims.Add(c.Type, c.Value);
            }
            bool loggedin = this.CreateAndLoginUser(tokenClaims["given_name"], tokenClaims["given_name"] + " " + tokenClaims["family_name"], tokenClaims);
            if (loggedin)
            {
                if (!String.IsNullOrWhiteSpace(idstate))
                {
                    Sitecore.Web.WebUtil.Redirect(idstate, false);
                    args.AbortPipeline();
                }
            }
        }
    }

    private bool CreateAndLoginUser(string userName, string fullName, Dictionary<string, string> props)
    {
        bool _res = false;
        Sitecore.Security.Accounts.User currentUser;
        bool success = B2CHelper.CreateVirtualUser(userName, fullName, props, out currentUser);
        if (success)
        {
            bool loggedin = Sitecore.Security.Authentication.AuthenticationManager.LoginVirtualUser(currentUser);
            _res = loggedin;
        }
        return _res;
    }
    #endregion
 }
}

B2CHelper class as follows :

#region Namespace References
using Sitecore.Web;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Script.Serialization;
using System.Security.Cryptography;
using System.IdentityModel.Tokens;
using System.IdentityModel.Selectors;
using System.Text;
#endregion

namespace AuthenticationHelper
{

public static class B2CHelper
{
    #region Variables        
    static string appID = Settings.ClientId;
    static string redirectURL = Settings.SignInCallbackUrl;
    static string policyName = Settings.PolicyName;
    static string directoryName = Settings.DirectoryName;
    #endregion

    #region Custom Methods
    public static void InitializeMetadata()
    {
        if (HttpContext.Current.Application[Settings.ApplicationName] != null) return;
        string metadataEndpoint = String.Format("https://login.microsoftonline.com/{0}/v2.0/.well-known/openid-configuration?p={1}", directoryName, policyName);

        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(metadataEndpoint);
        request.Method = "GET";
        string respJson = String.Empty;
        using (var response = request.GetResponse())
        {
            using (var sr = new StreamReader(response.GetResponseStream()))
            {
                respJson = sr.ReadToEnd();
            }
        }
        var serializer = new JavaScriptSerializer();

        B2CMetadata config = serializer.Deserialize<B2CMetadata>(respJson);

        config = serializer.Deserialize<B2CMetadata>(respJson);
        HttpContext.Current.Application[Settings.ApplicationName] = config;
    }

    public static KeysWrapper GetB2CKeys()
    {
        var config = (B2CMetadata)HttpContext.Current.Application[Settings.ApplicationName];
        HttpWebRequest keysRequest = (HttpWebRequest)WebRequest.Create(config.jwks_uri);
        keysRequest.Method = "GET";
        string keysJson = String.Empty;
        using (var kresponse = keysRequest.GetResponse())
        {
            using (var ksr = new StreamReader(kresponse.GetResponseStream()))
            {
                keysJson = ksr.ReadToEnd();
            }
        }
        var keySerializer = new JavaScriptSerializer();
        KeysWrapper keys = keySerializer.Deserialize<KeysWrapper>(keysJson);

        return keys;
    }

    public static void InitiateCustomerSignInUp(B2CMetadata b2cMetadata)
    {
        var config = (B2CMetadata)HttpContext.Current.Application[Settings.ApplicationName];
        config = config != null ? config : b2cMetadata;
        string authEndpoint = config.authorization_endpoint;
        //We are using the Nonce parameter to avoid replay attacks
        string nonce = Guid.NewGuid().ToString();
        HttpContext.Current.Session["_NONCE_"] = nonce;

        string state = redirectURL;
        string rType = "id_token";
        string rMode = "form_post";
        string clientId = appID;
        string redirUri = redirectURL;
        string scope = "openid";
        string policy = policyName;
        string prompt = "login";

        var sb = new StringBuilder(authEndpoint)
.Append($"&client_id={clientId}")
.Append($"&scope={scope}")
.Append($"&response_type={rType}&response_mode={rMode}")
.Append($"&redirect_uri={redirUri}")
.Append($"&state={state}&nonce={nonce}")
.Append($"&prompt={prompt}");
        WebUtil.Redirect(sb.ToString());
    }

    public static void LogOut()
    {
        //Just logout
        Sitecore.Security.Authentication.AuthenticationManager.Logout();
    }

    public static bool CreateVirtualUser(string userName, string displayName, Dictionary<string, string> properties, out Sitecore.Security.Accounts.User user)
    {
        bool _res = false;
        user = null;
        try
        {
            string fullUsername = String.Format("extranet\\{0}", userName);
            // Create virtual user
            var virtualUser = Sitecore.Security.Authentication.AuthenticationManager.BuildVirtualUser(fullUsername, true);
            virtualUser.RuntimeSettings.Load();
            virtualUser.RuntimeSettings.AddedRoles.Clear();

            //set profile properties
            virtualUser.Profile.Email = userName;
            virtualUser.Profile.Name = displayName;
            foreach (var prop in properties)
            {
                virtualUser.Profile.SetCustomProperty(prop.Key, prop.Value);
            }
            virtualUser.Profile.Save();
            virtualUser.RuntimeSettings.IsVirtual = true;

            virtualUser.RuntimeSettings.Save();
            user = virtualUser;
            _res = true;
        }
        catch (Exception ex)
        {
            _res = false;
        }
        return _res;
    }

    public static JwtPayload ValidateToken(string rawToken)
    {
        JwtPayload token;
        if (Validate(rawToken, out token))
        {
            return token;
        }
        else
        {
            return null;
        }
    }
    private static bool Validate(string rawToken, out JwtPayload token)
    {
        try
        {
            var handler = new JwtSecurityTokenHandler();
            if (!handler.CanReadToken(rawToken) || !handler.CanValidateToken)
            {
                token = null;
                return false;
            }
            var tkn = handler.ReadToken(rawToken) as JwtSecurityToken;
            JwtHeader header = tkn.Header;
            JwtPayload jwtPayload = tkn.Payload;
            //Get the keys for validation
            var keysDictionary = B2CHelper.GetB2CKeys();
            var key = keysDictionary.FindByKidValue(tkn.Header["kid"].ToString());
            var publicKey = new { e = key["e"], n = key["n"] };
            var signatureToValidate = tkn.EncodedHeader + "." + tkn.EncodedPayload;

            //Create a CryptoServiceProvider to represent the public key
            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
            rsa.ImportParameters(
            new RSAParameters()
            {
                Modulus = new Microsoft.Owin.Security.DataHandler.Encoder.Base64UrlTextEncoder().Decode(publicKey.n),
                Exponent = new Microsoft.Owin.Security.DataHandler.Encoder.Base64UrlTextEncoder().Decode(publicKey.e),

            });
            var rawSignature = tkn.RawSignature;

            //Add the provider to the tokenvalidation parameters
            var options = new TokenValidationParameters()
            {
                IssuerSigningKeyResolver =
            ((tk, st, kid, tvp) => { return new RsaSecurityKey(rsa); }),
                ValidIssuer = tkn.Issuer,
                ValidAudiences = tkn.Audiences,
                ValidateLifetime = true,
                ValidateIssuer = true,
                CertificateValidator = X509CertificateValidator.None
            };
            SecurityToken secToken;

            //Call the validator with our keys
            handler.ValidateToken(rawToken, options, out secToken);
            token = jwtPayload;
            return true;
        }
        catch (SecurityTokenValidationException vex)
        {
            //Validation exceptions are caught here
            token = null;
            return false;
        }
        catch (Exception ex)
        {
            token = null;
            return false;
        }
    }
    #endregion
}
}

Please find link to the following Sitecore Marketplace Module which is created for the same purpose :

https://marketplace.sitecore.net/en/Modules/S/SitecoreAzureAdloginusingOpenIDAuthentication.aspx

4
  • Thanks Pratik for your response. I have already tried this solution but did not worked. Please note I am using Sitecore 7.2 version. Commented Jul 9, 2018 at 16:21
  • Can you please tell what error you are getting ? because this module has no dependency of different Sitecore versions. Commented Jul 9, 2018 at 16:38
  • Thanks Pratik. I need to integrate your module in existing web form code with Siteocore 7.2 version. After updating few dlls versions and dependencies also little bit of code I am able to retrieve the Azure Ad token value. Thanks for saving my life. Commented Jul 10, 2018 at 19:35
  • @Jitendra , good to know it is working , thanks for the update ! Commented Jul 26, 2018 at 16:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.