Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
West Wind WebSurge - Rest Client and Http Load Testing for Windows

Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application


:P
On this page:

Note: This article applies to classic .NET Frameworks based ASP.NET MVC applications only. It does not work with .NET Core.

As of ASP.NET 4, ASP.NET provides a fairly useful identity system. If you create a new project and choose an MVC project and choose to add both internal and external authentication, it’s fairly straight forward to get a reasonable identity implementation into your application.

However, if you have an existing application, or if the full Entity Framework based identity structure doesn’t work for you, then the process to hook up a minimal and custom implementation that uses your own domain/business model and classes is not exactly as straightforward. You have to either rip out the pieces you don’t need from an full template install, or add the necessary pieces. In this post I hope I can show you how to do the latter, showing only the pieces that you need.

The process is not necessarily hard – but it’s not very well documented. It’s difficult to find the information necessary to just create the necessary handlers that can deal with linking accounts to external oAuth providers like Google, Facebook, GitHub and so on. So in this post I’ll go over this scenario.

A Real World Use Case

I’ve come down this path myself over the last weekend as one of my old sites – CodePaste.net – required an update to the old OpenID authentication I had running on that site. Codepaste is a pretty old site; in fact it is was the first MVC application I ever built 😃. But it’s been running unencumbered for the last 7 years. That is until Google decided to pull the plug on the old OpenId implementation. Promptly I started getting a ton of emails, and decided I had to re-implement the external providers.

The old implementation used DotNetOpenAuth combined with FormsAuthentication which I blogged about years ago, but DotnetOpenAuth seems to have fallen out of favor over the years, so I decided to bite the bullet and switch over to the newer, built-in OWIN based Identity functionality in ASP.NET 4.

When you first start looking at Identity the amount of information out there is rather overwhelming. Lots of intro articles that talk about how to use the stuff ‘as is’ without customization. But there’s not a lot of information on using the core Identity pieces without the full bore UserManager and Entity Framework data store, in order to use just the Authentication/Authorization on their own and integrate them with my own business objects/domain model.

Just the Core Identity Features for Local and External Logins

The goal of this post is to show the minimal pieces of the OWIN Identity system to handle Local and External account logins and hook them to a custom domain model rather than using the Entity Framework based UserManager. In short focus on the system components that manage authentication and leave the user management to the application.

Just Show me what I need!

The main text of this article is lengthy as it covers the CodePaste.NET login and user management features relevant to the discussion in addition to the core login features. For those of you that want just the nuts and bolts there’s also a summary section at the end that shows just the relevant code in skeleton format that you can just plug into your controllers. I’ve also linked to the full source code of the Account Controller I discuss here if you want to see the full code, rather than bite-sized snippets.

For more detail… read on.

OWIN based Identity in ASP.NET MVC 5

If you haven’t used the new Identity features in ASP.NET MVC 5 at all, I suggest that you check it out first by creating a new MVC Web site and letting it create a default Web site with Individual User Authentication enabled. This will let you see how the default implementation works. If you’re looking at code specific implementation details you can check out the AccountController and ManageController which deal with the user management interfaces. AccountController deals with logging in for both local and external accounts, while ManageController deals with setting up new accounts, email confirmations etc. This is part of the reason why Identity feels pretty overwhelming – when you look at the code there’s a ton of stuff going on and it’s all tightly intermixed with the specific Entity Framework based implementation.

Nevertheless I encourage you to at least take a brief glance at it, maybe stepping through to understand the overall flow of how logins are processed.

Extracting just the necessary Pieces

In order to use the OWIN Idenity pieces in your own application that doesn’t use the EF based UserManager, you’ll have to do a few things. Again, my goal as part of this article is to:

  • Support Cookie based User Logins (username/password)
  • Support External Logins (Google, Github, Twitter)
  • Support ability to create Accounts using both local or external Accounts
  • Support logging in with either local or external accounts

In order to accomplish this with an existing application I have to:

  • Turn off IIS Authentication for the Application
  • Add the appropriate NuGet Packages
  • Implement the Account Registration for Local and External Accounts
  • Implement Logging in for Local and External Accounts
  • Allow for signing in using the OWIN based Login mechanism

Let’s take a look and see what each of these steps look like.

Turn off IIS Authentication for the Application

ASP.NET Identity works using the OWIN platform which is a custom subsystem that doesn’t rely on standard IIS security. Because OWIN can be self hosted there’s no dependency on IIS in this system. On IIS OWIN plugs into the IIS pipeline using a few dynamically inject modules but it essentially completely takes over the Authentication/Authorization process for your application. So, in order to use ASP.NET Identity the first thing you have to do is actually turn off standard IIS authentication in web.config for your application.

<system.web>   
    <authentication mode="None" />   
<system.web>

I did not do this initially and it took me a while to figure why my application kept throwing up browser authentication dialogs instead of navigating to my login page or sending me off to an external provider login. Make sure standard authentication is off!

Adding NuGet Packages

If you have an existing project that doesn’t have any identity features installed you’ll need to get the right assemblies into your project. If you look at a newly created MVC project and the litany of assemblies in there it’s not obvious what’s actually required to get just the basic features.

Luckily, thanks to the power of NuGet, to get just the core identity features you can add just the following  packages to get all the references you needed for the core identity features plus a few of the external providers needed.

  • Microsoft.Owin.Host.SystemWeb
  • Microsoft.Owin.Security.Cookies

The first two packages are enough to light up the OWIN identity framework. If you’re not running on IIS use Microsoft.Owin.Host.SelfHost instead of the SystemWeb host.

You’ll also need to install the the specific external provider packages:

  • Microsoft.Owin.Security.Google
  • Microsoft.Owin.Security.Twitter
  • Owin.Security.Providers

The provider packages add support for specific external providers you can log in with. The Owin.Security.Providers package is a third party library that includes a ton of additional providers you can integrate with and that’s what I used to support GitHub logins, since this is a developer focused site.

Startup Class: Provider Configuration

The next step is to configure OWIN pipeline to actually handle the various login solutions. To do so you have to create a Startup class that configures the various authentication mechanisms. In my case I support Local user authentication (Cookie Auth) as well as external providers for Google, Twitter and GitHub.

Here’s the config code:

