I am creating a basic Login system with FastApi, React and MongoDb.
I want to store the JWT's in cookies, but nothing worked for me and i really don't want to store them in LocalStorage...
Can someone please tell me what's wrong with my code?
#app.post("/login")
async def login(response:Response,data: OAuth2PasswordRequestForm=Depends()):
username = data.username
password = data.password
user = await query_user(username)
if not user:
raise InvalidCredentialsException
if not verify_password(password,user["password"]):
raise InvalidCredentialsException
access_token = accessToken.create(user)
refresh_token = refreshToken.create(user)
response.set_cookie(key="access_token",value=f"Bearer {access_token}", httponly=True)
response.set_cookie(key="refresh_token",value=f"Bearer {access_token}", httponly=True)
return response
This is pretty easy. In my case, once the user logs in successfully, I redirect to dashboard and then set the token in the cookies.
#app.post('/token', tags=["authenticate"])
async def login_for_access_token(response:Response, request_form: OAuth2PasswordRequestForm = Depends()):
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
token = await token_generator(request_form.username, request_form.password,
expires_delta=access_token_expires)
response = RedirectResponse(url='/api/v1/dashboard', status_code=status.HTTP_303_SEE_OTHER)
response.set_cookie(key="access_token", value=f"Bearer {token[0]}", httponly=True)
return response
My token consists of both the access_token and user_id keys, hence the token[0]. As you will notice, the 'httponly' flag is set to True. This is a security feature as there is no way to access anything about the HttpOnly cookies from "non-HTTP" APIs, e.g. JavaScript. By design, neither reading nor writing such cookies is possible. HttpOnly header flag became a standard, defined in section 5.2.6 of RFC6265, with the storage semantics described in the same document (look for "http-only-flag" throughout the RFC text). Hope this helps, good luck!
Related
I'm migrating my ServiceStack Ormite MVC application to use the JWTAuthProvider for stateless auth. I have this working by authenticating as normal, and setting the returned BearerToken and RefreshToken on a successful auth:
using (var authService = HostContext.ResolveService<AuthenticateService>(ServiceStackRequest))
{
var response = await authService.PostAsync(new Authenticate
{
provider = CredentialsAuthProvider.Name,
UserName = model.UserName,
Password = model.Password,
RememberMe = true,
});
var authResponse = response.GetDto() as AuthenticateResponse;
Response.Cookies.Append("ss-tok", authResponse.BearerToken, new CookieOptions() {...});
Response.Cookies.Append("ss-reftok", authResponse.RefreshToken, new CookieOptions() {...});
}
This appears to work for some users and not for others. The problem seems to be that user accounts with a lot of permissions end up with much larger BearerToken values, some over 4096, so these cannot be set for the ss-tok cookie.
The JWT Auth provider docs provide references to the CreatePayloadFilter and the PopulateSessionFilter, however these are only used when creating a session from a token, not the other way around. I want to filter out items (the permissions in particular) when serializing to a token.
Ideally the permissions would be excluded if there are too many (or always be excluded if that's not possible) and would be lazy-loaded when accessed. This may be possible with a custom AuthUserSession inheriting from the base AuthUserSession that Lazy-loads the Permissions, but I don't know how I could do this without the JWT Provider loading the permissions to serialise too.
Normally, JWT tokens are signed with an application-wide secret or an asymmetric key pair. However, I am integrating into a system that uses a per-user secret that is in fact the salt of the password in the Users table (called private_key there).
I find this system a bit odd. It was apparently meant to make sure that if a user changed his password, the issued tokens would stop working. But it does kill the main advantage of JWT: for any other system to be able to accept the token without having to call the Auth service to validate it. In this case, decoding the token requires to decode it without validation, fetching the database user/private key and then validating it.
The .net code:
List<Claim> claims = new List<Claim>()
{
new Claim("UserUUID", user.UserUUID.ToString())
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(user.PrivateKey));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: _configuration["JwtIssuer"],
audience: _configuration["JwtIssuer"],
claims: claims,
expires: expirationDate,
signingCredentials: creds);
return Ok(new
{
access_token = new JwtSecurityTokenHandler().WriteToken(token),
expires_on = expirationDate
});
Is this actually reasonable and if there is an issue other than validation issues, why is it bad ?
I have seen a similar question but it doesn't address whether this scheme is a good idea, bad idea or just useless burden.
I'd like to share the _profiler cookie for all request for an user, anyway to do this in locust?
self.client.post(
"/customer/account/loginPost/bind/no/",
headers = headers,
data = {
'login[username]': email,
'login[password]': password,
'form_key': form_key
},
cookies={
'_profiler': True
}
)
//Another request in another task, I hope the `_profiler` cookies exists here.
self.client.get('/customer/section/load?sections=cart&force_new_section_timestamp=false',headers=headers)
I think that it's related to this:
How to configure or set cookie as json before making a post request in locust load test tool for FastHTTPUser
You could use the same approach
How to get the last login session details of a user in Keycloak using keycloak rest endpoints?
Example:
builder.append(OAuth2Constants.AUDIENCE+"="+clientId+"&");
builder.append(OAuth2Constants.GRANT_TYPE+"="+OAuth2Constants.UMA_GRANT_TYPE+"&");
headers.put("Content-Type", "application/x-www-form-urlencoded");
headers.put("Authorization", "Bearer "+accessToken);
//String keycloakURL = keyCloakCFGBean.getCreateRefreshSession();
String keycloakURL="http://10.10.8.113:10004/auth/realms/{realm}/protocol/openid-connect/token";
keycloakURL = keycloakURL.replace("{realm}", realmName);
URL url = new URL(keycloakURL);
httpURLConnection = (HttpURLConnection) url.openConnection();
httpURLConnection.setUseCaches(false);
httpURLConnection.setDoInput(true);
httpURLConnection.setRequestMethod("POST");
httpURLConnection.setDoOutput(true);
if (headers != null && headers.size() > 0) {
Iterator<Entry<String, String>> itr = headers.entrySet().iterator();
while (itr.hasNext()) {
Entry<String, String> entry = itr.next();
httpURLConnection.setRequestProperty(entry.getKey(), entry.getValue());
}
}
outputStreamWriter = new OutputStreamWriter(httpURLConnection.getOutputStream(), StandardCharsets.UTF_8);
outputStreamWriter.write(builder.toString());
outputStreamWriter.flush();
So there are a couple of scenarios here. All of this information assumes that you have an appropriate bearer token that you are sending in the header of the request for authentication/authorisation, and requires that you have sufficient admin privileges in the Keycloak realm.
I've not gone into detail in terms of the precise code you write in a particular language, but hopefully the instructions are clear in terms of what you need your code to do.
Sessions
If you are interested in ACTIVE user sessions specifically, you can use the API endpoint as described at: https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_getsessions
That is:
GET /{realm}/users/{id}/sessions
e.g. the full URL would be:
https://{server}/auth/admin/realms/{realm}/users/{id}/sessions
In the response there will be a property called lastAccess that will contain a number that is the usual UNIX milliseconds since 1/1/1970. If you take that number, you can then parse it in your language of choice (Java from the looks of it?) to get the date/time in the format that you require.
All Logins
However I suspect what you really want is to look at the last login across all of the stored information in Keycloak, not just active user sessions, so for that you need to look for the Realm EVENTS. Note that Keycloak only stores events for a certain amount of time, so if it's older than that then you won't find any entries. You can change how long events are stored for in the events config page of the realm admin console.
To get all realm events you call the endpoint mentioned here: https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_getevents (Search for "Get events Returns all events, or filters them based on URL query parameters listed here" if the link doesn't take you straight there).
i.e.
GET /{realm}/events
e.g. the full URL would be: https://{server}/auth/admin/realms/{realm}/events
You will need to filter the results based on "type" (i.e. so that you only have events of type "LOGIN"), and if you want to check a specific user you would also want to filter the results on userId based on the ID of that user account.
You can perform both of these filters as part of the request, to save you having to get the full list of events and filter it client-side. To filter in the request you do something like the following:
https://{server}/auth/admin/realms/{realm}/events?type=LOGIN&user={id}
From the resultant JSON you can then get the result with the highest value of the time property, that represents that login event. The time property will be a UNIX time of milliseconds since 1/1/1970 again, so again you can convert this to a format that is appropriate to you once you have it.
Hope that's helpful!
use Keycloak rest Api
${keycloakUri}/admin/realms/${keycloakRealm}/users
and you will get a response as JWT. Decode it and you will get all the info related to the user.
OR you may use the java client API for example by
Keycloak kc = KeycloakBuilder.builder()
.serverUrl("https://localhost:8443/auth")
.realm("master")
.username("admin")
.password("admin")
.clientId("Mycli")
.resteasyClient(new ResteasyClientBuilder().connectionPoolSize(10).build())
.build();
CredentialRepresentation credential = new CredentialRepresentation();
credential.setType(CredentialRepresentation.PASSWORD);
credential.setValue("test123");
UserRepresentation user = new UserRepresentation();
user.setUsername("testuser2");
user.setFirstName("Test2");
user.setLastName("User2");
user.setEmail("aaa#bbb.com");
user.setCredentials(Arrays.asList(credential));
user.setEnabled(true);
user.setRealmRoles(Arrays.asList("admin"));
UsersResource usersResource = kc.realm("my-realem").users();
UserResource userResource = usersResource.get("08afb701-fae5-40b4-8895-e387ba1902fb");
you will get the list of users. Filter by user ID then you will find all user info.
I just found out that I have a problem with auth0 and it relates to the auth0 configuration audience. So when I explicitly write the audience, the JWT verification failed with error The provided Algorithm doesn't match the one defined in the JWT's Header. When I don't write the audience, everything will work fine, except now everytime the token expire and user click on login link it skip the login process and immediately logged in with the previous credential. I don't want this to happen, I want user to still authenticate themselves again after token expire, just like when I write the audience.
So what is audience and why does it affect the behaviour like this?
And How can I fix it to get the behaviour I wanted?
Below is the configuration of the Auth0
auth0 = new auth0.WebAuth({
clientID: environment.auth0ClientId,
domain: environment.auth0Domain,
responseType: 'token id_token',
//Below is the audience I'm talking about
audience: '${constants.MY_APP}/userinfo',
redirectUri: `${constants.ORIGIN_URL}/auth`,
scope: 'openid email'
});
I need to know how I can make the JWT to be verified correctly as well as make the login behaviour correctly when the JWT expire.
Auth0 can issue two types of tokens: opaque and JWT.
When you specify the audience parameter, you will receive a JWT token. JWTs differ from opaque tokens in that they are self-contained and therefore you verify them directly in your application.
In this case, the JWT you have received is signed with an algorithm different to that which you've defined in your verification logic. You can decode the JWT using https://jwt.io and you can see which algorithm it was signed with in the alg attribute of the header.
You can also find out the signing algorithm your API uses in the Auth0 dashboard. Go APIs, click your API, click the Settings tab and then scroll to Token Setting. You will see it listed as the Signing Algorithm.
Judging by the error message, you are using the java-jwt library, in which case you will need change the signing algorithm accordingly per the steps outlined here: https://github.com/auth0/java-jwt#verify-a-token
For HS256:
try {
Algorithm algorithm = Algorithm.HMAC256("secret");
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer("auth0")
.build(); //Reusable verifier instance
DecodedJWT jwt = verifier.verify(token);
} catch (JWTVerificationException exception){
//Invalid signature/claims
}
Where secret is your API's Signing Secret.
For RS256, it's a little more involved. You first need to decode the token to retrieve the kid (key ID) from the header:
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE";
try {
DecodedJWT jwt = JWT.decode(token);
} catch (JWTDecodeException exception){
//Invalid token
}
You then need to construct a JwkProvider using the jwks-rsa-java library:
JwkProvider provider = new UrlJwkProvider("https://your-domain.auth0.com/");
Jwk jwk = provider.get(jwt.getKeyId());
Finally, you can use the public key retrieved from the JWKS and use it to verify the token:
RSAPublicKey publicKey = (RSAPublicKey) jwk.getPublicKey();
try {
Algorithm algorithm = Algorithm.RSA256(publicKey, null);
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer("auth0")
.build(); //Reusable verifier instance
DecodedJWT jwt = verifier.verify(token);
} catch (JWTVerificationException exception) {
//Invalid signature/claims
}
Keep in mind that it's preferred to use RS256 over HS256 for the reasons outlined here: https://auth0.com/docs/apis#signing-algorithms
You may also find this article useful for detailed information on verifying tokens: https://auth0.com/docs/api-auth/tutorials/verify-access-token