Menu

 

Thoughts on Software Engineering

ASP.NET MVC Web API Identity (OWIN Security): Auto Login after Register + Custom Login Service Endpoint

Recently I worked on ASP.NET MVC WebAPI-based REST service and I needed to implement public services + non-public services (after login). I needed very simple register / login / logout. I started from the default Web API REST service template from Visual Studio and this ended to tons of auto-generated code for my services. Nice, I have standard .NET MVC project, with controllers for user register / login / logout, for my data, etc.

What is Broken in the Web API Authentication Model (Owin.Security)?

If you create a Web API project in MVC 5 (in Visual Studio 2013 Update 4), you get RESTful services designed for OAuth authentication: local and external login (FB login / Twitter login / Google login, etc.). This is nice, but seems this auto-generated REST service project have something missing:

  • Where is my login service? I have AccountController which holds user register, logout, change password, add external login, external login, and many other REST service methods, but does not have the most important of them: login. The user login method is missing in the AccountController of my project??? Why?
  • Yes, I know, Microsoft introduced a powerful authentication model based on OAuth that integrates local and external logins, and uses it for all MVC applications (Web apps and Web API REST services). It is implemented in the Microsoft.Owin.Security and Microsoft.AspNet.Security namespaces. This is great: out-of-the-box OAuth for all ASP.NET apps. But why my login is out of my AccountController? Could this be a “design flaw” in the Owin.Security infrastructure? Let’s investigate.
  • My Web API RESTful service app has a Help page, auto-generated well-documented API reference, which is a great idea. It explains about user registration (/api/user/Register), about user logout (/api/user/Logout), etc. But again: where is my login?

image

  • Yes, the auto-generated by Visual Studio Web API AccountController is inconsistent: it has register and logout but does not have login.
  • The final big problem is the Logout does not work in Web API 2! You have a “logout” action in the account controller, but it does not sign-out. Your access_token is still valid after logout! I have a solution, but it is not simple (see below). The reason is the there is no way to revoke OAuth token in the Microsoft.Owin.Security implementation.

My User Login Service is in “/Token”, Yahhh

OK, I know where is my login: it is in /Token (why not in /api/Token or /api/Login?). But let’s investigate: how this happens to work?

  • In the file Startup.Auth.cs an OWIN Authorization server is configured to accept login (token) HTTP requests at /Token endpoint and external login requests at /api/Account/ExternalLogin. Very consistent, right?
  • Note also that user registration service requires { email, password, confirmPassword } (RegisterBindingModel) but the user login service (token) requires { username, password, grant_type } (no binding model, this is hidden in Owin.Security). We register users by email + password but login by username + password. This is also very consistent, right? In fact my REST service silently asumes that username == email.
  • But where is my token service implementation? If I can access it, I could make an API method Login(username, password) in my AccountController and fix the inconsistencies in my AccountController. If you analyze the Owin.Secuity source code, you may find the “/Token” service implementation: it is very well hidden inside an HTTP handler in a non-public class “Microsoft.Owin.Security.OAuth.OAuthAuthorizationServerHandler”, which bypasses the Web API infrastructure, handles the “login” functionality and issues the well-known Beareraccess_token”.
  • Yes, I now, Microsoft have a good reason to deeply encapsulate the authorization functionality: they want to provide a OAuth server that does everything related to internal and external login through a REST service and inside traditional MVC applications. But why they don’t reveal the server components for extension. SOLID principles say “Open for Extension, Closed for Modification”. So, how could I extend the OAuth token service? I can’t even call it. It is deeply hidden inside a complex infrastructure. For example, I want some very typical for REST APIs: auto-login after registerring a new account. This is almost imposible with this design.
  • In fact, OAuthAuthorizationServerHandler works through a HTTP handler which directly handle the /Token endpoint and produces JSON output. What if I need to call this externally? What if I want to return XML instead of JSON? What is I need to modify the input request or the HTTP response? We are given very limited extension point in the “ApplicationOAuthProvider.cs” class –> we could implement own user / password validation, and we could add text-only properties in the token service output JSON. No way to add a boolean or integer property! I think this design could be improved for better extensibility.

How to Create a Simple Login Service?

