Implementing OAuthAuthentication with OWIN and Identity to secure Web API and let external application like Chrome Extension to access our self hosted API for getting and posting data through our server.
I know this is the basic implementation of OAuth. Could this still be improved in terms of security and best practice?
Also, OAuth Authentication is implemented along our existing Cookie Authentication.
WebApiConfig:
public class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
config.EnableCors(new EnableCorsAttribute("*", "*", "get,post"));
config.Routes.MapHttpRoute(
"API_Default",
"api/{controller}/{id}",
new {id = RouteParameter.Optional});
}
}
Start.Auth
public void ConfigureAuth(IAppBuilder app)
{
app.CreatePerOwinContext<UserManager>(UserManager.Create);
app.CreatePerOwinContext<RoleManager>(RoleManager.Create);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/account/login"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<UserManager, User, int>(
validateInterval: TimeSpan.FromMinutes(Settings.Instance.SessionValidateInterval),
regenerateIdentityCallback: async (m, u) =>
{
var identity = await m.CreateIdentityAsync(u, DefaultAuthenticationTypes.ApplicationCookie);
//omitted some codes
return identity;
},
getUserIdCallback: u => Convert.ToInt32(u.GetUserId()))
},
ExpireTimeSpan = TimeSpan.FromMinutes(Settings.Instance.SessionExpiryTimeout)
});
DataProtectionProvider = app.GetDataProtectionProvider();
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
//OAuth
app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true, //enable this only when debugging.
ApplicationCanDisplayErrors = true,
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
TokenEndpointPath = new PathString("/oauth2/token"),
Provider = new OAuthAuthorizationServerProvider
{
OnValidateClientAuthentication = async (context) =>
{
context.Validated();
await Task.FromResult<object>(null);
},
OnGrantResourceOwnerCredentials = async (context) =>
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
var manager = context.OwinContext.GetUserManager<UserManager>();
var user = await manager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The username or password is incorrect");
return;
}
if (!user.EmailConfirmed)
{
context.SetError("invalid_grant", "Invalid login");
return;
}
var identity = await manager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ExternalBearer);
//omitted some codes
var authProps = new AuthenticationProperties(new Dictionary<string, string>
{
{ "client_id", 'Foo' },
//omitted some codes
});
var ticket = new AuthenticationTicket(identity, null);
context.Validated(ticket);
await Task.FromResult<object>(null);
},
OnGrantRefreshToken = async (context) =>
{
var originalClient = context.Ticket.Properties.Dictionary["client_id"];
var currentClient = context.ClientId;
if (originalClient != currentClient)
{
context.SetError("invalid_clientId", "Refresh token is issued to a different clientId.");
return;
}
var refreshIdentity = new ClaimsIdentity(context.Ticket.Identity);
//omitted some codes
var refreshTicket = new AuthenticationTicket(refreshIdentity, context.Ticket.Properties);
context.Validated(refreshTicket);
await Task.FromResult<object>(null);
}
},
RefreshTokenProvider = new OAuthRefreshTokenProvider()
});
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
Provider
public class OAuthRefreshTokenProvider : IAuthenticationTokenProvider
{
public async Task CreateAsync(AuthenticationTokenCreateContext context)
{
var clientId = context.Ticket.Properties.Dictionary["client_id"];
if (string.IsNullOrEmpty(clientId))
{
return;
}
context.Ticket.Properties.ExpiresUtc = DateTime.UtcNow.AddMinutes(120);
context.SetToken(context.SerializeTicket());
await Task.FromResult<object>(null);
}
public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
context.DeserializeTicket(context.Token);
if (context.Ticket == null)
{
context.Response.StatusCode = 400; //Bad request
context.Response.ContentType = "application/json";
context.Response.ReasonPhrase = "Invalid Token";
return;
}
if (context.Ticket.Properties.ExpiresUtc <= DateTime.UtcNow)
{
context.Response.StatusCode = 401; //Unauthorized
context.Response.ContentType = "application/json";
context.Response.ReasonPhrase = "Unauthorized";
return;
}
context.Ticket.Properties.ExpiresUtc = DateTime.UtcNow.AddMinutes(120);
context.SetTicket(context.Ticket);
await Task.FromResult<object>(null);
}
public void Create(AuthenticationTokenCreateContext context)
{
throw new NotImplementedException();
}
public void Receive(AuthenticationTokenReceiveContext context)
{
throw new NotImplementedException();
}
}