namespace CodePasteMvc
{   
    public partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            ConfigureAuth(app);
        }

        // For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
        public void ConfigureAuth(IAppBuilder app)
        {
            // Enable the application to use a cookie to store information for the signed in user
            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                LoginPath = new PathString("/Account/LogOn")
            });


            app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
            
            // App.Secrets is application specific and holds values in CodePasteKeys.json
            // Values are NOT included in repro – auto-created on first load
            if (!string.IsNullOrEmpty(App.Secrets.GoogleClientId))
            {
                app.UseGoogleAuthentication(                
                    clientId: App.Secrets.GoogleClientId,
                    clientSecret: App.Secrets.GoogleClientSecret);
            }

            if (!string.IsNullOrEmpty(App.Secrets.TwitterConsumerKey))
            {
                app.UseTwitterAuthentication(
                    consumerKey: App.Secrets.TwitterConsumerKey,
                    consumerSecret: App.Secrets.TwitterConsumerSecret);
            }

            if (!string.IsNullOrEmpty(App.Secrets.GitHubClientId))
            {
                app.UseGitHubAuthentication(
                    clientId: App.Secrets.GitHubClientId,
                    clientSecret: App.Secrets.GitHubClientSecret);
            }

            AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;
        }
    }
}

This is an implementation of an OWIN startup class. When the OWIN runtime fires up, it looks via Reflection for a class named Startup class with a Configuration(IAppBuilder app) method and when it finds it executes that method.

ConfigureAuth() then configures Cookie Authentication for local logins and the external providers for Google, Twitter and Github.  I use a custom ApplicationConfiguration class to hold my secret values in a JSON file using my ApplicationConfiguration class (mainly to keep them out of the the GitHub repo), but you can either hardcode these values or read them from a configuration setting.

Each provider requires what amounts to an application ID and a secret key that you have to configure. In order to use external providers you have to create an application for each at the providers Developer Web site  and then pick up the keys these applications generate. Here are links to each of those (assumes you’re logged in to each site):

Note that you can pick and choose your providers. You can use only Cookie authentication, or only externals or both in combination as I’m doing on this site.

Customizing the AccountController

All of the code I’ll be discussing in relation to these logins is located in an MVC controller – specifically the AccountController. There are really 3 main set of features that need to implemented – local logins, external logins and the actual sign in/out operations. Here’s roughly what this looks like in AccountController class:

AccountControllerOverview

There are also a few more traditional Controller actions that deal with some of the supporting feature of my implementation – password recovery and account activation specifically. This is my entire implementation, so as you can see it’s considerably simpler – and less featured – than the full (overkill?) Identity implementation you get in a default full features MVC site which is perfect for this post.

Signing in and Out

I’m going to start with signing in and out first, because it’s a core feature that will be used by all the the others operations. A user is signed in whenever either a local or external login succeeds, and this process essentially creates the authentication Cookie that identifies the user and allows the Identity framework to figure out whether the user is already logged in and setup the User Principal object for each request.

If you’ve used FormsAuthentication in ASP.NET you know that there’s a global object that handles management of the user tracking cookie that associates a user with an account. OWIN has its own version of an authentication manager in the IAuthenticationManager interface which is attached to the HttpContext object. To get a reference to it you can use:

HttpContext.GetOwinContext().Authentication;

This object handles creation and deleting of the secure cookie that is used to track the user through the site. The identity cookie is used to track all logged in users, regardless of whether they logged in locally with a username and password or using an external provider like Google. Once a user is authenticated, the SignIn method is called to create the cookie. On subsequent requests, OWIN based Identity subsystem then picks up the Cookie and authorizes the user the appropriate IPrinciple (a ClaimsPrinciple with a ClaimsIdentity) based User whenever the user accesses your site.

Identity sign-ins work with ClaimsIdentity objects, which contains user information stored in claims. The claims provide the user ID and names and any other information you want to store with the authenticated user as cached state.

To simplify sign-in I use a couple of helper functions that look like this:

public void IdentitySignin(AppUserState appUserState, string providerKey = null, bool isPersistent = false)
{
    var claims = new List<Claim>();

    // create required claims
    claims.Add(new Claim(ClaimTypes.NameIdentifier, appUserState.UserId));
    claims.Add(new Claim(ClaimTypes.Name, appUserState.Name));

    // custom – my serialized AppUserState object
    claims.Add(new Claim("userState", appUserState.ToString()));

    var identity = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie);

    AuthenticationManager.SignIn(new AuthenticationProperties()
    {
        AllowRefresh = true,
        IsPersistent = isPersistent,
        ExpiresUtc = DateTime.UtcNow.AddDays(7)
    }, identity);
}

public void IdentitySignout()
{
    AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie,
                                    DefaultAuthenticationTypes.ExternalCookie);
}

private IAuthenticationManager AuthenticationManager
{
    get { return HttpContext.GetOwinContext().Authentication; }
}

SignIn/SignOut

The key methods are SignIn() and SignOut() on the AuthenticationManager, which create or delete the application cookies on the executing request. SignIn() takes an Identity object that includes any claims you have assigned to it. This identity is what you also get back once the user is logged in and you look at Context.User.Identity later to check for authorization.

A Word about AppUserState

Note I’m using an application specific AppUserState class to represent the logged in user’s state which is then added to the Identity’s claims! This is a custom object in my application that basically holds basic User that are the bare minimum needed by the application to display user info like name, admin status, theme etc. This object is persisted and cached inside of the ClaimsIdentity claims and therefore in the cookie, so that the data is available without having to look up a user in the database for each request.

Above I show how the AppUserState is persisted in the identity cookie. For retrieval and storage in a property on the controller,  I have a base controller that has an internal AppUserState property that is loaded up from a valid ClaimsPrincipal when a request comes in in BaseController.Initialize():

protected override void Initialize(RequestContext requestContext)
{
    base.Initialize(requestContext);

    // Grab the user's login information from Identity
    AppUserState appUserState = new AppUserState();
    if (User is ClaimsPrincipal)
    {
        var user = User as ClaimsPrincipal;
        var claims = user.Claims.ToList();

        var userStateString = GetClaim(claims, "userState");
        //var name = GetClaim(claims, ClaimTypes.Name);
        //var id = GetClaim(claims, ClaimTypes.NameIdentifier);

        if (!string.IsNullOrEmpty(userStateString))
            appUserState.FromString(userStateString);
    }
    AppUserState = appUserState;
            
    ViewData["UserState"] = AppUserState;
    ViewData["ErrorDisplay"] = ErrorDisplay;
}

The net effect is that anywhere within Controller and most Views this AppUserState object is available for user info display or visual display options.

This approach made great sense when I was using FormsAuthentication because you could effectively only store a single string value which was the serialized AppUserState value. But now ClaimsIdentity can contain multiple values as claims explicitly as a dictionary so it may be much cleaner to simply store any values as claims on the ClaimsIdentity. In the future the AppUserState code could probably be abstracted away, using a custom ClaimsIdentity instead that knows how to persist and retrieve its state from the attached claims instead. I’ll leave that exercise for another day though because AppUserState is used widely in this application.

