Following blog series by @Taiseer Joudeh (http://bitoftech.net/2015/01/21/asp-net-identity-2-with-asp-net-web-api-2-accounts-management/) I've build simple WebApi with JWT authentication. I'm able to login and query my endpoint using token.
I'm trying to change how my login mechanism works.
Right now user must send his login and password to /oauth/token to get access token that will be send with every request to webapi.
I'd like to be able to login user with password that will be send via SMS to phone number assigned to his account.
It should require two requests to token endpoint path. First one with only user id (login or some unique identifier), then endpoint should check if user exists, if yes then it should send password via SMS.
Then user would input that password in my form and second request would be done to token endpoint path with both user id and password.
I've read about two-factor authentication (http://bitoftech.net/2014/10/15/two-factor-authentication-asp-net-web-api-angularjs-google-authenticator/ and other sources), but it requires user to login using username and password and then to submit second password from SMS.
In my case I want user to pass only username and SMS password.
EDIT:
Below is code I've tried to build, it is starting point, but I don't know how to finish implementing it. I've overriden GrantCustomExtension in OAuthAuthorizationServerProvider
public override async Task GrantCustomExtension(OAuthGrantCustomExtensionContext context)
{
const string allowedOrigin = "*";
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });
//custom grant_type
if (context.GrantType != "sms")
{
context.SetError("invalid_grant", "unsupported grant_type");
return;
}
var userName = context.Parameters.Get("username");
if (userName == null)
{
context.SetError("invalid_grant", "username is required");
return;
}
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser user = await userManager.FindByNameAsync(userName);
if (user == null)
{
context.SetError("invalid_grant", "user not found");
return;
}
//generate one-time password
//store it in database
//send it to user phone number
//return ok message
context.Validated();
//return base.GrantCustomExtension(context);
}
And here is part of Startup.cs that is responsible for setting up oAuthAuthorizationServer:
public void ConfigureOAuthTokenGeneration(IAppBuilder app)
{
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
var issuer = ConfigurationManager.AppSettings["Issuer"];
OAuthAuthorizationServerOptions oAuthServerOptions = new OAuthAuthorizationServerOptions()
{
#if DEBUG
AllowInsecureHttp = true,
#endif
TokenEndpointPath = new PathString("/oauth/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(1),
Provider = new CustomOAuthProvider(),
AccessTokenFormat = new CustomJwtFormat(issuer)
};
// OAuth 2.0 Bearer Access Token Generation
app.UseOAuthAuthorizationServer(oAuthServerOptions);
}
I'm able to send custom grant_type and find user in database, but the hardest part is still missing. I know I can store that one-time password in User table, but maybe ASP.Net Identity 2 has build in mechanism, I dont want to reinvent the wheel.