2
votes

I have recently started delving into WebApi as a basis to start new C# web projects from. I have had a look at the basic templates that VS2013 Update 4 provides, especially the MVC (to try and port the methods to WebApi) as well as the basic WebApi templates.

Through my efforts of setting up this framework (which basically should provide authentication and authorization rules) I have used to following sources as a guideline:

Using these sources I have pieced together a basic WebApi framework that uses OAuth along with JWT as the bearer token provider. I have also replaced the native AspNet Identity libraries with a library that uses a Telerik ORM instead of Entity Framework (as we need to map to custom tables that are structurally defined using meta data, however this is unimportant for now).

However, none of the above sources describe how to do a log in with WebApi, and other searches I have done did not yield results.

Using the basic MVC template (with Individual user accounts and Cookie Authentication) that uses AspNet Identity, it includes quite a beefy login process that logs a user in, that can lock a user out, or that can check whether account verification is required, and this is all provided by the sign in manager.

The basic WebApi template (with Individual user accounts and Cookie Authentication) uses AuthAuthorizationServerOptions which is set up to check whether a user with the provided credentials exist, generates the user identity to get the ClaimsIdenity, generates an authentication ticket, validates the ticket, and then calls context.Request.Context.Authntication.SignIn(param type ClaimsIdentity). What this function does, I am not sure, as it does not seem to have the same rules applied to it as what the MVC template has that uses the SigninManager.

What I would like to know, is what would the correct procedure be to log a person in using WebApi? Does the bearer token granting form the just of the log in process, or should a log in be done (using an IAuthenticationManager or a SigninManager), and based on the result of the log in, if it is successful, then only should the token be requested?

Below I have provided my version of my Startup.cs and my OAuthProvider.

Startup.cs:

[assembly: OwinStartup(typeof(TelerikAndWebAPI.Startup))]
namespace TelerikAndWebAPI
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            HttpConfiguration config = new HttpConfiguration();

            app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
            app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
            app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);

            ConfigureOAuthTokenGeneration(app);
            ConfigureOAuthTokenConsumption(app);

            WebApiConfig.Register(config);
            app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
            app.UseWebApi(config);
        }

        //Uses JWT bearer tokens
        public void ConfigureOAuthTokenGeneration(IAppBuilder app)
        {
            bool debug = false;
            if (System.Diagnostics.Debugger.IsAttached) {
                debug = true;
            }
            else{
                debug = false;
            }

            OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
            {
                AllowInsecureHttp = debug,
                TokenEndpointPath = new PathString("/token"),
                AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
                Provider = new CustomOAuthProvider(),
                AccessTokenFormat = new CustomJwtFormat(HttpContext.Current.Request.Url.AbsoluteUri.Replace(HttpContext.Current.Request.Url.PathAndQuery, "/"))
            };

            app.UseOAuthAuthorizationServer(OAuthServerOptions);
        }

        //Consume the JWT bearer tokens
        private void ConfigureOAuthTokenConsumption(IAppBuilder app)
        {

            var issuer = HttpContext.Current.Request.Url.AbsoluteUri.Replace(HttpContext.Current.Request.Url.PathAndQuery, "/");
            string audienceId = JwtCodeProvider.audienceID;
            byte[] audienceSecret = TextEncodings.Base64Url.Decode(JwtCodeProvider.audienceSecret);
            // Api controllers with an [Authorize] attribute will be validated with JWT
            app.UseJwtBearerAuthentication(
                new JwtBearerAuthenticationOptions
                {
                    AuthenticationMode = AuthenticationMode.Active,
                    AllowedAudiences = new[] { audienceId },
                    IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
                    {
                        new SymmetricKeyIssuerSecurityTokenProvider(issuer, audienceSecret)
                    }
                });
        }
    }
}

OAuthProvder:

    public class CustomOAuthProvider : OAuthAuthorizationServerProvider
    {
        public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {
            context.Validated();
            return Task.FromResult<object>(null);
        }

        public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
        {
            var allowedOrigin = "*";

            context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });

            using (AccountController accounts = new AccountController())
            {
                //Should I plug in a signin manager or authentication manager here and then based on whether the
                //sign in is successful, then do the bearer token generation?
                IdentityUser user = await accounts.FindUser(context.UserName, context.Password);

                if (user == null)
                {
                    context.SetError("invalid_grant", "The user name or password is incorrect.");
                    return;
                }

                ClaimsIdentity oAuthIdentity = await accounts.GenerateUserIdentity(user, "JWT");

                var ticket = new AuthenticationTicket(oAuthIdentity, null);

                context.Validated(ticket);
            }          

        }
    }

And just for clarity sake, the source for ClaimsIdentity oAuthIdentity = await accounts.GenerateUserIdentity(user, "JWT");:

    public async Task<ClaimsIdentity> GenerateUserIdentity(IdentityUser user,string authenticationType)
    {
        ClaimsIdentity oAuthIdentity = await UserManager.CreateIdentityAsync(user, authenticationType);

        return oAuthIdentity;
    }

I have included comments in the OAuthRpovider class of where I think I should plug in a SignInManager method, however, i am not sure. If anyone could shed some light on what the best practice is, I would greatly appreciate it.

Just as a disclaimer, a lot of the code in my classes are as they are in my provided sources. This I will change this once I have finalized the proof of concept.

1

1 Answers

5
votes

The problem with VS 2013 templates that they mix between authentication for MVC application (Application which renders in a browser and use traditional auth cookies) and Web Api authentication which depends on OAuth 2.0 Bearer Tokens regardless the token format (JWT or the default format).

In the REST APIs World authentication should kept simple becuase you have a wide range on consumers (mobile apps, JS, server side services, MVC, etc...) and it should be stateless (Authentication done with each request) so to ahcive this you need to obtain an OAuth access token at the beginning by providing username/password validating them on the server then issue this token and return it to the consumer. This token should have expiry date/time (few hours, days, months).

Once you have the access token you need to send it with each request in the Authorization header using Bearer scheme. Then server will be responsible to validate it and allow/deny the call. JWT access tokens are self contained tokens, so you can put claims inside them (information about the user logged in or the client application acting on behalf of the user), and you can read those claims again on the server with each request without hitting the DB.

Now the SignIn Manager is not needed in your case, it will be useful if you want to create a secure authentication cookie which will be used in MVC app.

If your application will contain MVC and Web API then you need the SignIn Manager.

I hope that my answer clarifies your concerns.