Either way I want to make it clear that AppUserState is a custom implementation and an application specific implementation detail for my application.

Local Logins using Cookies

There are two steps for each login type: Creating the user and account initially, and then actually logging in the user. So lets start with the registration process for local logins.

Here’s what the User Registration form looks like:

RegistrationForm

The top of the form holds the local login, while the bottom has the external provider logins. From this form you can choose to sign up with an external login, which fills the local user registration form with any data that might be received from the external provider. Google provides the email address, GitHub both email and name, and Twitter provides only the name for example. In either case a new user is created in the application.

The code that responds to the Register button essentially creates a new account (if there are no validation errors), and signs the user in.

Here’s what the local user login code looks like (keep in mind this application specific):

AcceptVerbs(HttpVerbs.Post)]
[ValidateAntiForgeryToken]
public ActionResult Register(FormCollection formVars)
{
    string id = formVars["Id"];
    string confirmPassword = formVars["confirmPassword"];

    bool isNew = false;
    User user = null;
    if (string.IsNullOrEmpty(id) || busUser.Load(id) == null)
    {
        user = busUser.NewEntity();
        user.InActive = true;
        isNew = true;
    }
    else
        user = busUser.Entity;
            
    UpdateModel<User>(busUser.Entity,
        new string[] { "Name", "Email", "Password", "Theme" });
            
    if (ModelState.Count > 0)
        ErrorDisplay.AddMessages(ModelState);


    if (ErrorDisplay.DisplayErrors.Count > 0)
        return View("Register", ViewModel);

    if (!busUser.Validate())
    {
        ErrorDisplay.Message = "Please correct the following:";
        ErrorDisplay.AddMessages(busUser.ValidationErrors);
        return View("Register", ViewModel);
    }

    if (!busUser.Save())
    {
        ErrorDisplay.ShowError("Unable to save User: " + busUser.ErrorMessage);
        return View("Register", ViewModel);
    }

    AppUserState appUserState = new AppUserState();
    appUserState.FromUser(user);
    IdentitySignin(appUserState, appUserState.UserId);            

    if (isNew)
    {
        SetAccountForEmailValidation();
        ErrorDisplay.HtmlEncodeMessage = false;
        ErrorDisplay.ShowMessage(@"Thank you for creating an account...");
        return View("Register", ViewModel);
    }


    return RedirectToAction("New","Snippet");
}

This code really isn’t very different from how we used to do it previously. You check to see if the saved is a user we already have in the system and if so update her, or else create a new one and update the new user instead.

The key item here that is different is simply that I set the AppUserState as described before and then create call IdentitySignIn() to authenticate that user. On subsequent hits I then get a Context.User with the ClaimsIdentity for that user.

Logging in a Local Account

Once an account is registered you can actually log in. Here’s what the login form looks like:

LogInform

The code to handle the login looks like this:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult LogOn(string email, string password, bool rememberMe, string returnUrl, bool emailPassword)
{
    if (emailPassword)
        return EmailPassword(email);

    var user = busUser.ValidateUserAndLoad(email, password);
    if (user == null)
    {
        ErrorDisplay.ShowError(busUser.ErrorMessage);
        return View(ViewModel);
    }

    AppUserState appUserState = new AppUserState()
    {
        Email = user.Email,
        Name = user.Name,
        UserId = user.Id,
        Theme = user.Theme,
        IsAdmin = user.IsAdmin
    };
    IdentitySignin(appUserState, user.OpenId, rememberMe);

    if (!string.IsNullOrEmpty(returnUrl))
        return Redirect(returnUrl);

    return RedirectToAction("New", "Snippet", null);
}

Again as you may guess, the code here simply looks up the username and password and if valid, updates the AppUserState object and then calls IdentitySignin() to log the user in.

Both of these workflows are not different at all from what I used to do with FormsAuthentication. The only real difference is that I’m calling  IdentitySignin() instead of FormsAuthentication.Authenticate().

Now on to the fun stuff that you couldn’t easily do before – external logins.

Linking to an External Login

As you can see in the screen shots above both the registration and login forms support using external providers to handle authentication of an account. For the registration form the external login can either be performed at the beginning of registration to pre-fill the registration information from the external provider, or by attaching the external login to an existing local account.

For external logins, when you click any of the provider buttons you are redirected to the provider site (Google, Twitter, GitHub), which checks whether your account is logged in. If it isn’t you’re shuttled to the providers log in page on their server where you can log in and/or specify what kind of rights you want to give to the application that is requesting the login (ie. my site). After you click accept, the server fires a callback request on your server and provides the Claims that the provider makes available. Typically this is a provider key (an identifier for your user’s login) as well as either name or email or both.

External logins are handled via an OAuth2 flow that is managed internally by the OWIN authentication pipeline in ASP.NET. When the requests are fired off they include a callback url which fires first into the OWIN handler. The callback URL is /signin-google or /signin-twitter or /signin-github.

Both creation of an initial account link between a local account and the external account as well as logging has a two endpoint request flow: One to actually start the remote authentication process via a Challenge operation (which is a Redirect really), and one to receive the callback when the authentication is complete.

Here’s the first method that initiates the Linking of an External Account by Challenging and Redirecting to the provider:

[AllowAnonymous]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ExternalLinkLogin(string provider)
{
    // Request a redirect to the external login provider to link a login for the current user
    return new ChallengeResult(provider, Url.Action("ExternalLinkLoginCallback"), AppUserState.UserId);
}

ChallengeResult is a helper class that is part of the stock ASP.NET MVC default controller implementation from which I simply copied it:

private const string XsrfKey = "CodePaste_$31!.2*#";

public class ChallengeResult : HttpUnauthorizedResult
{
    public ChallengeResult(string provider, string redirectUri)
        : this(provider, redirectUri, null)
    { }

    public ChallengeResult(string provider, string redirectUri, string userId)
    {
        LoginProvider = provider;
        RedirectUri = redirectUri;
        UserId = userId;
    }

    public string LoginProvider { get; set; }
    public string RedirectUri { get; set; }
    public string UserId { get; set; }

    public override void ExecuteResult(ControllerContext context)
    {
        var properties = new AuthenticationProperties { RedirectUri = RedirectUri };
        if (UserId != null)
            properties.Dictionary[XsrfKey] = UserId;
                
        var owin = context.HttpContext.GetOwinContext();
        owin.Authentication.Challenge(properties, LoginProvider);
    }
}

