2

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.

Misiu
  • 4,738
  • 21
  • 94
  • 198

1 Answers1

2

You can do it by changing the user password every time.

dont know if is accepted in your situation

In case you are interested in this solution, here is how you can do it:

User Manager

    ApplicationUserManager _userManager;

    public ApplicationUserManager UserManager
    {
        get
        {
            return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
        }
        private set
        {
            _userManager = value;
        }
    }

// get your user here
var currentUser = UserManager.Users...
            if (currentUser != null)
            {
                var resultRemove = UserManager.RemovePassword(currentUser.Id);
                var resultAdd = UserManager.AddPassword(currentUser.Id, model.NewPassword);
                if (resultAdd.Succeeded && resultRemove.Succeeded)
                    //Send SMS here
            }
Ermir Beqiraj
  • 867
  • 11
  • 24
  • Thanks for sharing Your code. This is something that might work. I've added code I have so far. The parts missing are generating one-time pass, saving it to database (Your answer will help), sending SMS (quite easy) and returning correct answer from OAuthAuthorizationServerProvider. Could You help me with that? – Misiu Apr 15 '16 at 09:33
  • in which part you need help then ? – Ermir Beqiraj Apr 15 '16 at 09:37
  • last one - returning correct response after from `/oauth/token` right now even when I find correct user my endpoint is always returning BadRequest and unsupported_grant_type. I'd like to return OK (200) and message 'password send' – Misiu Apr 15 '16 at 09:40
  • have a look at this maybe helps : and this one: – Ermir Beqiraj Apr 15 '16 at 09:46
  • Sorry for not writing it before, but if I'm sending request using grant_type of password I get correct response, when sending same data with grant_type of sms I get BadRequest. I'll add startup class to my question with my oAuthServerOptions. – Misiu Apr 15 '16 at 10:07
  • I've set some breakpoints and debugged my code. Inside Startup.cs I'm setting CustomJwtFormat as AccessTokenFormat. This is used when I'm using grant_type=password, but when I'm using grant_type=sms I'd like to return custom message, not AccessToken. Can this be done? – Misiu Apr 15 '16 at 10:17