Most RESTful back-ends need a simple login+password based authentication. Am I wrong? Do you really need external login for your REST API? I don’t think this is the typical scenario. The typical scenario for most Web API apps is to provide a simple register / login / logout . You can do several things:

  • Use the existing (and very complicated) Owin.Security + ASP.NET Identity model and stay limited in flexibility and extensibility. Just use the “/Token” for login, “Authorization: Bearer access_token” for authorizing the requests and “/api/Account/Register” + “/api/Account/Logout” for register / logout. This works but how do you implement “auto login after user registration” or a non-string field in the token service JSON response?
  • Forget about the Microsoft ASP.NET Identity model + Owin OAuth server and implement login / logout / authorization by hand (e.g. by custom header field or a cookie + custom ASP.NET Web API filter).
  • Forget about ASP.NET and write your RESTful service in Node.js, Python or other server side language / framework.

Hacking the ASP.NET MVC Web API Login and Register: Own Login + Register REST Services

I have a hack. I am not happy of it, but it works (locally, at my Win2012 production server and in Windows Azure). I just define my own Login and Register HTTP POST services that call internally the OAuth token service from Owin.Security by HTTP POST request (using the HttpClient API).

This is my UserController which defines 3 simple REST services: Login + Register + Logout:

[Authorize]
[RoutePrefix("api/user")]
public class UserController : ApiController
{
    ...

    // POST api/User/Login
    [HttpPost]
    [AllowAnonymous]
    [Route("Login")]
    public async Task<HttpResponseMessage> LoginUser(LoginUserBindingModel model)
    {
        // Invoke the "token" OWIN service to perform the login: /api/token
        // Ugly hack: I use a server-side HTTP POST because I cannot directly invoke the service (it is deeply hidden in the OAuthAuthorizationServerHandler class)
        var request = HttpContext.Current.Request;
        var tokenServiceUrl = request.Url.GetLeftPart(UriPartial.Authority) + request.ApplicationPath + "/api/Token";
        using (var client = new HttpClient())
        {
            var requestParams = new List<KeyValuePair<string, string>>
            {
                new KeyValuePair<string, string>("grant_type", "password"),
                new KeyValuePair<string, string>("username", model.Username),
                new KeyValuePair<string, string>("password", model.Password)
            };
            var requestParamsFormUrlEncoded = new FormUrlEncodedContent(requestParams);
            var tokenServiceResponse = await client.PostAsync(tokenServiceUrl, requestParamsFormUrlEncoded);
            var responseString = await tokenServiceResponse.Content.ReadAsStringAsync();
            var responseCode = tokenServiceResponse.StatusCode;
            var responseMsg = new HttpResponseMessage(responseCode)
            {
                Content = new StringContent(responseString, Encoding.UTF8, "application/json")
            };
            return responseMsg;
        }
    }

    // POST api/User/Register
    [HttpPost]
    [AllowAnonymous]
    [Route("Register")]
    public async Task<HttpResponseMessage> RegisterUser(RegisterUserBindingModel model)
    {
        if (!ModelState.IsValid)
        {
            return await this.BadRequest(this.ModelState).ExecuteAsync(new CancellationToken());
        }

        var user = new ApplicationUser
        {
            UserName = model.Username,
            Name = model.Name
        };

        IdentityResult result = await this.UserManager.CreateAsync(user, model.Password);

        if (!result.Succeeded)
        {
            return await this.GetErrorResult(result).ExecuteAsync(new CancellationToken());
        }

        // Auto login after register (successful user registration should return access_token)
        var loginResult = this.LoginUser(new LoginUserBindingModel()
        {
            Username = model.Username,
            Password = model.Password
        });
        return await loginResult;
    }

    // POST api/User/Logout
    [HttpPost]
    [Route("Logout")]
    public IHttpActionResult Logout()
    {
        this.Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationType);
        return this.Ok(new { message = "Logout successful." } );
    }
}

public class LoginUserBindingModel
{
    [Required]
    public string Username { get; set; }

    [Required]
    [DataType(DataType.Password)]
    public string Password { get; set; }
}

public class RegisterUserBindingModel
{
    [Required]
    public string Username { get; set; }

    [Required]
    [DataType(DataType.Password)]
    public string Password { get; set; }
}

What we have achieved by this code?

  • We have a simple Login and Register (by username and password) + simple logout
  • After user registration the user is automatically logged-in: register returns access_token
  • We have API Help page documentation for all service endpoints (including /api/User/Login)

Update (March 2015)

I found a better way to invoke the “token” service from the OWIN middleware in Web API to perform a bearer authorization from my “/api/user/login” REST service endpoint. It uses the Microsoft.OWIN.Testing package to execute the HTTP request in a temporary in-memory HTTP server.