The key of this class is the OWIN Authentication.Challenge() method which issues a 302 Redirect to the provider to handle the login with a URL that includes the Redirect URL and some state information. The state in this case is an a user identifier (our user id in this case) that lets us check and make sure the result is the one we’re interested in when it comes back.

When the provider has validated (or failed to validate) the user, it makes  callback to your server using a specific URL. The URL that is called back to is ~/signin-google or ~/signin-github or ~/signin-twitter for example. The OWIN pipeline internally handles this callback for you and after it has validated redirects to your actual endpoint so you can handle the authenticated request.

To illustrate check out this Fiddler trace of a Registration Link request, when already logged into my Google account:

FiddlerRegistration

Notice all the 302 requests. The first request is initiated by your code using the ChallengeResult which redirects to Google. Google then redirects back to the OWIN internal endpoint to handle the provider OAuth parsing and finally the OWIN pipeline calls into your code with ExternalLinkLoginCallback().

Here’s the Callback method that is called when the Link process is complete:

[AllowAnonymous]
[HttpGet]
public async Task<ActionResult> ExternalLinkLoginCallback()
{
    // Handle external Login Callback
    var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync(XsrfKey, AppUserState.UserId);
    if (loginInfo == null)
    {
        IdentitySignout(); // to be safe we log out
        return RedirectToAction("Register", new {message = "Unable to authenticate with external login."});
    }

    // Authenticated!
    string providerKey = loginInfo.Login.ProviderKey;
    string providerName = loginInfo.Login.LoginProvider;

    // Now load, create or update our custom user

    // normalize email and username if available
    if (string.IsNullOrEmpty(AppUserState.Email))
        AppUserState.Email = loginInfo.Email;
    if (string.IsNullOrEmpty(AppUserState.Name))
        AppUserState.Name = loginInfo.DefaultUserName;

    var userBus = new busUser();
    User user = null;

    if (!string.IsNullOrEmpty(AppUserState.UserId))
        user = userBus.Load(AppUserState.UserId);

    if (user == null && !string.IsNullOrEmpty(providerKey))
        user = userBus.LoadUserByProviderKey(providerKey);

    if (user == null && !string.IsNullOrEmpty(loginInfo.Email))
        user = userBus.LoadUserByEmail(loginInfo.Email);

    if (user == null)
    {
        user = userBus.NewEntity();
        userBus.SetUserForEmailValidation(user);
    }

    if (string.IsNullOrEmpty(user.Email))
        user.Email = AppUserState.Email;

    if (string.IsNullOrEmpty(user.Name))
        user.Name = AppUserState.Name ?? "Unknown (" + providerName + ")";


    if (loginInfo.Login != null)
    {
        user.OpenIdClaim = loginInfo.Login.ProviderKey;
        user.OpenId = loginInfo.Login.LoginProvider;
    }
    else
    {
        user.OpenId = null;
        user.OpenIdClaim = null;
    }

    // finally save user inf
    bool result = userBus.Save(user);

    // update the actual identity cookie
    AppUserState.FromUser(user);
    IdentitySignin(AppUserState, loginInfo.Login.ProviderKey);

    return RedirectToAction("Register");
}

There’s a fair bit of code in this snippet, but… most of the code is application specific. The most important piece of the code is the first line that is responsible for returning the LoginInfo object that contains the providerKey and any other claims that the provider makes available. Typically those are a name and/or email. The rest of the code then checks to see if the user already exists and updates it or if not creates a new one and saves it. If all goes where IdenitySignin() is called to effectively log the user in by creating the cookie and authorized ClaimsIdentity on subsequent requests.

When done the user is logged in, but I want to re-display the Registration form to show the external account registration:

LinkedAccountDisplay

Logging in with an External Login

Finally we still have to hook up the logic to log in with an external provider. As with the Linking of the provider, this is a two step process – fire off the initial authentication request. As with the link operation a challenge request handles this:

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult ExternalLogin(string provider)
{
    string returnUrl = Url.Action("New","Snippet",null);            
    return new ChallengeResult(provider, Url.Action("ExternalLoginCallback",
                               "Account", new { ReturnUrl = returnUrl }));
}

Again the same 302 requests are issued to eventually bring back the result into the OWIN pipeline which in turn redirects to the ExternalLoginCallback() method:

[AllowAnonymous]
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
    var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
    if (loginInfo == null)
        return RedirectToAction("LogOn");

    // AUTHENTICATED!
    var providerKey = loginInfo.Login.ProviderKey;


    // Aplication specific code goes here.
    var userBus = new busUser();
    var user = userBus.ValidateUserWithExternalLogin(providerKey);
    if (user == null)
    {
        return RedirectToAction("LogOn", new
        {
            message = "Unable to log in with " + loginInfo.Login.LoginProvider +
                        ". " + userBus.ErrorMessage
        });
    }

    // store on AppUser
    AppUserState appUserState = new AppUserState();
    appUserState.FromUser(user);
    IdentitySignin(appUserState, providerKey, isPersistent: true);

    return Redirect(returnUrl);
}

The process is similar  to the link operation in the previous example except the login code simply checks to see if the user is valid and if so logs him in using the now familiar IdentySignin() method. 

The user is logged in at this point so simply redirect where we want to go.

Finally if you want to unlink the account, all I have to do is remove the link in the domain model. In my case I remove the field values for OpenId and OpenIdClaim, so that any subsequent login using an external account will fail as we won’t match the provider key provided by the external provider.

Here’s the code for the unlink operation:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ExternalUnlinkLogin()
{
    var userId = AppUserState.UserId;
    var user = busUser.Load(userId);
    if (user == null)
    {
        ErrorDisplay.ShowError("Couldn't find associated User: " + busUser.ErrorMessage);
        return RedirectToAction("Register", new { id = userId });
    }
    user.OpenId = string.Empty;
    user.OpenIdClaim = string.Empty;

    if (busUser.Save())
        return RedirectToAction("Register", new { id = userId });

    return RedirectToAction("Register", new { message = "Unable to unlink OpenId. " + busUser.ErrorMessage });
}

After this code executes, the external account link is gone. I show the register page again and this time the external link account is no longer shown replaced again by the three external providers one of which can be hooked up.

And that’s all the base operations!

A quick Review

You may think that that’s a lot of code you have to write for something that should be pretty simple. But keep in mind that this code includes some application specific logic that I provided here in order to provide a somewhat realistic context. While the actual underlying Identity code is pretty small (and I’ve highlighted the core requirements in the snippets in bold), the code shown here is probably the bare minimum you need for a basic self managed user management implementation you probably want to run in a real application.

If you want to see the complete controller code you can check it out on Github.

In summary you basically deal with:

IdentitySignIn, IdentitySignOut for standard LogIn/LogOut functions

