I want to custom my login scenario in my MVC 5 project.
I followed the SPA template to use oauth bearer token for authentication. But I want to add some logic to control the AccessTokenExpires time.
From this topic and A de Baugh's comment, I can change the expire time of the ticket properties. But I still can't find where to add my logic to my custom OAuthAuthorizationServerProvider class.
Basically I want to add something like Remember Me by passing more parameter when sending the login request.
At the moment I must create another controller to handle login
The login method of the controller looks like
// POST api/Account/Login
[AllowAnonymous]
[Route("Login")]
public async Task<IHttpActionResult> Login(LoginViewModel model)
{
if (ModelState.IsValid)
{
ApplicationUser user = await userManager.FindAsync(model.UserName, model.Password);
if (user != null)
{
ClaimsIdentity oAuthIdentity = await userManager.CreateIdentityAsync(user,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookieIdentity = await userManager.CreateIdentityAsync(user,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = ApplicationOAuthProvider.CreateProperties(user.UserName);
// my check for remember me
if (model.RememberMe)
properties.ExpiresUtc = properties.IssuedUtc + TimeSpan.FromDays(7);
else properties.ExpiresUtc = properties.IssuedUtc + TimeSpan.FromHours(3);
var ticket = new AuthenticationTicket(oAuthIdentity, properties);
Authentication.SignIn(properties, oAuthIdentity, cookieIdentity);
return Ok(new
{
user = model.UserName,
access_token = accessTokenFormat.Protect(ticket),
expire = properties.ExpiresUtc
});
}
ModelState.AddModelError("error", "Invalid username or password");
}
return BadRequest(ModelState);
}
Authentication
private IAuthenticationManager Authentication
{
get { return Request.GetOwinContext().Authentication; }
}
And the accessTokenFormat to protect ticket is Startup.OAuthOptions.AccessTokenFormat
Am I doing this in an unusual way? I believe there is a better approach to archive this.
Any suggestion would be appreciate. Thanks!
I am not totally understanding your questions but by rolling my one OAuthAuthenicationServerProvider I found a very good example from #leastprivilege on github: The SimpleRefreshTokenProvider ... this describes (with comments) how to handle the refresh cycle of a token. Maybe this is a good starting point to abstract your own example.
https://github.com/thinktecture/Thinktecture.IdentityModel/blob/master/samples/OAuth2/EmbeddedResourceOwnerFlowWithRefreshTokens/EmbeddedAuthorizationServer/Provider/SimpleRefreshTokenProvider.cs
HTH
P.S.: It is also worth it to spend some time reading his blog about security OAuth, ... http://leastprivilege.com/
Related
Objective:
Once logged in, be authorized to reroute to Contact page which is authorized with the
Must Belong To HR Department policy.
(Note: Using razor pages)
Expected:
Log in, see the claims in user for HR department, have the policy requirements be read, allow the user access to the contact page.
Actual:
Log in successfully, see claims in user for HR department, policy requirements not read or met or something, denied the user access to the contact page.
Program.cs file:
//Authorization
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("MustBelongToHRDepartment",
policy => policy.RequireClaim("Department", "HR"));
});
////RazorPage Options
builder.Services.AddRazorPages(options =>
{
options.Conventions.AuthorizePage("/contact", "MustBelongToHRDepartment");
});
Login page:
public async Task<IActionResult> OnPostAsync(User user)
{
var result = await _signInManager.PasswordSignInAsync(user.username,
user.password, user.rememberMe, lockoutOnFailure: true);
if (result.Succeeded)
{
_logger.LogInformation("User logged in.");
//Create the security context
var claims = new List<Claim> {
new Claim(ClaimTypes.Name, "admin"),
new Claim(ClaimTypes.Email, "admin#mywebsite.com"),
new Claim("Department", "HR")
};
var identity = new ClaimsIdentity(claims, "MyCookieAuth");
ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync("MyCookieAuth", claimsPrincipal);
return RedirectToPage("contact");
}
else
{
return Page();
}
}
Is there anything I'm missing? Format? Silly error?
I have tried docs, YouTube videos, website, asking friends, and searching forums.
I have tried different methods of authorization and such, but nothing seems to work.
This method I am using now should work according to documentation, but I know something is wrong with it.
Resources I have been using:
This is a YouTube video on register and login
docs
docs
var IdeUser = await _userManager.FindByNameAsync(user.username);
await _userManager.AddClaimsAsync(IdeUser, claims);
var claimsPrincipal = await _signInManager.CreateUserPrincipalAsync(IdeUser);
await _signInManager.RefreshSignInAsync(IdeUser);
I added this and it worked. I think claims weren't going to userManager.
I am trying to implement a "Remember Me" functionality in my Web Api project.
I would like to :
have the Remember Me functionality when the user Sign In.
save a cookies for to keep the user always logged in, so that the user no need type the username and password every single time when they visit the websites.
Sign the user in by reading the cookies that saved on the last login.
One more question that I am thinking about is... I am trying to generate the cookies by using JavaScript when the user checked the Remember Me Checkbox. Is it possible to do this?
OR
I should implement the RememberMe() in the AccountController??
Addition:
Here's my code in ApplicationOAuthProvider.
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser user = await userManager.FindByNameAsync(context.UserName);
if (user == null) {...}
if (userManager.IsLockedOut(user.Id)) {...}
if (!(await userManager.CheckPasswordAsync(user, context.Password)))
{ ... }
if (!user.EmailConfirmed) {...}
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = CreateProperties(user.UserName);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
In my JavaScript.
$('#checkbox').click(function () {
if ($('#checkbox').is(':checked')) {
// save username and password
username = $('#txtLoginEmail').val();
password = $('#pass').val();
checkbox = $('#chkRememberMe').val();
} else {
username = '';
password = '';
checkbox = '';
}
});
You need to implement refresh tokens in you app to be able to offer this functionality.
Basically, you need to create a RefreshTokenOAuthProvider that will generate refresh tokens. You can use 2 types of client_id to make a difference between clients who need to be remembered or not.
It is explained in this excellent series of blog posts (though it might start to become a little bit outdated, the information regarding owin setup is gold).
I'm trying to lock user login after 3 unsuccessful login attempts for 5 minutes. I have add this 3 lines to App_Start/IdentityConfig.cs public static ApplicationUserManager Create( ... ) method:
manager.MaxFailedAccessAttemptsBeforeLockout = 3;
manager.DefaultAccountLockoutTimeSpan = new TimeSpan(0, 5, 0);
manager.UserLockoutEnabledByDefault = true;
After that I register new user via POST /api/Account/Register (in default scaffolded AccountController). Account is created and LockoutEnabled property is set to true. But if I try to login for via POST /Token few times with wrong password account isn't locked down.
I'm also interested where is implementation of /Token endpoint. Is it in AccountController GET api/Account/ExternalLogin. I have set breakpoint there but execution wasn't stopped there when I tried to login.
What am I missing?
If you are using the default Web API template from Visual Studio, you have to change the behavior of GrantResourceOwnerCredentials method of the ApplicationOAuthProvider class (found inside the Provider folder of your Web API project). Something like this could allow you to track failed login attempts, and stop locked out users from logging in:
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
var user = await userManager.FindByNameAsync(context.UserName);
if (user == null)
{
context.SetError("invalid_grant", "Wrong username or password."); //user not found
return;
}
if (await userManager.IsLockedOutAsync(user.Id))
{
context.SetError("locked_out", "User is locked out");
return;
}
var check = await userManager.CheckPasswordAsync(user, context.Password);
if (!check)
{
await userManager.AccessFailedAsync(user.Id);
context.SetError("invalid_grant", "Wrong username or password."); //wrong password
return;
}
await userManager.ResetAccessFailedCountAsync(user.Id);
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = CreateProperties(user.UserName);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
Be aware that this way you can only lock out users trying to login using the password grant (Resource Owner Credentials). If you also want to disallow locked out user to login using other grants, you have to override the other methods (GrantAuthorizationCode, GrantRefreshToken, etc.), checking if await userManager.IsLockedOutAsync(user.Id) is true in those methods too.
we are facing some problems with the new OWIN auth in web api 2. We are developing mobile apps connecting to API services and we need to differentiate the responses during the login phase. Example: a) Bad Request, b) user not found, c) password incorrect, etc.
Unfortunately, if I intentionally send a wrong password, the /Token endpoint return a "Bad Request" HTTP response, so I can't distinguish between the cases.
Inspecting the web code, I think this is the point (where user is null) where i could decide what kind of response to return back, but how?
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
User user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = CreateProperties(user.UserName, user.ID);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
Thanks in advance for help ;)
Returning 400 bad request when the user provides invalid username, or a password, or user is in active is the right way to do it check this post here.
As well my recommendation from security standpoint and once the user try to login to your system, you should not return where is the issue coming from, in other words you should return that "Username or password is incorrect". You rarely see systems return what is the incorrect input param when you do login, i.e. "Password is incorrect."
Incase that you have to implement the requirements you asked for, you might build simple POCO class which contains (ErrorCode, ErrorDesc) and return it as JSON object in the response while keeping the http status code 400. And for the client application you will read this JSON object when you receive 400.
Hope this answers your question.
It looks like you could do something like
context.Response.StatusCode = 401;
return;
About a month ago I had a project working perfectly with ASP Identity OAuth. I'd send a POST request to the /Token endpoint with grant_type, username, and password, and all was dandy.
I recently started a new project based off of Visual Studio 2013 RC2's SPA template. It's a bit different than the old template. Authentication is set up to pretty basic defaults,
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
//AuthorizeEndpointPath = new PathString("/Account/Authorize"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
Nothing significant changed from the default template. I can register accounts successfully through a Web API controller method I have implemented;
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
public async Task<IHttpActionResult> Register(RegisterBindingModel model)
{
if (ModelState.IsValid)
{
var user = new TunrUser() { UserName = model.Email, Email = model.Email, DisplayName = model.DisplayName };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
return Created(new Uri("/api/Users/" + user.Id,UriKind.Relative), user.toViewModel());
}
else
{
return BadRequest(result.Errors.First());
}
}
return BadRequest(ModelState);
}
However, no matter what I POST to the /Token endpoint, I always get the same response.
{"error":"invalid_client"}
Normally I pass the following request body
grant_type=password&username=user%40domain.com&password=userpassword
But this results in the same error. This worked in the previous VS2013 SPA template / Identity. What's changed?
Thank you!
You have to Override the ValidateClientAuthentication & GrantResourceOwnerCredentials in the OAuthAuthorizationServerProvider.
See example here:
http://www.tugberkugurlu.com/archive/simple-oauth-server-implementing-a-simple-oauth-server-with-katana-oauth-authorization-server-components-part-1
So it turns out that the new templates don't include a functional implementation of ApplicationOAuthProvider that was present in the older templates.
After watching this build talk, I investigated further and found that a working implementation of ApplicationOAuthProvider is available to check out in this NuGet package! It's very similar to the old implementation.
In addition, you can use the ApplicationOAuthProvider class that comes with the WebApi template when Individual User Accounts is chosen as the Security option. However, you'll have to change a couple other things, which I've listed below. I hope it helps.
The ApplicationOAuthProvider class that comes with the WebApi/Individual User Accounts template contains the following method:
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = CreateProperties(user.UserName);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
Copy this to the ApplicationOAuthProvider class in your SPA template project, overwriting the original method. The code user.GenerateUserIdentityAsync method is invalid when copied to the SPA template project because the ApplicationUser class does not allow for the "bearer" authentication type.
Add an overload similar to the following to the ApplicationUser class (find it in the Models\IdentityModels.cs file):
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager , string authenticationType)
{
var userIdentity = await manager.CreateIdentityAsync(this , authenticationType);
// Add custom user claims here
return userIdentity;
}
You should now be able to use /Token endpoint correctly.