ASP.NET MVC + OWIN + external authentication made easy


In this post I will describe how to build an ASP.NET MVC web application that is using an external authentication, via Slack, with minimum effort.

Prereqs

First of all, start off by creating a Slack application on https://api.slack.com/applications. Note the Client ID and Client Secret. The redirect URI needs to be updated pointing to the correct URL for this application. This will most likely be something like this http://localhost:123456.

Let’s ge to work!

Here’s the plan

The work will be devided into three major parts:

  • Set up the minimalistic ASP.NET MVC web application with an OWIN startup class and configure the application to use Slack as an external authentication provider.
  • Add an MVC controller with two actions, one allowing anonymous users and one with the requirement to login.
  • Finish the implementation using the external provider to authenticate the user, e.g. signing the user in properly.

Let’s go!

Set up the ASP.NET MVC web application + OWIN startup

Start off by creating a new project and add an empty ASP.NET MVC web application, e.g. hit File – New project – ASP.NET Web application. I use .NET Framework 4.6 for this project. Give the project some useful name and hit OK.

Select the ASP.NET 4.6 Emtpy template, check the MVC combobox, make sure is is using “No Authentiction” and not to host it in the cloud.

Select ASP.NET MVC template

Hit OK.

When the empty site is created it is a good time to add the OWIN startup class. This is a special class to be named Startup and to be placed under the root namespace of the web application. Add the class by right-clicking the project, Add – New Item and type owin in the template search field. The template OWIN Startup class is the one you will use. Adding this class will also install some nuget packages. We have to add some more, so let’s do that.

Install the nuget packages using the Package manager console:

Install-Package Microsoft.Owin.Host.SystemWeb
Install-Package Microsoft.Owin.Security.Cookies
Install-Package Microsoft.AspNet.Identity.Owin
Install-Package Owin.Security.Providers

OK, that’s it with all the packages for the project.

Now open the Startup class added before and remove the line [assembly: OwinStartup(typeof(…))]. This is not needed as long as the class is named Startup and placed in the root namespace.

In the Configuration method let’s add the things to make this application use Slack as an external security provider. The Startup class should look something like this:

    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                LoginPath = new PathString("/Account/Login")
            });

            app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

            var options = new SlackAuthenticationOptions
            {
                ClientId = "Slack app Client ID",
                ClientSecret = "Slack app Client secret",
                //TeamId = "" // optional
            };
            options.Scope.Add("identify");
            app.UseSlackAuthentication(options);
        }
    }

The client ID and client secret on line 15 and 16 are shown when creating the Slack app on https://api.slack.com/applications.

Now add a breakpoint at the top of the Configuration method and hit F5. The web application should start andthe debugger should hit the breakpoint.

Add Home controller and actions

Add a new controller to the project by right-clicking the Controllers folder in the solution explorer, then Add – Controller…. Select the MVC 5 Controller – Empty template and hit Add. To make things easy, name the controller HomeController and hit Add. The HomeController class is created and an Index action added automatically.

In the Views folder in the solution explorer, a new folder was created when adding the controller. Right-click the Home folder and Add – View…

Add index view

Make sure to name the view Index and uncheck the “Use a layout page”. Hit Add.

The view is created, so add some nice text to the body, like “Hello from /Home/Index”. If there was a red squiggly line in the Index action in the Home controller class, it should be gone by now.

Let’s add another action to the Home controller. Give it the name Secure and decorate with [Authorize]. The Home controller class should now look like this:

    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }

        [Authorize]
        public ActionResult Secure()
        {
            return View();
        }
    }

Add another view in the Views – Home folder in the solution explorer, give it the name Secure and add some text, like “This is secured”.

Hit F5 and navigate to the root of the web application. The “Hello from /Home/Index” should be shown. Now navigate to /Home/Secure.  If everything is OK you should now see a 404 Not found page, pointing to /Account/Login. This is the action pointed out in the Configuration method in the OWIN Startup class above, line 8. The authentication mechanism in the application is redirecting to that action an anonymous user bumps a non-anonymous action in the application. Now is a good time to implement that action and actually signing the user in, using Slack.

Signing the user in using an Account controller and external provider

When hitting the /Home/Secure action in the previous section, the user was redirected to /Account/Login and given a 404 Not found page. Let’s begin implementing the last part actually signing the user in and redirect him/her back to the Secure action.

Right-click the Controllers folder in the solution explorer, Add – Controller, select the empty template as before and name the controller AccountController. Rename the Index action to Login and add a returnUrl string parameter:

    public class AccountController : Controller
    {
        public ActionResult Login(string returnUrl)
        {
            return View();
        }
    }

As you might expect, this is not the complete implementation authentication mechanism. I’ll paste in the actual implementation and explain it below:

    public class AccountController : Controller
    {
        public ActionResult Login(string returnUrl)
        {
            return new ChallengeResult("Slack", Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }));
        }

        public ActionResult ExternalLoginCallback(string returnUrl)
        {
            var loginInfo = HttpContext.GetOwinContext().Authentication.GetExternalLoginInfo();
            if (loginInfo.ExternalIdentity.IsAuthenticated)
            {
                SignIn();
                return Redirect(returnUrl);
            }

            return new HttpUnauthorizedResult();
        }

        private void SignIn()
        {
            var loginInfo = HttpContext.GetOwinContext().Authentication.GetExternalLoginInfo();
            if (loginInfo.ExternalIdentity.IsAuthenticated)
            {
                var manager = HttpContext.GetOwinContext().Authentication;
                var claims = loginInfo.ExternalIdentity.Claims;
                manager.SignIn(new AuthenticationProperties
                {
                    AllowRefresh = true,
                    IsPersistent = false,
                    ExpiresUtc = DateTimeOffset.Now.AddMinutes(60)
                },
                new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie));
            }
        }

        private class ChallengeResult : HttpUnauthorizedResult
        {
            public ChallengeResult(string provider, string redirectUri)
            {
                LoginProvider = provider;
                RedirectUri = redirectUri;
            }

            private string LoginProvider { get; }
            private string RedirectUri { get; }

            public override void ExecuteResult(ControllerContext context)
            {
                var properties = new AuthenticationProperties() { RedirectUri = RedirectUri };
                context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider);
            }
        }
    }

The implementation of the Login action returns a ChallengeResult, a private class starting on line 37. This is a class inherited from the HttpUnauthorizedResult, causing the user to be redirected to the external login provider, via the ExecuteResult method on line 48. In this case we’re redirected to Slack.

In the information passed to the external provider goes a link to a callback, /Account/ExternalLoginCallback, on line 8, for the external provider to redirect the user back to our web site. In that action we are able to read all the claims from the external provider if the login was successful. We might for instance use this information to map the external user to a locally stored user profile, to get a personalized user experience in our web application.

The SignIn method on line 20 is the one taking care of the actual signing in to our web application through the call to the OWIN context’s Authentication property on line 25 and 27.

When the implementation above is in place, hit F5 and navigate to /Home/Secure. The user should now be redirected according to:

Below is a screenshot of the secured view, /Home/Secure/, listing all the claims from a successful Slack login:

Slack-claims

Wrap up

The code in this post is located here: https://github.com/NerdBlogs/Minimalistic-ASP.NET-MVC-OWIN-with-authentication. Clone and have a go!

One comment

Leave a comment