Implementing ExternalLinkLogin() and ExternalLinkLoginCallback()

Implementing ExternalLogin() and ExternalLoginCallback()

The various ExternalXXXX methods follow a simple boilerplate of using ChallengeResult for the initial request and calling GetExternalLoginInfoAsync() to pick up the result data. It’s really fairly straight forward as long as you know the pieces you actually need to implement.

Minimal Code Summary

Because the code above is quite lengthy, here’s a summary of just the relevant parts you have to implement with just the basic pieces:

Startup Configuration Class

public partial class Startup
{
    public void Configuration(IAppBuilder app)
    {
        ConfigureAuth(app);
    }

    // For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
    public void ConfigureAuth(IAppBuilder app)
    {
        app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

        // Enable the application to use a cookie to store information for the signed in user
        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/Account/LogOn")
        });
            
        // these values are stored in CodePasteKeys.json
        // and are NOT included in repro - autocreated on first load
        if (!string.IsNullOrEmpty(App.Secrets.GoogleClientId))
        {
            app.UseGoogleAuthentication(                
                clientId: App.Secrets.GoogleClientId,
                clientSecret: App.Secrets.GoogleClientSecret);
        }

        if (!string.IsNullOrEmpty(App.Secrets.TwitterConsumerKey))
        {
            app.UseTwitterAuthentication(
                consumerKey: App.Secrets.TwitterConsumerKey,
                consumerSecret: App.Secrets.TwitterConsumerSecret);
        }

        if (!string.IsNullOrEmpty(App.Secrets.GitHubClientId))
        {
            app.UseGitHubAuthentication(
                clientId: App.Secrets.GitHubClientId,
                clientSecret: App.Secrets.GitHubClientSecret);
        }

        AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;
    }
}

IdentitySignIn/SignOut

public void IdentitySignin(string userId, string name, string providerKey = null, bool isPersistent = false)
{
    var claims = new List<Claim>();

    // create *required* claims
    claims.Add(new Claim(ClaimTypes.NameIdentifier, userId));
    claims.Add(new Claim(ClaimTypes.Name, name));

    var identity = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie);

    // add to user here!
    AuthenticationManager.SignIn(new AuthenticationProperties()
    {
        AllowRefresh = true,
        IsPersistent = isPersistent,
        ExpiresUtc = DateTime.UtcNow.AddDays(7)
    }, identity);
}

public void IdentitySignout()
{
    AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie,
                                  DefaultAuthenticationTypes.ExternalCookie);
}

private IAuthenticationManager AuthenticationManager
{
    get
    {
        return HttpContext.GetOwinContext().Authentication;
    }
}
[AllowAnonymous]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ExternalLinkLogin(string provider) //Google,Twitter etc.
{
    return new ChallengeResult(provider, Url.Action("ExternalLinkLoginCallback"), userId);
}

[AllowAnonymous]
[HttpGet]        
public async Task<ActionResult> ExternalLinkLoginCallback()
{
    // Handle external Login Callback
    var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync(XsrfKey,userId);
    if (loginInfo == null)
    {
        IdentitySignout(); // to be safe we log out
        return RedirectToAction("Register", new {message = "Unable to authenticate with external login."});
    }

    // Authenticated!
    string providerKey = loginInfo.Login.ProviderKey;
    string providerName = loginInfo.Login.LoginProvider;


    // Your code here…


    
    // when all good make sure to sign in user
    IdentitySignin(userId, name, providerKey, isPersistent: true);


    return RedirectToAction("Register");
}     

This code and the External Login also require the ChallengeResult helper class:

// Used for XSRF protection when adding external logins
private const string XsrfKey = "CodePaste_$31!.2*#";

public class ChallengeResult : HttpUnauthorizedResult
{
    public ChallengeResult(string provider, string redirectUri)
        : this(provider, redirectUri, null)
    {  }

    public ChallengeResult(string provider, string redirectUri, string userId)
    {
        LoginProvider = provider;
        RedirectUri = redirectUri;
        UserId = userId;
    }

    public string LoginProvider { get; set; }
    public string RedirectUri { get; set; }
    public string UserId { get; set; }

    public override void ExecuteResult(ControllerContext context)
    {
        var properties = new AuthenticationProperties { RedirectUri = RedirectUri };
        if (UserId != null)
            properties.Dictionary[XsrfKey] = UserId;
                
        var owin = context.HttpContext.GetOwinContext();
        owin.Authentication.Challenge(properties, LoginProvider);
    }
}

External Login

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult ExternalLogin(string provider)
{
    string returnUrl = Url.Action("New","Snippet",null);            
    return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", 
                               "Account", new { ReturnUrl = returnUrl }));
}
        
[AllowAnonymous]
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{                        
    if ( string.IsNullOrEmpty(returnUrl) )
        returnUrl = "~/";
            
    var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
    if (loginInfo == null)
        return RedirectToAction("LogOn");

    // AUTHENTICATED!
    var providerKey = loginInfo.Login.ProviderKey;

    // Your code goes here.

    // when all good make sure to sign in user
    IdentitySignin(userId, name, providerKey, isPersistent: true);

    return Redirect(returnUrl);
}

Local Account Registration

[AcceptVerbs(HttpVerbs.Post)]
[ValidateAntiForgeryToken]
public ActionResult Register(FormCollection formVars)
{

    // Capture User Data and Create/Update account
     
    // when all good make sure to sign in user
    IdentitySignin(userId, name, appUserState.UserId);            

    return RedirectToAction("New","Snippet",null);
}cs

Local Account Login

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult LogOn(string email, string password, bool rememberMe, string returnUrl, bool emailPassword)
{
    // validate your user 
    
    // if all OK sign in
    IdentitySignin(userId, name, user.OpenId, rememberMe);

    return RedirectToAction("New", "Snippet", null);
}

public ActionResult LogOff()
{
    IdentitySignout();
    return RedirectToAction("LogOn");
}

Summary

For new projects it’s probably a good idea to think long and hard whether you want to roll your own, or just stick with the stock Identity implementation. Personally I’m not a fan of the EF based implementation that comes out of the box. While it can be customized you still have to massively tweak the UI to make things fit with your application and probably add and remove fields from the Identity models. Worst of all though is the default EF dependency that doesn’t easily integrate into even another EF model. Personally, I prefer to have user management integrated as part of my own domain models of the application rather than linking users to customers for example.

