Using Owin External Login without ASP.NET Identity
Join the DZone community and get the full member experience.
Join For FreeASP.NET MVC5 has excellent support for external social login providers (Google, Facebook, Twitter) integrating with the ASP.NET Identity system. But what if we want to use external logins directly without going through ASP.NET Identity? Using external logins together with ASP.NET Identity is very simple to get started with, but it requires all users to register with the application. External logins are just another authentication method against the internal ASP.NET Identity user. In some cases there is no need for that internal database, it would be better to get rid of it and use the external login providers without ASP.NET Identity. That’s possible, but requires a bit of manual coding.
For public facing web applications I think that it is often a good idea to use ASP.NET Identity as it doesn’t tie the user to a specific login provider. But if we are fine with using one and only one specific login provider for each user it’s possible to skip ASP.NET Identity. It could be an organization that heavily relies on Google Apps already so that all users are known to have Google accounts. It could be an application that uses SAML2 based federative login through Kentor.AuthServices.
In this post I’ll start with a freshly created ASP.NET MVC Application without any authentication at all and make it use Google authentication, without ASP.NET Identity being involved at all. The complete code is available on my GitHub account.
Creating the Project
First, I’ve created a normal ASP.NET MVC 5.2.0 using Visual Studio 2013. The default is to enable individual user accounts, so that have to be changed to using no authentication.
Running the created project gives the default ASP.NET MVC template, with no trace of any sign in link or anything. Exactly as we want it. The next step is to add a resource that requires authentication and see how it fails when there is no way to login.
Adding a Secure Page
To test the login, there must be some page that requires authentication. I’ve created a simple Secure
action in the HomeController
.
[Authorize] public ActionResult Secure() { ViewBag.Message = "Secure page."; return View(); }
Running the application and trying to access /Home/Secure gives a standard 401.1 UnAuthorized error page, which is exactly what’s expected.
Adding the Cookie Middleware
The Owin authentication model differentiates between the login middleware and the session handling middleware. To use Google login we first need to set up a cookie middleware that will set and keep a cookie with the identity once we’ve logged in. First a number of nuget packages need to be added.
- Microsoft.Owin.Security.Cookies for the cookie middleware.
- Microsoft.Owin.Security.Google for the Google authentication.
- Microsoft.Owin.Host.SystemWeb to run the Owin pipeline on top of IIS.
The cookie middleware is set up through a Startup.Auth.cs
file. I’m following the conventions from the ASP.NET MVC template for new projects with individual user accounts. The file doesn’t have to be named that, but I think it makes it more easy to find.
public partial class Startup { private void ConfigureAuth(IAppBuilder app) { var cookieOptions = new CookieAuthenticationOptions { LoginPath = new PathString("/Account/Login") }; app.UseCookieAuthentication(cookieOptions); } }
The cookie authentication middleware is both responsible for persisting the identity across calls and for redirecting any unauthenticated requests to secure pages to a login page. We’ll follow the convention and name it /Account/Login.
To get the ConfigureAuth
method called some plumbing is done in a Startup.cs
file.
[assembly: OwinStartupAttribute(typeof(SocialLoginWithoutIdentity.Startup))] namespace SocialLoginWithoutIdentity { public partial class Startup { public void Configuration(IAppBuilder app) { ConfigureAuth(app); } } }
With these files added, running the application and clicking on the Secure link will give a 404 not found error on /Account/Login/. We need to have an action at that location that can start the login sequence.
Adding Google Login
The login is initiated from the /Account/Login location, by creating a ChallengeResult
. TheChallengeResult
class is a simplified version of the one from the standard template.
public ActionResult Login(string returnUrl) { // Request a redirect to the external login provider return new ChallengeResult("Google", Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl })); } // Implementation copied from a standard MVC Project, with some stuff // that relates to linking a new external login to an existing identity // account removed. private class ChallengeResult : HttpUnauthorizedResult { public ChallengeResult(string provider, string redirectUri) { LoginProvider = provider; RedirectUri = redirectUri; } public string LoginProvider { get; set; } public string RedirectUri { get; set; } public override void ExecuteResult(ControllerContext context) { var properties = new AuthenticationProperties() { RedirectUri = RedirectUri }; context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider); } }
The Google login provider needs to be configured in Startup.Auth.cs
. The ClientId
and ClientSecret
is available by registering the application on Google developers console. Remember to also enable theGoogle+ API for the application or the Google middleware won’t work (I lost a few hours on debugging before finding out that was the problem…).
app.SetDefaultSignInAsAuthenticationType(cookieOptions.AuthenticationType); app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions { ClientId = GoogleClientId, ClientSecret = GoogleClientSecret });
The call to UseGoogleAuthentication
should be quite obvious why it’s needed. But the first one toSetDefaultSignInAsAuthenticationType
is not as obvious. Looking at the owin external authentication pipeline a social login middleware normally relies on the external cookie middleware registered before the social login middleware. In the setup of the external cookie middleware, it sets itself as the default signin type. That’s how the social login middleware knows that it should use the external cookie. In this setup there is no external cookie, so we have to manually set the main cookie middleware as the default signin type. The cookie middleware will only issue a cookie if the AuthenticationType
matches the one in the identity created by the social login middleware.
One more piece of code is needed. In the Login
action there’s a reference to an ExternalLoginCallback
action that is called after the external login is performed. In the default implementation in the template, theExternalLoginCallback
looks up the matching ASP.NET Identity account and signs in to that. But in this case everything is already done. The external sign in is complete and a cookie has been issued. TheExternalLoginCallback
just needs to redirect the user back to the secure location that triggered the login in the first place.
public ActionResult ExternalLoginCallback(string returnUrl) { return new RedirectResult(returnUrl); }
Information Returned by Google
When using ASP.NET Identity, the information returned by Google is mostly ignored. When using this setup, all that information is readily available as claims on the current identity. In the example application, I’ve listed all the available claims. It’s a nameidentifier that is a unique user identifier for the Google User for this application. Another application using Google SignIn would receive another identifier. Then there’s the full name, first name and last name, the e-mail address and finally my Google plus profile URL. That’s all the information needed about a user in many applications. The name and the e-mail are useful. Having a unique name identifier makes it possible to save user data with that as an id and retrieve it later.
As long as it is fine to lock the user in to the identity provider that was used when signing up, there is no need for ASP.NET Identity in simple applications.
Published at DZone with permission of Anders Abel, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Trending
-
Introduction to Domain-Driven Design
-
Operator Overloading in Java
-
The SPACE Framework for Developer Productivity
-
Mastering Go-Templates in Ansible With Jinja2
Comments