Search code examples
c#asp.netentity-frameworkasp.net-web-api2ef-database-first

DB-First authentication confusion with ASP.NET Web API 2 + EF6


I need to create a Web API C# application for an existing MySQL database. I've managed to use Entity Framework 6 to bind every database table to a RESTful API (that allows CRUD operations).

I want to implement a login/registration system (so that I can implement roles and permissions in the future, and restrict certain API requests).

The MySQL database I have to use has a table for users (called user) that has the following self-explanatory columns:

  • id
  • email
  • username
  • password_hash

It seems that the de-facto standard for authentication is ASP.Net Identity. I have spent the last hour trying to figure out how to make Identity work with an existing DB-First Entity Framework setup.

If I try to construct ApplicationUser instances storing user instances (entities from the MySQL database) to retrieve user data, I get the following error:

The entity type ApplicationUser is not part of the model for the current context.

I assume I need to store Identity data in my MySQL database, but couldn't find any resource on how to do that. I've tried completely removing the ApplicationUser class and making my user entity class derive from IdentityUser, but calling UserManager.CreateAsync resulted in LINQ to Entities conversion errors.

How do I setup authentication in a Web API 2 application, having an existing user entity?


Solution

  • You say:

    I want to implement a login/registration system (so that I can implement roles and permissions in the future, and restrict certain API requests).

    How do I setup authentication in a Web API 2 application, having an existing user entity?

    It definitely means that you DO NOT need ASP.NET Identity. ASP.NET Identity is a technology to handle all users stuffs. It actually does not "make" the authentication mechanism. ASP.NET Identity uses OWIN Authentication mechanism, which is another thing.

    What you are looking for is not "how to use ASP.NET Identity with my existing Users table", but "How to configure OWIN Authentication using my existing Users table"

    To use OWIN Auth follow these steps:

    Install the packages:

    Owin
    Microsoft.AspNet.Cors
    Microsoft.AspNet.WebApi.Client
    Microsoft.AspNet.WebApi.Core
    Microsoft.AspNet.WebApi.Owin
    Microsoft.AspNet.WebApi.WebHost
    Microsoft.Owin
    Microsoft.Owin.Cors
    Microsoft.Owin.Host.SystemWeb
    Microsoft.Owin.Security
    Microsoft.Owin.Security.OAuth
    

    Create Startup.cs file inside the root folder (example):

    make sure that [assembly: OwinStartup] is correctly configured

    [assembly: OwinStartup(typeof(YourProject.Startup))]
    namespace YourProject
    {
        public class Startup
        {
            public void Configuration(IAppBuilder app)
            {
                var config = new HttpConfiguration();
                //other configurations
    
                ConfigureOAuth(app);
                app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
                app.UseWebApi(config);
            }
    
            public void ConfigureOAuth(IAppBuilder app)
            {
                var oAuthServerOptions = new OAuthAuthorizationServerOptions()
                {
                    AllowInsecureHttp = true,
                    TokenEndpointPath = new PathString("/api/security/token"),
                    AccessTokenExpireTimeSpan = TimeSpan.FromHours(2),
                    Provider = new AuthorizationServerProvider()
                };
    
                app.UseOAuthAuthorizationServer(oAuthServerOptions);
                app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
            }
        }
    
        public class AuthorizationServerProvider : OAuthAuthorizationServerProvider
        {
            public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
            {
                context.Validated();
            }
    
            public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
            {
                context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
    
                try
                {
                    //retrieve your user from database. ex:
                    var user = await userService.Authenticate(context.UserName, context.Password);
    
                    var identity = new ClaimsIdentity(context.Options.AuthenticationType);
    
                    identity.AddClaim(new Claim(ClaimTypes.Name, user.Name));
                    identity.AddClaim(new Claim(ClaimTypes.Email, user.Email));
    
                    //roles example
                    var rolesTechnicalNamesUser = new List<string>();
    
                    if (user.Roles != null)
                    {
                        rolesTechnicalNamesUser = user.Roles.Select(x => x.TechnicalName).ToList();
    
                        foreach (var role in user.Roles)
                            identity.AddClaim(new Claim(ClaimTypes.Role, role.TechnicalName));
                    }
    
                    var principal = new GenericPrincipal(identity, rolesTechnicalNamesUser.ToArray());
    
                    Thread.CurrentPrincipal = principal;
    
                    context.Validated(identity);
                }
                catch (Exception ex)
                {
                    context.SetError("invalid_grant", "message");
                }
            }
        }
    }
    

    Use the [Authorize] attribute to authorize the actions.

    Call api/security/token with GrantType, UserName, and Password to get the bearer token. Like this:

    "grant_type=password&username=" + username + "&password=" password;
    

    Send the token within the HttpHeader Authorization as Bearer "YOURTOKENHERE". Like this:

    headers: { 'Authorization': 'Bearer ' + token }
    

    Hope it helps!