The other advantage of using your own is that you’re not stuck to Microsoft’s whims. We’ve gone through too many authentication frameworks with Microsoft seemingly changing the model with every major release cycle. Granted the new Identity is probably the closest thing that they ever had that is actually usable out of the box, but I’m still wary to rely on anything that Microsoft sticks in the box in this regard for fear of getting it yanked out from under me in the next version. Using my own I don’t have to worry about at least the user management features that can travel with my applications. With my own implementation I might have a little more setup to do, but at least I have a standard way that I can easily carry forward through applications of any version of ASP.NET.

In the end I have to say that although it took me quite a while to get my head around EXACTLY what I needed to implement, was not very difficult. The hard part was just finding the right information on what you need to implement by digging into the generate code and ripping out the relevant pieces. Once you know what’s needed the implementation of the actual code pieces is relatively straight forward.

I hope that this post provides a good summary of what’s required and especially with the Minimal Code Summary it’ll be easier to create the skeleton code required to plug your own domain driven user management into the core identity framework.

Resources

Posted in ASP.NET  MVC  OWIN  

The Voices of Reason


 

Jonathas Morais
April 29, 2015

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

What about a SAML integration?
I'm keen to integrate .NET apps with an Identity Server, which in turn contains several Services and Identity Providers but there's not much material out there explaining how to do it in .NET.

Nice post by the way!

Thanks.

Jeff P.
April 29, 2015

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

I went through this exercise with my open source forum app, and mostly it works pretty well. However, something changed dramatically in v3.x of the OWIN stack, and it breaks. I haven't been able to figure out why and it's completely frustrating. I assume it's something configuration related, but I can't nail it down.

http://stackoverflow.com/questions/28704700/updating-owin-from-2-1-to-3-0-1-breaks-external-auth

I notice that you're calling GetExternalLoginInfoAsync from the AuthenticationManager. I'm using GetAuthenticationResult. Any idea what the difference is?

Rick Strahl
April 29, 2015

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

@Jeff - I lifted the code from the original templates, so I'm just doing what the default implementation would do which I assume is safe. When I run into problems like this sadly this is my go-to solution: Check what the default templates do and then adjust my code to match.

I didn't look closely at the lower level AuthenticationManager methods to see what's needed given that the code does what it's supposed to using ExternalLinkLoginInfoAsync(). Have you tied using that instead? Also notice the use of app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie) instead of your app.SetDefaultSignInAsAuthenticationType().

Bummer to hear that this broke for you though - that really shouldn't be happening with components that are as integral as this.

Jeff P.
April 29, 2015

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

Yeah, it's weird that a major version update would break stuff. But your article gives me some ideas to experiment with, so I'll see what I can do. I'll post back here if I discover anything interesting.

Matt
April 30, 2015

# Great Article

Great Article Rick! I look forward to putting some of this is use in my up coming project.

Jeff P.
May 02, 2015

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

I'm starting to see that the things I'm not using, that you are referencing, are in the Microsoft.AspNet.Identity.Owin package, which to this point I haven't added a reference to. For example the app builder extension method for UseExternalSignInCookie(). That's pretty frustrating that you need more dependencies and that the existing code is broken just from a version upgrade. Will let you know if this gets me there...

Firegarden
August 24, 2015

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

Your tutorial was the best Rick. I tried to get this to work quickly with every other tutorial out there but could not get past the need to expose

IAuthenticationManager AuthenticationManager
{
get { return HttpContext.GetOwinContext().Authentication; }
}

Also if you don't have using System.Web which is common in an MVC project you will have an even harder time. Word to your mother.

floyd
September 02, 2015

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

This is the best article about AspNet.Identity I've read ever!

Murat
September 04, 2015

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

Dear MVC5 Authentication Guru,

It's very hard to find the good and usefull articles like this. Every forum or question related to MVC5 Authentication are about the default OWIN stuffs. This is the best solution i ever found for the custom Authentication Mechanizm.

I played with the code and works %100 for me for now. You should definitely write more articles about this setup. May be you can add Authorization, Roles, Multiple Roles in a single user, how to show different content in views to different users or roles, or somethink like that.

YOU ARE A HERO!

Cheers.

Yam
September 11, 2015

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

Thank you for taking the initiative to write this article which has helped me quite a lot!

Regards!

Neil Moss
September 12, 2015

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

Hi Rick - struggling with the samples here.


I removed all the Microsoft.AspNet.Identity packages - did I go too far? If I just register the nuGet packages you suggest, the compiler doesn't like the Startup partial at all.

app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

The UseExternalSignInCookie property doesn't exist under IAppBuilder

            // Enable the application to use a cookie to store information for the signed in user
            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                LoginPath = new PathString("/Account/LogOn")
            });

In class Microsoft.Owin.Security.Cookies.CookieAuthenticationOptions, AuthenticateType is a string and not an enum.

And so on..!

Can you clarify the nuGet packages and versions you're using and the namespaces you're 'using' please?

Thanks

Rick Strahl
September 12, 2015

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

You're missing the base OWin assemblies. If you had an existing project and you pulled out packages except the ones mentioned you'll lose the dependencies that get pulled in from the packages. The list I provide only works if you add the packages to a project and then pull in the related dependencies.

The best thing to figure out the assemblies if you did do this is to create a new project add the nuget references and then copy the package list.

Neil Moss
September 13, 2015

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

Thank you Rick. I had indeed added the related dependencies.

Having double checked and found nothing missing according to the instructions in the post, I went to your CodePaste.NET GitHub to see what was actually in play there. (Should have done that before bothering you, but... well, next time).

The packages I was missing were these below:
 <package id="Microsoft.AspNet.Identity.Core" version="2.2.1" targetFramework="net451" />
 <package id="Microsoft.AspNet.Identity.Owin" version="2.2.1" targetFramework="net451" />

From the title and general direction of the post, I'd got the idea that we didn't need the Microsoft.AspNet.Identity packages because we're just using the minimal OWIN classes.

Still, up and moving along now, so much obliged to you Sir.

Regards.

Steve
October 07, 2015

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

I'm curious if it would be possible to do something similar to what you have, but integrate it with Azure Active Directory for Application Roles and to allow users to manage their password resets, etc?

Constantinos Haskas
October 07, 2015

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

Anyone knows if the provider key changes or is it unique?

John A Davis
October 25, 2015

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

I, too, thought that your article implies that the Identity and Authorization blah blah wasn't needed. I took out anything like that thinking I would roll my own which I have done before, but without a Twitter login (oh heavens).
But your code is just as crazy as what the mad scientists at Microsoft are coming up with. They for sure are driving people away from using MS programming languages.

 if (!string.IsNullOrEmpty(App.Secrets.GoogleClientId))
            {
                app.UseGoogleAuthentication(                
                    clientId: App.Secrets.GoogleClientId,
                    clientSecret: App.Secrets.GoogleClientSecret);
            }

