Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Alejandro Ocampo 33 posts 225 karma points c-trib
    Jan 11, 2019 @ 14:03
    Alejandro Ocampo
    2

    Azure Active Directory Implementation for Umbraco

    Hi there,

    This post is just something I would like to share since I haven't found any solid guidance on the internet and I think it might be helpful to share it here as always feedback is appreciated if you see something is not secure or can be done better.

    The task was to implement Azure Active Directory (from now on AAD) to validate access to an Umbraco site (Umbraco users or members are NOT involved with this validation).

    This is what I did to achieve this:

    OwinStatup.cs

    using x.Configuration;
    using Microsoft.IdentityModel.Protocols.OpenIdConnect;
    using Microsoft.Owin;
    using Microsoft.Owin.Security;
    using Microsoft.Owin.Security.Cookies;
    using Microsoft.Owin.Security.OpenIdConnect;
    using Owin;
    using System.Globalization;
    using System.IdentityModel.Tokens;
    using System.Threading.Tasks;
    using Umbraco.Web;
    using Umbraco.Web.Security.Identity;
    
    [assembly: OwinStartup("OwinStartup", typeof(x.OwinStartup))]
    
    namespace x.Authentication
    {
        public class OwinStartup : UmbracoDefaultOwinStartup
        {
            private static readonly string _clientId = ConfigurationHelper.GetValue("azureAd:clientId");
            private static readonly string _tenantId = ConfigurationHelper.GetValue("azureAd:tenantId");
            private static readonly string _aActiveDirectoryInstance = ConfigurationHelper.GetValue("azureAd:AADInstance");
            private static readonly string _redirectUri = ConfigurationHelper.GetValue("azureAd:RedirectUri");
            private static readonly string _logOutUri = ConfigurationHelper.GetValue("azureAd:LogOutUri");
            private readonly string _authority = string.Format(CultureInfo.InvariantCulture, _aActiveDirectoryInstance, _tenantId);
    
            public override void Configuration(IAppBuilder app)
            {
                base.Configuration(app);
    
                app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
    
                app.UseCookieAuthentication(new CookieAuthenticationOptions());
                app.UseOpenIdConnectAuthentication(
                    new OpenIdConnectAuthenticationOptions
                    {
                        // Sets the ClientId, authority, RedirectUri as obtained from web.config
                        ClientId = _clientId,
                        Authority = _authority,
                        RedirectUri = _redirectUri,
                        // PostLogoutRedirectUri is the page that users will be redirected to after sign-out.
                        PostLogoutRedirectUri = _logOutUri,
                        Scope = OpenIdConnectScope.OpenIdProfile,
                        // ResponseType is set to request the id_token - which contains basic information about the signed-in user
                        ResponseType = OpenIdConnectResponseType.IdToken,
                        // ValidateIssuer set to false to allow personal and work accounts from any organization to sign in to your application
                        // To only allow users from a single organizations, set ValidateIssuer to true and 'tenant' setting in web.config to the tenant name
                        // To allow users from only a list of specific organizations, set ValidateIssuer to true and use ValidIssuers parameter
                        TokenValidationParameters = new TokenValidationParameters
                        {
                            ValidateIssuer = false
                        },
                        // OpenIdConnectAuthenticationNotifications configures OWIN to send notification of failed authentications to OnAuthenticationFailed method
                        Notifications = new OpenIdConnectAuthenticationNotifications
                        {
                            AuthenticationFailed = context =>
                            {
                                context.HandleResponse();
                                context.Response.Redirect("/your-login-page/?message=" + context.Exception.Message);
                                return Task.FromResult(0);
                            }
                        }
                    }
                );
    
                //https://issues.umbraco.org/issue/U4-7213
                //https://shazwazza.com/post/getting-umbraco-to-work-with-azure-easy-auth/
                app
                    .UseUmbracoBackOfficeCookieAuthentication(ApplicationContext, PipelineStage.PostAuthenticate)
                    .UseUmbracoBackOfficeExternalCookieAuthentication(ApplicationContext, PipelineStage.PostAuthenticate)
                    .UseUmbracoPreviewAuthentication(ApplicationContext, PipelineStage.Authorize);
            }
        }
    }
    

    AdAuthorize.cs

    using System.Web.Mvc;
    
    namespace x.Attributes
    {
        public class AdAuthorize : AuthorizeAttribute
        {
            // Set default Unauthorized Page Url here to login
            public string LogInUrl { get; set; } = "/your-login-page/";
    
            protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
            {
                if (filterContext.HttpContext.Request.IsAuthenticated)
                {
                    //do normal process
                    base.HandleUnauthorizedRequest(filterContext);
                }
                else
                {
                    filterContext.Result = new RedirectResult(LogInUrl);
                }
            }
        }
    }
    

    FilterConfig.cs

    using x.Attributes;
    using System.Web.Mvc;
    
    namespace x.Authentication
    {
        public class FilterConfig
        {
            public static void RegisterGlobalFilters(GlobalFilterCollection filters)
            {
                filters.Add(new AdAuthorize());
            }
        }
    }
    

    Global.cs

    namespace x.Web
    {
        public class Global : UmbracoApplication
        {
            protected override void OnApplicationStarting(object sender, EventArgs e)
            {
                FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            }
        }
    }
    

    xSurfaceController.cs

    using Microsoft.Owin.Security;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
    using Umbraco.Web.Mvc;
    
    namespace x.Controllers.Actions
    {
        public class xSurfaceController : SurfaceController
        {
            [AllowAnonymous]
            public void Login()
            {
                //Send an OpenID Connect sign -in request.
                if (Request.IsAuthenticated)
                {
                    Response.Redirect("/");
                }
                else
                {
                    HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/" });
                }
            }
    
            public void LogOut()
            {
                // To sign out the user, you should issue an OpenIDConnect sign out request.
                if (Request.IsAuthenticated)
                {
                    var authTypes = HttpContext.GetOwinContext().Authentication.GetAuthenticationTypes();
                    HttpContext.GetOwinContext().Authentication.SignOut(authTypes.Select(t => t.AuthenticationType).ToArray());
                    Request.GetOwinContext().Authentication.GetAuthenticationTypes();
                }
                else
                {
                    Response.Redirect("/your-login-page/");
                }
            }
        }
    }
    

    YourLoginPageController.cs

    using System.Web.Mvc;
    using Umbraco.Web.Models;
    using Umbraco.Web.Mvc;
    
    namespace x.Controllers.Hijacks
    {
        [AllowAnonymous]
        public class LoginPageController : RenderMvcController
        {
            public override ActionResult Index(RenderModel model)
            {
                return View("~/Views/YourLoginPage.cshtml");
            }
        }
    }
    

    Changes in Web.config

    <add key="owin:appStartup" value="OwinStartup" />
    <add key="AAD:AADInstance" value="https://login.microsoftonline.com/{0}" />   
    <add key="AAD:tenantId" value="ffff-ccccc-rrrrr-tttt-sdfsdfsdfsd" />
    <add key="AAD:clientId" value="ffff-ccccc-rrrrr-tttt-sdfsdfsdfsd" />
    <add key="AAD:RedirectUri" value="http://localhost:12345/" />
    <add key="AAD:LogOutUri" value="http://localhost:12345/your-login-page/" />
    

    and

    <authentication mode="None" />
    

    Using this global filter I'm securing the access to the whole site with the [AdAuthorize] attribute and I'm only allowing the login page with [AllowAnonymous] attribute.

    This was the only way I got to work as I wanted using AAD. The current behavior is:

    • if a user requesting a url is NOT authenticated it will get redirected to /your-login-page/.

    • if a user requesting a url IS authenticated it will let the user continue.

    Hope this helps!

    Thanks,

    Ale

  • John Bergman 407 posts 959 karma points
    Jan 12, 2019 @ 05:33
    John Bergman
    0

    Thanks! Good info :-)

Please Sign in or register to post replies

Write your reply to:

Draft