Keycloak API: Getting specific message when user action is required - api

Im calling this API Keycloak endpoint
/auth/realms/master/protocol/openid-connect/token
to obtain the user token. But the response is
{
"error":"invalid_grant",
"error_description":"Account is not fully set up"
}
and status code 400.
This response is too ambiguous. How can I get more detailed response to know that i have to redirect the user to my custom "change password" page and how can i get the user token?

Login to Keycloak and check if there are any Required User Actions pending for the user
like (Update Password, Verify email, etc)
You can check Keycloak logs for more detail about the error

I had the same problem, so I used workaround. Workaround is Authentication SPI. I added new step into authentication flow like this:
#Override
public void authenticate(AuthenticationFlowContext context) {
var user = context.getUser();
var foundUpdatePasswordRequiredAction = user.getRequiredActionsStream()
.anyMatch(actionName -> UserModel.RequiredAction.UPDATE_PASSWORD.name().equals(actionName));
if (foundUpdatePasswordRequiredAction) {
context.getEvent().user(user);
context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
Response challengeResponse = errorResponse(Response.Status.UNAUTHORIZED.getStatusCode(), "temporary_password", "Constant password is not set up");
context.failure(AuthenticationFlowError.INVALID_USER, challengeResponse);
return;
}
context.success();
}
It means if user has required action and the action equals "UPDATE_PASSWORD" return my error code and my error message. It works good for me.

Related

Clean way to create user and issue jwt token?