Nowhere in my project can I get the "App.Secrets.GoogleClientId" from being underlined in red. "The name App does not exist in the current context."

I woke up today thinking I could finally move on to actually working on my CRUD stuff in my application.

Rick Strahl
October 26, 2015

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

@John - maybe if you actually had read the article and not just cut and pasted, you would have seen the bit about the values in App.Secrets being an application specific implementation where I store the configuration information of the client id strings. You can use strings. I sure as hell am not going to hardcode those values and encourage it either.

Rafe Kemmis
November 15, 2015

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

FYI - I had to install package Microsoft.AspNet.Identity.Core in order to resolve this type: DefaultAuthenticationTypes.

Jeny
January 21, 2016

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

Thank you, I'd been looking for something like this all through and finally landed here and is surely a life saver!!

Uday
February 04, 2016

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

Hi Rick this article is very nice.

If want to know in deep about OWIN and how to use it with .NET which is the best tutorial or a reference book.

Can some one please suggest.

veysel
February 26, 2016

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

This is actually the most useful article to understand the asp.net identity mechanism i have seen to date. Thank you very much for writing it !
I would like another article about custom role based authentication with new asp.net identity model.

Joe Pool
April 06, 2016

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

Hi Rick,

I was directed here from the Asp.net forum.

I have an existing ASP.Net web application. It is *not* MVC!

Will (should) the steps here enable my ASP.Net web application to permit my members to log in using OWin or would I run into other problems?

If this is possible, after a member logs in using the OWin process, what would I need to do to match up that login identity to a member in my application's SQL database where I store all of my members by their email address?

Rick Strahl
April 08, 2016

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

@Joe - OWin is separate from any particular framework, so what's described in this post can be implemented with Web Forms or raw Handlers/Modules. But the operation is optimized for MVC and routed URLs which would make handling non-MVC/Web API applications a more difficult target for this. You'd have to implement custom route handlers most likely to make it work.

The post describes a scenario that doesn't use the default EF identity implementation - it only uses the oWin components but none of the authorization or user management pieces which are deferred for storage in the application.

ecesari
May 21, 2016

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

Hi Rick, thank you for the great article
Do you have idea on how to integrate MVC website with app mobile.
I mean... now I'm able to do external login on the web mvc, I moved some funtions to web api and I0m triyng to connect also using custom (xamarin) APP. what is the correct approach? I want my APP to be logged in to my mvc backend, using google credentials
Do you have any idea?
Thanks so much

Konrad
May 30, 2016

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

I absolutely, positively love this blog! You should get a Nobel Prize in OWIN, for duck's sake (typo intended).

Ragu
June 21, 2016

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

I followed the above steps to implement OWIN authentication for internal login and working fine as expected. But the problem is that the "ClaimsIdentity" is still valid and IsAuthentication=true after Signed out . Also I am able to access the resources using previous token id which I have captured using some tool like Fiddler Burp Suite.Is there any way to invalidate the Claims Identity after logged out from system? I tried all the possibilities but not able to resolve this issue.

dpmragu
June 21, 2016

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

Hi Rick,

Thanks for this article and helped me a lot to implement OWIN authentication in MVC5 . Can you please guide me how I can invalidate the claimsIndentity after signout from the application?

Afshin
September 16, 2016

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

Hi Rick,
I have a MVC4 web site which is currently uses simple membership and sql server for user management. Some of my users are already authenticated through facebook. I want to upgrade to OWIN identity to get the google authentication working. I was wondering if OWIN can consume existing user information in sql server or it has a different schema?

Thanks

Dwayne
September 23, 2016

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

Thanks so much for writing this excellent article. I was scratching my head and Googling for a couple hours before I came to this site, and now everything is so clear. Very well done, and very much appreciated.

Danny Scheelings
September 25, 2016

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

Hi, I am developing a simple authentication webapp to test OWIN, but on my IIS8.5 the cookie authentication does not work after restarting IIS. Do you have any idea what the problem is?
You can also take a look at my StackOverflow post: http://stackoverflow.com/questions/39661593/owin-cookie-authentication-not-working-on-iis-8-5
Thx,
Danny

Sam Murray
November 12, 2016

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

Hi Rick,

Just wanted to say a quick thanks for this blog entry. It covers a lot of the little things that places like StackOverflow and other blogs miss out.

Made my life a lot easier!

Thanks.
Sam.

Angela
November 21, 2016

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

Just adding a comment for other people who are new to 3rd party provider login... In the code it states

    // these values are stored in CodePasteKeys.json
    // and are NOT included in repro - autocreated on first load
    if (!string.IsNullOrEmpty(App.Secrets.GoogleClientId))
    {
        app.UseGoogleAuthentication(                
            clientId: App.Secrets.GoogleClientId,
            clientSecret: App.Secrets.GoogleClientSecret);
    }

The App is not resolved. It is probably a class that he dumps reading of his json file into.

Either way, I was not familiar with 3rd party providers. So I searched the web for clientId and Google authentication and what i found was you have to register with those providers you website applications so you can interact with them. And in the case of google they will give you a clientid and a clientsecret that you will use when contacting them to verify a user.

Just thought a put a note here in case anyone else who is unfamiliar with working with third party logins did not understand. I would say if you are not using third party login you can probably comment out that code. That is what i am doing for now.


Ken
February 26, 2017

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