// POST api/user/login
[HttpPost]
[AllowAnonymous]
[Route("login")]
public async Task<IHttpActionResult> LoginUser(UserAccountBindingModel model)
{
    if (model == null)
    {
        return this.BadRequest("Invalid user data");
    }

    // Invoke the "token" OWIN service to perform the login (POST /api/token)
    var testServer = TestServer.Create<Startup>();
    var requestParams = new List<KeyValuePair<string, string>>
    {
        new KeyValuePair<string, string>("grant_type", "password"),
        new KeyValuePair<string, string>("username", model.Username),
        new KeyValuePair<string, string>("password", model.Password)
    };
    var requestParamsFormUrlEncoded = new FormUrlEncodedContent(requestParams);
    var tokenServiceResponse = await testServer.HttpClient.PostAsync(
        "/api/Token", requestParamsFormUrlEncoded);

    return this.ResponseMessage(tokenServiceResponse);
}

// POST api/user/register
[HttpPost]
[AllowAnonymous]
[Route("register")]
public async Task<IHttpActionResult> RegisterUser(UserAccountBindingModel model)
{
    if (model == null)
    {
        return this.BadRequest("Invalid user data");
    }

    if (!ModelState.IsValid)
    {
        return this.BadRequest(this.ModelState);
    }

    var user = new User
    {
        UserName = model.Username
    };

    var identityResult = await this.UserManager.CreateAsync(user, model.Password);

    if (!identityResult.Succeeded)
    {
        return this.GetErrorResult(identityResult);
    }

    // Auto login after registrаtion (successful user registration should return access_token)
    var loginResult = await this.LoginUser(new UserAccountBindingModel()
    {
        Username = model.Username,
        Password = model.Password
    });
    return loginResult;
}

The above code uses the Web application Startup class, which is by default located in the Startup.cs and Startup.Auth.cs files.

To make the above code working correctly, you will need to modify your Startup.Auth.cs to ensure the OAuthAuthorizationServerOptions are created only once (to reuse the random SystemClock in it):

public void ConfigureAuth(IAppBuilder app)
{
    // Configure the db context and user manager to use a single instance per request
    app.CreatePerOwinContext(MessagesDbContext.Create);
    app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);

    // Enable the application to use a cookie to store information for the signed in user
    // and to use a cookie to temporarily store information about a user logging in with a third party login provider
    app.UseCookieAuthentication(new CookieAuthenticationOptions());
    app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

    // Configure the application for OAuth based flow. Do it only once!
    if (OAuthOptions == null)
    {
        PublicClientId = "self";
        OAuthOptions = new OAuthAuthorizationServerOptions
        {
            TokenEndpointPath = new PathString(TokenEndpointPath),
            Provider = new ApplicationOAuthProvider(PublicClientId),
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
            AllowInsecureHttp = true
        };
    }

    // Enable the application to use bearer tokens to authenticate users
    app.UseOAuthBearerTokens(OAuthOptions);
}

See the working example here: https://github.com/SoftUni/SPA-with-AngularJS/tree/master/Ads-REST-Services.

Update (April 2015)

Many people have noted that the “logout” from Microsoft.OWIN is not working correctly with the OAuth authentication. Yes, the reason is that OAuth is authorization protocol and once a token is issued, OWIN cannot revoke it (no such functionality). See details about the “Web API Signout Problem with OWIN Bearer Authorization” here: http://stackoverflow.com/questions/24552448/web-api-2-owin-authentication-signout-doesnt-logout.

To implement a correct session management and “logout” in your ASP.NET Web API project with the build in Wep API identity framework (Microsoft.AspNet.Identity.Owin), it will require a lot of manual work. I have implemented the following:

  • I created a new database table UserSessions(Id, OwnerUser, AuthToken, ExpirationDateTime) to hold the user sessions.
  • After login, my custom logic saves the access token in the DB + an exipiration date (usually current date and time + 30 minutes).
  • At each request I extend the expiration date: current date and time + 30 minutes.
  • The “signout” action deletes the user session from DB.
  • I needed to change the default [Authorize] attribute with a custom [SessionAuthorize] attribute.

My improved Web API user session management now works as expected:

  • Sessions die after 30 minutes of inactivity.
  • Session’s life is extended at each authorized HTTP request with more 30 minutes.
  • Logout works correctly: after logout the bearer access_token becomes invalid (revoked).

See the full source code of my improved Web API session here.

WebAPI Simple Login + Register + Logout – Source Code

Test the Ads REST service live at: http://softuni-ads.azurewebsites.net:

Browse the full source code at GitHub:

Enjoy the real working ASP.NET Web API user registration + login + logout.

Previews (214,568), Views (31,274), Comments (58)

