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:
- http://bitoftech.net/2014/06/01/token-based-authentication-asp-net-web-api-2-owin-asp-net-identity/
- http://bitoftech.net/2015/02/03/asp-net-identity-2-accounts-confirmation-password-user-policy-configuration/
- http://kaliko.com/blog/aspnet-template-for-data-access-identity/
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.