AWESOME Article you have my sanity !!!! This is one of the best articles on this topic. I have spent the whole weekend working on the following solution: Using local authentication against LDAP and Windows Azure Active Directory. Just a note to everyone I had to put OpenIdConnectAuthentication in passive mode so that it wouldn't try to authenticate to WAAD immediately.

        app.UseOpenIdConnectAuthentication(
            new OpenIdConnectAuthenticationOptions
            {
                AuthenticationMode = AuthenticationMode.Passive,

Soapbox - Frameworks like OWIN are great when it's a common problem but are a nightmare when you need to go off the beaten path!


Mark H
March 05, 2017

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

Thanks for this article. I had a go at implementing Owin before I realised it was easier and faster to write my own library.

OAuth is not a difficult protocol to implement. But when it takes this much minimal code to implement a library, which has deep reliances on other unneccesary libraries, many entry points and outside code required, you know something is wrong. The application startup config code also doesn't lend itself to multi-tennant apps. This middleware is simply an overengineered mess.

I implemented several providers. Each one had its own slight twist/quirks on OAuth, but I only needed to override 3 properties and a method from a base class for each provider in my library, which were more similar than different. I simply pass in the HttpRequest, the provider, and add one new route to the web app, then look for a ProfileInfo class to eventually come back (or an error). A few lines of readable code.

Owin was useful for monitoring requests and response urls, but I recommend rolling your own code. Otherwise its a sledgehammer to crack a nut.


JP
May 10, 2017

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

Hi, thanks a lot for this post, it was quite difficult to find just this and not the whole Identity + EF implementation. I have a question though, regarding cookie timeout and presenting it to the user.

I have been following this kind of approach to it: http://stackoverflow.com/questions/23090706/how-to-know-when-owin-cookie-will-expire

basically I setup my cookie with a SlidingExpiration = true, then, after the user logs in I do a request by ajax to get the current remaining cookie valid time, but by doing that request (which runs periodically by 1 sec or so) then the cookie itself gets renewed and never expires, do you have any suggestions to present that info to the user and not confuse the ajax request with a user's request?

thanks in advance!!


James Farr
June 28, 2017

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

This article is excellent and exactly what I needed!! I had spent hours reading through fragmented information across many different sites with no results to show for it at all.

After reading through this article it took less than 15 minutes to get the login working!

So thank you very much for taking the time to write this, it's greatly appreciated

James


Marcel Slats
August 15, 2017

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

Finally found an article that does not direct me to the new project wizard to start a complete project without telling me what does what.

With this article I understand what's going on and how to handle the results without using the EF.

Excellent article, thanks.


Bob
November 06, 2017

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

Hi Rick, You must hear this all the time, however this article is making my life easy! This is the first article that shows the parts needed to implement from scratch and why. I searched for hours and most search results were way into the weeds already! Thanks again! and keep on writing articles. This is not the first of yours I have read and wont be the last! Regards.


Vinod
March 12, 2018

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

Hello, really nice and on the topic article and as you mentioned in start of your article that "Lots of intro articles that talk about how to use the stuff ‘as is’ without customization. But there’s not a lot of information on using the core Identity pieces without the full bore UserManager and Entity Framework data store, in order to use just the Authentication/Authorization on their own and integrate them with my own business objects/domain model." then it really makes me feel then I am not alone among those stuff.

Finally got what I was looking for but now I have a couple of queries

  1. Expire time is set at two places (in Startup class and another in AuthenticationProperties in Signin Method), what is difference between two and which one takes precedence?
  2. What will happen to another when another thing expires first than other in above question.
  3. How AllowRefresh = true works in SignIn method
  4. You mentioned above that "The identity cookie is used to track all logged in users", can you please explains this bit more. Can we notify other users about user online/offline based on it, if yes then how, also does SlidingExpiration is used for this purpose ?

Many thanks for such nice article and good bye to those massive and full of EF and default template based code explanation on the topic 😃


Stephen Grattan
May 22, 2018

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

Hi Rick, great article but I have an issue and wondered if you're still monitoring this article. I'd likt know why even after signing in:

HttpContext.GetOwinContext().Authentication.SignIn(new AuthenticationProperties() , identity);

any subsequent requests (even redirects) are not authenticated/authorized.

Have I misunderstood something. Any help would be very much appreciated.


David
July 05, 2019

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

This is a truly awesome resource. I was stuck wondering how to integrate the full Identity Framework into my existing application and this fits the bill perfectly.

Must have taken you ages to work out the details - thanks for sharing.


Paul Buck
October 17, 2019

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

I have to tell you Rick, this article was pure gold. I've been writing our site for awhile and I noticed my security was sending some information clear text. Well, I was going to go down the forms auth route, but saw that wasn't the standard. Thanks to you, this tutorial is a clear cut way to just use the authorization we need without all the bloat. ~Thanks much brother!


Tsiro
April 01, 2020

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

Hi, Thanks for the article, very useful indeed. I have a similar asp.net app structure with a global.asax where unity container is configured. Is there a way to use objects injected in Application_Start() in owin startup class?


Narbeh Minassian
January 09, 2022

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

There is a bug in the SignIn() method where it fails to set the Claims Principal and Claims Identity for the current request. You need to manually set it in case the authentication request itself also needs to access authorized actions/controllers. Luckily we can easily set the User principal:

var identity = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie);

AuthenticationManager.SignIn(new AuthenticationProperties()
{
    AllowRefresh = true,
    IsPersistent = isPersistent,
    ExpiresUtc = DateTime.UtcNow.AddDays(7)
}, identity);

/* This ensures the current request is authroized in case your app needs to access authorized actions/controllers */
AuthenticationManager.User = new ClaimsPrincipal(identity);

Rick Strahl
January 10, 2022

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

@Narbeh - I suppose that makes sense and is easy enough to do, although I find it doubtful that you'd need the Principal to check if you're in a the middle of a login operation.

What's your use case where that's necessary?


Narbeh Minassian
January 12, 2022

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

Rick,

I ran into this issue when implementing some automation/simulation that requires a claims principal for a background process. Let's say you want to programmatically authenticate and call a protected controller/action. The automation invokes the SignIn method and then immediately calls RenderAction() to invoke an action that requires authorization. Since SignIn() appears to just create and return an auth cookie, we have no other subsequent request that would send that auth cookie to be handled and a Principal to be created.

Of course you could argue that if it's a single request, you don't need to even call the SignIn() method and just opt to create a new Claims Principal but if we could use the same authentication method as a regular human user then it prevents me from creating a separate process for the automation. By just creating the ClaimsPrincipal during the sign in makes the method more versatile.

It took me a few hours to figure out why my automation was returning a 401 and redirecting to the login page. I can't imagine them leaving this out intentionally but who knows. There may be a case where the auth cookie to be created is intentionally set to be expired and we can't assume the request is authenticated after SignIn() so it could pose a security risk. Great article by the way, I learned a lot about OWIN authentication.


vandana
May 09, 2022

# re: Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application

Thanks Rick, finally an article which cleared the clutter and highlighted the essential pieces.

My setup: My app is a .net 4.6 framework app and is not using system.web.mvc, instead it is a layered app. It is called by a frontend in angular, which calls api routes which in turn land on the .net app controllers which are based on system.web.http and uses token based authentication. Currently, we use AuthenticationFilterAttribute for implementing authentication.

We now need to integrate this app with external login providers like google, fb etc.

My question is as follows:

  1. Do i need to add system.web.mvc to use owin startup class, ChallengeResult (inhereting from HttpUnauthorizedResult which is only in system.web.mvc and and all related code)?

  2. Can I continue to use token based authentication, or will Cookie based authentication need to be used in order to have the external 3rd parties integrated?

  3. Will I need to remove the AuthenticationFilterAttribute and just have the owin startup be r responsible for authentication?

Thanks for your help.


West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024