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.
- Create a new Web App, I named it AuthTest. Use Individual Account for the authentication type.
- 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
- Add a necessary nuget package
Microsoft.AspNet.Identity
(it was auto included as a dependency toMicrosoft.AspNet.Authentication.Cookies
) - Rewrite the
\Models\ApplicationUser.cs
so it doesn't rely onEntity 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; } } }
- Add a new empty class in the Models folder,
ApplicationRole.cs
namespace AuthTest.Models { public class ApplicationRole { } }
- Delete the
\Models\ApplicationDbContext.cs
file - Delete the
\Migrations
folder - Delete the
\Controllers\AccountController.cs
file - Delete the
\Controllers\ManageController.cs
file - Delete the
\Views\Account
folder - Delete the
\Views\Manage
folder - Cleanup the
\startup.cs
,\Views\_ViewImports.cshtml and
\Views\Shared_layout.cshtml`.- In
startup.cs
, you change theservices.AddIdentity<ApplicationUser, IdentityRole>()
toservices.AddIdentity<ApplicationUser, ApplicationRole>()
- Since we removed a bunch of packages, this is just cutting out all of the removed packages.
- In
- 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.
- Now to do the actual auth stuff.
- Create a new folder,
AuthStuff
- 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); } } }
- 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(); } } }
- 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; } } } }
- Modify
\Startup.cs
to use your new middleware by changing theapp.UseIdentity();
toapp.UseMiddleware<AuthStuff.TestAuthenticationMiddleware>(new AuthStuff.TestAuthenticationOptions());
- 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 {
- Cut everything out of the 1\Views\Home\Index.cshtml1 file
- 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>
- 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.