ASP.NET vNext Custom Authentication

Welcome all, here's my delima. I need to be able to implement my own custom Authentication middleware in vNext. I needed to do this because of a requirement where I cannot use cookies. Reason being is that my site will be iFramed into another application. I know, its not a good solution, but it is what it is. There was little to no documentation on how to implement your own authentication middleware so I set out on a journey. It's relatively easy to set it up and get it going. Now, this is not for cookieless authentication. This is a plain, bare bones, nothing fancy authentication replacement. Basically it will pretend the user is always authenticated, whether they really are or not.

So, here's what I did.

  1. Create a new Web App, I named it AuthTest. Use Individual Account for the authentication type.
  2. Remove all of the superfluous nuget packages from project.json
    EntityFramework.Commands
    EntityFramework.MicrosoftSqlServer
    Microsoft.ApplicationInsights.AspNet
    Microsoft.AspNet.Authentication.Cookies
    Microsoft.AspNet.Diagnostics.Entity
    Microsoft.AspNet.Identity.EntityFramework
    Microsoft.Extensions.CodeGenerators.Mvc
    
  3. Add a necessary nuget package Microsoft.AspNet.Identity (it was auto included as a dependency to Microsoft.AspNet.Authentication.Cookies)
  4. Rewrite the \Models\ApplicationUser.cs so it doesn't rely on Entity Framework
    using System.Security.Principal;
    
    namespace AuthTest.Models
    {
        public class ApplicationUser : IIdentity
        {
            public ApplicationUser(string authenticationType, bool isAuthenticated, string name)
            {
                this.AuthenticationType = authenticationType;
                this.IsAuthenticated = isAuthenticated;
                this.Name = name;
            }
    
            public string AuthenticationType
            {
                get;
            }
    
            public bool IsAuthenticated
            {
                get;
            }
    
            public string Name
            {
                get;
            }
        }
    }
    
  5. Add a new empty class in the Models folder, ApplicationRole.cs
    namespace AuthTest.Models
    {
        public class ApplicationRole
        {
        }
    }
    
  6. Delete the \Models\ApplicationDbContext.cs file
  7. Delete the \Migrations folder
  8. Delete the \Controllers\AccountController.cs file
  9. Delete the \Controllers\ManageController.cs file
  10. Delete the \Views\Account folder
  11. Delete the \Views\Manage folder
  12. Cleanup the \startup.cs, \Views\_ViewImports.cshtml and \Views\Shared_layout.cshtml`.
    • In startup.cs, you change the services.AddIdentity<ApplicationUser, IdentityRole>() to services.AddIdentity<ApplicationUser, ApplicationRole>()
    • Since we removed a bunch of packages, this is just cutting out all of the removed packages.
  13. Hit F5, make sure the site root page still comes up without any errors. Pretty much everything on there won't work because we just chopped it all out.
  14. Now to do the actual auth stuff.
  15. Create a new folder, AuthStuff
  16. Create a new class TestAuthenticationHandler
    using System.Security.Claims;
    using System.Threading.Tasks;
    using AuthTest.Models;
    using Microsoft.AspNet.Authentication;
    using Microsoft.AspNet.Http.Authentication;
    
    namespace AuthTest.AuthStuff
    {
        public class TestAuthenticationHandler : AuthenticationHandler<TestAuthenticationOptions>
        {
            protected override Task<AuthenticateResult> HandleAuthenticateAsync()
            {
                var claimsPrincipal = new ClaimsPrincipal(new ApplicationUser(this.Options.AuthenticationScheme, true, "test"));
                var authenticationProperties = new AuthenticationProperties();
                var authenticationTicket = new AuthenticationTicket(claimsPrincipal, authenticationProperties, this.Options.AuthenticationScheme);
                var result = AuthenticateResult.Success(authenticationTicket);
    
                return Task.FromResult(result);
            }
        }
    }
    
  17. Create a new class TestAuthenticationMiddleware
    using Microsoft.AspNet.Authentication;
    using Microsoft.AspNet.Builder;
    using Microsoft.Extensions.Logging;
    using Microsoft.Extensions.WebEncoders;
    
    namespace AuthTest.AuthStuff
    {
        public class TestAuthenticationMiddleware : AuthenticationMiddleware<TestAuthenticationOptions>
        {
            public TestAuthenticationMiddleware(RequestDelegate next, TestAuthenticationOptions testAuthenticationOptions, ILoggerFactory loggerFactory, IUrlEncoder urlEncoder)
                : base(next, testAuthenticationOptions, loggerFactory, urlEncoder)
            {
            }
    
            protected override AuthenticationHandler<TestAuthenticationOptions> CreateHandler()
            {
                return new TestAuthenticationHandler();
            }
        }
    }
    
  18. Create a new class TestAuthenticationOptions
    using Microsoft.AspNet.Authentication;
    using Microsoft.Extensions.OptionsModel;
    
    namespace AuthTest.AuthStuff
    {
        public class TestAuthenticationOptions : AuthenticationOptions, IOptions<TestAuthenticationOptions>
        {
            public TestAuthenticationOptions()
            {
                this.AuthenticationScheme = "AuthStuff";
                this.AutomaticAuthenticate = true;
            }
    
            public TestAuthenticationOptions Value
            {
                get
                {
                    return this;
                }
            }
        }
    }
    
  19. Modify \Startup.cs to use your new middleware by changing the app.UseIdentity(); to app.UseMiddleware<AuthStuff.TestAuthenticationMiddleware>(new AuthStuff.TestAuthenticationOptions());
  20. Setup your \Controllers\HomeController.cs to require authentication by adding the [Authorize] attribute to the class, the top should look like this:
    using Microsoft.AspNet.Authorization;
    using Microsoft.AspNet.Mvc;
    
    namespace AuthTest1.Controllers
    {
        [Authorize]
        public class HomeController : Controller
        {
    
  21. Cut everything out of the 1\Views\Home\Index.cshtml1 file
  22. Modify the 1\Views\Home\Shared_Layout.cshtml1 file to be the following:
    <!DOCTYPE html>
    <html>
        <head>
        </head>
        <body>
            <pre>
            User.Identity.IsAuthenticated: @(User?.Identity?.IsAuthenticated)
            User.Identity.Name: @(User?.Identity?.Name ?? "<NULL>")
            User.Identity.AuthenticationType: @(User?.Identity?.AuthenticationType ?? "<NULL>")
            </pre>
                @RenderBody()
        </body>
    </html>
    
  23. Hit F5, you will see that the user is authenticated, has a username and was authenticated using the new custom authentication middleware.

Some notes:

The IsSignedIn extension method on the Principal object will only return true if it was done using the Cookie Authentication middleware. So if you're wondering why that is returning false, bug Microsoft as I personally feel it should use the IsAuthenticated property on the identity.

There really needs to be a bit more documentation on this, but as it's probably not going to be something alot of people need to worry about I understand why there is not.

I did this using dnx 1.0.0-rc1-update1.