58 Responses to “ASP.NET MVC Web API Identity (OWIN Security): Auto Login after Register + Custom Login Service Endpoint”

  1. Йосиф says:

    Всичко е много хубаво, но не е handled invalid model state в LoginUser.

  2. KETRodrick says:

    Hello mates, its great piece of writing concerning educationand completely explained, keep it up all the time.

  3. Thanks on your marvelous posting! I seriously enjoyed reading it, you could be a great author. I will remember to bookmark your blog and will often come back from now on. I want to encourage you to continue your great posts, have a nice holiday weekend!

  4. It is in reality a great and helpful piece of info. I’m satisfied that you shared this helpful information with us. Please keep us up to date like this. Thank you for sharing.

  5. nakov says:

    @Йосиф, ами то няма нужда да се валидира модела, защото данните се препредават на оригиналната Login услуга, която го валидира. Ще стане двойна валидация. Ама все пак ще я добавя валидацията на binding модела за пълнота.

  6. Йосиф says:

    @nakov, има нужда, защото модела ако е null ще гръмне на model.Username.
    Весели празници.

  7. This page certainly has all of the info I wanted about this subject and didn’t know who to ask.

  8. Sales providers that may follow your corporate you achieve your goals passenger automotive working leasing gross sales division the oversees all of the work.

  9. KathlenPetre says:

    certainly like your web-site however you need to take a look at the spelling on quite a few of your posts. Several of them are rife with spelling problems and I find it very bothersome to tell the reality then again I will certainly come again again.

  10. What’s up every one, here every person is sharing these know-how, therefore it’s good to read this blog, and I used to pay a quick visit this webpage daily.

  11. Rosario says:

    Wow! At last I got a website from where I be able to really obtain useful information concerning my study and knowledge.

  12. Lucy says:

    Hello, i feel that i saw you visited my website so i came to go back the want?.I am attempting to in finding things to enhance my site!I suppose its ok to make use of a few of your ideas!!

  13. FawnCooley says:

    Nieshao Bo finally said: Guangdong God Chau Chemical Co. So when it comes to thinking about radiator covers, forget the assumptions and comparisons, and start from the same point as a bespoke radiator cabinet designer – with your home, exactly as it is, with all its corners and quirks. Learn the pros and cons of low carb dieting before you jump right in.

  14. Bernardo says:

    WOW just what I was searching for. Came here by searching for full page cache extension

  15. Great post. I was looking for a way to do token authentication with ASP.NET WebAPI, but it’s hard to find decent examples out there. This will do. I agree about being uncomfortable with the hack, but maybe they’re trying to “protect” us from ourselves by hiding the Token creation behind the great api wall.

    Thanks again.

  16. My spouse and I stumbled over here coming from a different web page and thought I might as well check things out. I like what I see so now i’m following you. Look forward to looking over your web page yet again.

  17. Zonetalk says:

    Zonetalk.me is a free dating site that can help you find a life mate online.
    All of us know that seeking online love and romance from dating services is common these days.

    This electronic world connects you singles together. A dating service is a great way to find single on net.
    This dating service helps you singles who live in, Canada,
    Australia, UK, German, … and others to seek their soul mates.
    Seeking women for marriage at a dating site is convenient in recent years.
    Zonetalk.me provides a great way to find girls dating and boys for marriage.

    Members do not pay any fee for using our dating service.
    Good luck and have a nice day!
    http://zonetalk.me/

  18. Nice replies in return of this difficulty with genuine arguments and describing the whole thing on the topic of
    that.

  19. MCupryk@shaw.ca says:

    I was wondering if someone implement the

    public async Task LoginUser(LoginUserBindingModel model)

    in their application.

    Any sample code of this would help.

    I am trying to do this in .NET not angularjs.

  20. mcupryk@shaw.ca says:

    Any example projects in asp.net web api 2.2 using the above example would be much appreciated.

  21. If you use milk bottles to store cold energy in your refrigerator, be sure to mark them with a note
    to remind yourself and your family members not to drink
    the water. Therefore, being involved in design of a bathroom one can come across both ungainly
    forms and abstract sizes. Now, backwards jump from the ledge and land on the balance beam
    on the pillar outside.

  22. Rico says:

    Whenn you are looking for health products, people who are common natural work the best.
    Gingko biloba is an additional ingredient in the best daily
    health supplement for guys to boost stamina and,
    this increases mental alertness, the circulation of blood and strength.

    These companies also make over-the-counter medicines aong with supplements.
    The most frequent causes of the excess lack of potassium are:.

  23. I use a server-side HTTP POST because I cannot directly invoke the service (it is deeply hidden in the OAuthAuthorizationServerHandler class) ?

    Why would you need to do that ? This is for sure the most overkill solution to combine logic on login. Making a post request to yourself ? Nakov, please change the source – what you need is to simply change the following things and this will work like 238905% faster !

    In the ApplicationOAuthProvider just add (line 59) the code that you have after a login is completed :
    var userSessionManager = new UserSessionManager();
    userSessionManager.CreateUserSession(username, authToken);
    // Cleanup: delete expired sessions fromthe database
    userSessionManager.DeleteExpiredSessions();
    You use the extracted user’s username. Just add from the client side the grant_type=password parameter to the request.

    Change the TokenEndpointPath constant in the partial class Startup to “api/User/Login”.

    Please fix this because what you are doing is overkill and you are teaching the youngsters bad practices. I hope you make this change.

  24. Desy says:

    > At each request I extend the expiration date: current date and time + 30 minutes.
    The “signout” action deletes the user session from DB.
    > I needed to change the default [Authorize] attribute with a custom [SessionAuthorize] attribute.

    Dude, it is called RESTful for a reason. By adding a “Session” to it, it stopped being a RESTful services. RESTful do not store ANY KIND OF SESSION STATE.

    • nakov says:

      Parse.com and many other RESTful bases Backend-as-a-Service clouds do similar style server-side session management. I don’t find anything annoying. Typically you login, you get authentication header and you authenticate in further requests by this header. This header is most-probably kept in the database or some other persistent storage – depends on the implementation. This is classical way to add login / authenticate to RESTful services.

  25. Paul says:

    Hi,
    I am trying to create a function to

    RESET PASSWORD (Forgot Password)

    Has anyone done this before?
    Thanks,
    Paul

    • nakov says:

      Changing password in ASP.NET Identity might look like this:


      ApplicationDbContext context = new ApplicationDbContext();
      UserStore store = new UserStore(context);
      UserManager
      UserManager = new UserManager(store);
      String userId = User.Identity.GetUserId(); // YourLogicAssignsRequestedUserId
      String newPassword = "test@123"; // PasswordAsTypedByUser
      String hashedNewPassword = UserManager.PasswordHasher.HashPassword(newPassword);
      ApplicationUser cUser = await store.FindByIdAsync(userId);
      await store.SetPasswordHashAsync(cUser, hashedNewPassword);
      await store.UpdateAsync(cUser);

  26. Bimal says:

    You wrote :

    “I needed to change the default [Authorize] attribute with a custom [SessionAuthorize] attribute.”

    What does it mean ? How to change ? Where is the code ?

  27. samca says:

    @nakov i wanted to thank you
    owin security is a must for every asp.net developer and you explained so much in this article but unfortunately the source code you provided at github (it was awesome by the way) have no client side code at all it is just the server side with some tweaks that you made but i as a developer (noob!) need to see the interactions between the server and the client (js html) and the webapi as well.
    thanks again i know this is an old article of yours and you must be so busy to answer a comment but i really appreciate your help.
    best regards samca

  28. Mudit Juneja says:

    Thanks man !!

    It helped me a lot 🙂

  29. Son says:

    Dear Author,
    Thank you for shared. I have a question want to you answer me.
    You using AccessTokenExpireTimeSpan = TimeSpan.FromDays(14).
    If I have one(many) millions user logged in system together. How many memory system need to save access token???

    P/s: sorry my english is bad.

  30. […] ASP.NET MVC Web API Identity (OWIN … – ASP.NET MVC Web API Identity (OWIN Security): Auto Login after Register + Custom Login Service Endpoint […]

  31. orgW says:

    )’)”.)(())

  32. shimozurdo says:

    Muy buen post, aunque me hubiera gustado haberlo leido antes cuando recien empece a tratar con estas cuestiones de autenticacion. :p

    Gracias!

  33. Tasks Blog says:

    Login Register Page Only 2 Web

    […] ºÐ¾ÑÑ‚о го валидира. Ще стане двойна валидация […]

  34. nicole freeman says:

    Thought-provoking post ! I loved the information , Does someone know if my business could get access to a template CA Adult\’s FSP Referral/Authorization Form copy to work with ?

  35. Musa says:

    Hi Nakov,

    Thanks for this article.

    I have an issue i hope you can help me out with. When i login using the new Login API, the token generated is always invalid.

    Any idea why?

    Thanks.

RSS feed for comments on this post. TrackBack URL

LEAVE A COMMENT