Currently i have an AuthenticationController which listens on post registration.
This registration method, stores the user and create a jwt token.
[HttpPost("registration")]
public async Task<IActionResult> Registration([FromBody] RegistrationViewModel model)
{
if (!ModelState.IsValid)
return BadRequest(new { code = "ModelNotValid", description = "Model is not valid." });
if (_userService.UserNameTaken(model.UserName))
return BadRequest(new { code = "UserNameTaken", description = $"The User Name
{model.UserName} is taken." });
var identityResult = await _userService.CreateUserAsync(model);
if (!identityResult.Succeeded)
return BadRequest(identityResult.Errors);
var user = _userService.GetUserByUserName(model.UserName);
int validityDurationInHours = 3;
string token = _jwtService.GenerateJwtToken(user, await _userService.GetUserRolesAsync(user),
validityDurationInHours);
return Ok(new { code = "RegistrationSuccess", auth_token = token });
}
But now i would like to refactor the code, so there would be a UsersController which saves the user and i am not sure if i should generate the jwt token in the same method as it is done now.
So i what i see as a way is when the user registers on client side it sends the data to the backend, if it was correct than the client will be notifed, which than redirects user to the login page. But i feel unnecesarry the redirection, the user data was correct so it should get the jwt token straigth away.
But i am unsure that the issueing the token should be done by the userscontroller default post method, which saves the user.
The other way i see is when the user is saved the backend notifies the client and than the client create a new request to the backend to issue the token. But then the request feels unnecessary.

How to invalidate a JWT token of a user after password is changed by someone

I created an MVC page used JWT authentication. Once a user logged in successfully, the app returns a JWT token to the user that's stored in the request header. But then a problem occurred. Another person also signs in with the same user account and changes the password. So the first logged in session should be terminated because of security issues. The solution I thought is invalidating the JWT token of that user. But I have to define when was the user's password changed. The JWT token doesn't contain the password information so I couldn't request to the backend server to determinate the password was changed every time the user (with old password) request to the server, either. I need some ideas, suggestions.
For this feature you should add new property like SerialNumber as string on Users table
public class User { public string SerialNumber { get; set; } }
when you want to create new token for user add user SerialNumber to Claims like this
new Claim(ClaimTypes.SerialNumber, user.SerialNumber, ClaimValueTypes.String, issuer),
and when changed user password or username or status or every important property you should update serial number. when serial changed on token validator method after first http request will raise error code 401 (that means Unauthorized)
public async Task ValidateAsync(TokenValidatedContext context)
{
var claimsIdentity = context.Principal.Identity as ClaimsIdentity;
if (claimsIdentity?.Claims == null || !claimsIdentity.Claims.Any())
{
context.Fail("This is not our issued token. It has no claims.");
return;
}
var serialNumberClaim = claimsIdentity.FindFirst(ClaimTypes.SerialNumber);
if (serialNumberClaim == null)
{
context.Fail("This is not our issued token. It has no serial.");
return;
}
var userIdString = claimsIdentity.FindFirst(ClaimTypes.UserData).Value;
if (!int.TryParse(userIdString, out int userId))
{
context.Fail("This is not our issued token. It has no user-id.");
return;
}
var user = await _signInService.GetUserAsync(userId);
if (user == null)
{
context.Fail("User deleted!");
return;
}
if (user.SerialNumber != serialNumberClaim.Value || !user.Status)
{
context.Fail("This token is expired. Please login again.");
return;
}
}
on JWT Token configuration
OnTokenValidated = context =>
{
var tokenValidatorService = context.HttpContext.RequestServices.GetRequiredService<ITokenFactoryService>();
return tokenValidatorService.ValidateAsync(context);
},
Using the password directly in the token is a security risk, since an attacker could retrieve it from the user's computer. Better to either:
include a unique token ID in the token, and maintain a list of revoked tokens (or of allowed tokens; whichever is a better fit). or
include a "version number" in the user list, change it whenever the password is changed, and include the version number when the token is issued. That way, all old tokens can be rejected. #Mohammad's answer has an example of something similar.
None of those pieces of information means anything by themselves.
Use password as claim in jwt?
It will be checked in every request, so after password is changed it will return 401

How to Create login and logout using Vapor (Basic Authentication)

I want to create login and logout methods and routes. I've done already basic authentication but now I'm stuck how to continue. How should I do that, should I use sessions?
I'm using Vapor 3, Swift 4 and PostgreSQL and followed this tutorial https://medium.com/rocket-fuel/basic-authentication-with-vapor-3-c074376256c3. I'm total newbie so I appreciate a lot if you can help me!
my User model
struct User : Content, PostgreSQLModel, Parameters {
var id : Int?
private(set) var email: String
private(set) var password: String
}
extension User: BasicAuthenticatable {
static let usernameKey: WritableKeyPath<User, String> = \.email
static let passwordKey: WritableKeyPath<User, String> = \.password
}
UserController.swift, registering user.
private extension UserController {
func registerUser(_ request: Request, newUser: User) throws -> Future<HTTPResponseStatus> {
return try User.query(on: request).filter(\.email == newUser.email).first().flatMap { existingUser in
guard existingUser == nil else {
throw Abort(.badRequest, reason: "a user with this email already exists" , identifier: nil)
}
let digest = try request.make(BCryptDigest.self)
let hashedPassword = try digest.hash(newUser.password)
let persistedUser = User(id: nil, email: newUser.email, password: hashedPassword)
return persistedUser.save(on: request).transform(to: .created)
}
}
}
So in Basic authentication there is no 'logout' per se as there's no login. With HTTP Basic Auth you transmit the user's credentials with each request and validate those credentials with each request.
You mention sessions, but first it's important to know what type of service you are providing? Are you providing an API or a website? They are different use cases and have different (usually) methods for authentication and login.
For an API you can use Basic Authentication and generally in your login function you exchange the credentials for some sort of token. Clients then provide that token with future requests to authenticate the user. To log out you simply destroy the token in the backend so it is no longer valid.
For a website, things are a little different since you can't manipulate the requests like you can with a normal client (such as setting the Authorization header in the request). HTTP Basic authentication is possible in a website, though rarely used these days. What traditionally happens is you submit the user's credentials through a web form, authenticate them and then save the authenticated user in a session and provide a session cookie back to the browser. This authenticates the user in future requests. To log a user out you just remove the user from the session.
Vapor's Auth package provides everything you need to do both of these scenarios. See https://github.com/raywenderlich/vapor-til for examples of both

Bookmarking login page with nonce

I'm trying to integrate an MVC4 web client with IdentityServer using Microsoft OWIN middleware OIDC authentication v4.0.0. When requesting an ID token from the authorize endpoint, a nonce must be supplied, and the login page served up has the nonce in the query string. If a user bookmarks this and uses it to log in the next day (for example), nonce validation in the client will fail because they'll no longer have that nonce stored, or it will have expired, etc.
This triggers the AuthenticationFailed notification in the client with this exception:
"IDX21323: RequireNonce is '[PII is hidden]'. OpenIdConnectProtocolValidationContext.Nonce was null, OpenIdConnectProtocol.ValidatedIdToken.Payload.Nonce was not null. The nonce cannot be validated. If you don't need to check the nonce, set OpenIdConnectProtocolValidator.RequireNonce to 'false'. Note if a 'nonce' is found it will be evaluated."
At this point I could HandleResponse, redirect to an error page and so on. If they then try to access a protected resource again, the redirect to IdentityServer immediately returns an ID token due to the previous successful login (from its point of view I guess?) and this time the nonce validates and the user is logged into the client. But this is a rather strange experience for the user - their first attempt to log in appears to fail, they get an error, but then when they try again they don't even have to log in, they're just taken straight in.
An alternative would be to handle this type of exception in AuthenticationFailed by redirecting to the home protected resource so the happens 'seamlessly' in the background. To the user it appears as if their first login attempt worked. But I'm not sure if this is appropriate for genuine nonce validation issues. I'm also worried this may lead to redirect loops in some cases.
So to get to my question... what is the common approach to this issue of bookmarking login pages / nonces? Have I made a fundamental mistake or picked up a fundamental misunderstanding somewhere along the line which has allowed this scenario to occur?
Here is the code that needs to go into the call to
UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
AuthenticationType = "oidc",
Authority = appSettings["ida:Authority"],
ClientId = appSettings["ida:ClientId"],
ClientSecret = appSettings["ida:ClientSecret"],
PostLogoutRedirectUri = appSettings["ida:PostLogoutRedirectUri"],
RedirectUri = appSettings["ida:RedirectUri"],
RequireHttpsMetadata = false,
ResponseType = "code id_token",
Scope = appSettings["ida:Scope"],
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = authFailed =>
{
if (authFailed.Exception.Message.Contains("IDX21323"))
{
authFailed.HandleResponse();
authFailed.OwinContext.Authentication.Challenge();
}
return Task.FromResult(true);
}
},
SignInAsAuthenticationType = "Cookies"
}
});

AspNet Core External Authentication with Both Google and Facebook

I am trying to implement the Form-Authentication in ASP.Net Core with Both Google and Facebook Authentications. I followed some tutorials and after some struggles, I managed to make it work both.
However, the problem is that I cannot use both authentications for the same email.
For example, my email is 'ttcg#gmail.com'.
I used Facebook authentication to log in first... Registered my email and it worked successfully and put my record into 'dbo.ASPNetUsers' table.
Then I logged out, clicked on Google Authentication to log in. It authenticated successfully, but when I tried to register it keeps saying that my email is already taken.
I tried to do the same thing for other online websites (Eg, Stackoverflow). I used the same email for both Google and Facebook and the website knows, I am the same person and both my login / claims are linked even though they come from different places (Google & Facebook).
I would like to have that feature in my website and could you please let me know how could I achieve that.
In theory, it should put another line in 'dbo.AspNetUserLogins' and should link the same UserId with multiple logins.
Do I need to implement my own SignInManager.SignInAsync method to achieve that feature? Or am I missing any configuration?
You need to link your Facebook external login to your Google external login with your email by using UserManager.AddLoginAsync, you cannot register twice using the same adresse if you use the adresse as login.
Check out the Identity sample on Identity github repo.
https://github.com/aspnet/Identity/blob/dev/samples/IdentitySample.Mvc/Controllers/ManageController.cs
To link external login to a user, the Manae controller expose methods LinkLogin and LinkLoginCallback
LinkLogin requests a redirect to the external login provider to link a login for the current user
LinkLoginCallback processes the provider response
//
// POST: /Manage/LinkLogin
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult LinkLogin(string provider)
{
// Request a redirect to the external login provider to link a login for the current user
var redirectUrl = Url.Action("LinkLoginCallback", "Manage");
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl, _userManager.GetUserId(User));
return Challenge(properties, provider);
}
//
// GET: /Manage/LinkLoginCallback
[HttpGet]
public async Task<ActionResult> LinkLoginCallback()
{
var user = await GetCurrentUserAsync();
if (user == null)
{
return View("Error");
}
var info = await _signInManager.GetExternalLoginInfoAsync(await _userManager.GetUserIdAsync(user));
if (info == null)
{
return RedirectToAction(nameof(ManageLogins), new { Message = ManageMessageId.Error });
}
var result = await _userManager.AddLoginAsync(user, info);
var message = result.Succeeded ? ManageMessageId.AddLoginSuccess : ManageMessageId.Error;
return RedirectToAction(nameof(ManageLogins), new { Message = message });
}