I'm new with keycloak and following a tutorial over internet, I've configured a new realm "example" with a client "app-backend", related role "admin" (not composed) and realm role "app-admin"(composed with the client role "admin").
I've also created one user and I've assigned the realm role "admin".
All ok at this point but, when I ask for access token with the POST API call to keycloak server(http://localhost:8080/auth/realms/barber-reservation/protocol/openid-connect/token), I've noticed that client roles are not contained inside the "resource_access" object, instead I've found "account" object inside it.
This strange behavior is making fail all authorization verification from my spring boot app.
Following the acess token received:
{
"exp": 1608478284,
"iat": 1608477984,
"jti": "5ce17d4d-e3b3-4207-8010-45c0895a9a6a",
"iss": "http://localhost:8080/auth/exmple/app-backend",
"aud": [
"app-backend"
],
"sub": "d3fcc7df-878e-4363-91d6-f06437de5f90",
"typ": "Bearer",
"azp": "app-frontend",
"session_state": "dad4ab85-850a-4c63-8a26-7b3b6de9f821",
"acr": "1",
"allowed-origins": [
"*"
],
"realm_access": {
"roles": [
"app-admin"
]
},
"resource_access": {
"account": {
"roles": [
"manage-account",
"manage-account-links",
"view-profile"
]
},
"app-backend": {
"roles": [
"admin"
]
}
},
"scope": "openid email profile",
"email_verified": true,
"name": "Name Surname",
"preferred_username": "user#email.com",
"locale": "it",
"given_name": "Name",
"family_name": "Surname",
"email": "user#email.com"
}
I was expecting that the client roles section was contained inside the "resource_access" object like this:
"resource_access": {
"app-backend": {
"roles": [
"admin"
]
}
}
Any Ideas on how to correct this strange behaviour?
Thank you.
There is nothing strange here, your client role admin from the client "app-backend" is on the resource_access object:
"resource_access":
{
"account": {
"roles": [
"manage-account",
"manage-account-links",
"view-profile"
]
},
"app-backend": {
"roles": [
"admin"
]
}
},
The resource_access is a list of Key Values, i.e., client and their roles.
Related
I am using Firebase Authentication with Identify Platform and am trying to add custom claims when a user is created. I am looking at this example from Google's website here: Setting custom and session claims:
exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
if (context.credential &&
context.credential.providerId === 'saml.my-provider-id') {
return {
// Employee ID does not change so save in persistent claims (stored in
// Auth DB).
customClaims: {
eid: context.credential.claims.employeeid,
},
// Copy role and groups to token claims. These will not be persisted.
sessionClaims: {
role: context.credential.claims.role,
groups: context.credential.claims.groups,
}
}
}
});
The code is straight forward. I am trying to add custom claims for all new users but they are not getting set. I am not sure how else to to try. This is my actual code:
exports.beforeUserCreate = functions.auth.user().beforeCreate((user, context) => {
functions.logger.info('Attempting to set claims for new user', user);
functions.logger.info('Here is the context', context);
return {
customClaims: {
roles: ['user'],
},
sessionClaims: {
roles: ['user'],
},
};
});
I do see the logs in the Google console, so I know my function is being called. I also tested the claims without the array like roles: 'TestRole', but that didn't work either. The user object just does not have the custom claims.
If I manually set the claims they do show up as expected:
{
"roles": [
"admin",
"subscriber",
"superadmin"
],
"iss": "https://securetoken.google.com/...",
"aud": "xxx",
"auth_time": 1661813313,
"user_id": "xxxx",
"sub": "xxx",
"iat": 1661813313,
"exp": 1661816913,
"email": "xxx",
"email_verified": false,
"firebase": {
"identities": {
"email": [
"xx"
]
},
"sign_in_provider": "password"
}
}
This is what the user object looks like when I try to create the claims automatically:
{
"iss": "https://securetoken.google.com/...",
"aud": "xxx",
"auth_time": 1661813351,
"user_id": "xxx",
"sub": "xxx",
"iat": 1661813351,
"exp": 1661816951,
"email": "xxx",
"email_verified": false,
"firebase": {
"identities": {
"email": [
"xxx"
]
},
"sign_in_provider": "password"
}
}
Also, I tried setting both customClaims and sessionClaims independently. Neither show up on the user object, nor are the custom claims saved for the user.
One more update. I tried setting the display name in beforeCreate and that worked.
return {
customClaims: {
roles: 'pie',
},
displayName: 'pie',
};
// RESULT:
{
"name": "pie",
"iss": "https://securetoken.google.com/...",
"aud": "xxx",
"auth_time": 1661816987,
"user_id": "xxx",
"sub": "xxx",
"iat": 1661816987,
"exp": 1661820587,
"email": "xxx",
"email_verified": false,
"firebase": {
"identities": {
"email": [
"xxx"
]
},
"sign_in_provider": "password"
}
}
From Darwin in comments:
Hi #Gremash , there's an open github issue regarding that. See sessionClaims content not getting added to the decoded token. Also, there's a fix that has been recently merged regarding this issue.
I have a user's refresh token with these scopes:
'https://www.googleapis.com/auth/analytics https://www.googleapis.com/auth/analytics.edit https://www.googleapis.com/auth/analytics.readonly https://www.googleapis.com/auth/analytics.manage.users’’
The user has a few accounts and a forbidden error occurs for XXXX2 account when I try to give access to an account to another user(you can see at the below code and account list json). I checked this account's effective permission field, I see that it is an empty array as you can see below as well.
In my opinion, this error reason is that this user does not have ‘manage_users’ permissions for this account(XXXX2), so this is an expected error, isn't it?
PS: I can link XXXX1 properly with the below code.
My code :
linkAccount = self.service.management().accountUserLinks().insert(
accountId=accountId,
body={
'permissions': {
'local': [
'EDIT',
'MANAGE_USERS'
]
},
'userRef': {
'email': email}}).execute()
User Account list :
{
"id": "XXXX1",
"kind": "analytics#account",
"selfLink": "https://www.googleapis.com/analytics/v3/management/accounts/XXXX1”,
"name": "XXXX1",
"permissions": {
"effective": [
"COLLABORATE",
"EDIT",
"MANAGE_USERS",
"READ_AND_ANALYZE"
]
},
"created": "2014-02-17T17:52:10.911Z",
"updated": "2019-06-04T16:06:12.717Z",
"childLink": {
"type": "analytics#webproperties",
"href": "https://www.googleapis.com/analytics/v3/management/accounts/XXXX1/webproperties"
}
},
{
"id": "XXXX2”,
"kind": "analytics#account",
"selfLink": "https://www.googleapis.com/analytics/v3/management/accounts/XXXX2”,
"name": "XXXX2",
"permissions": {
"effective": []
},
"created": "2015-07-02T19:11:16.307Z",
"updated": "2019-03-05T21:16:19.552Z",
"childLink": {
"type": "analytics#webproperties",
"href": "https://www.googleapis.com/analytics/v3/management/accounts/XXXX2/webproperties"
}
}
Error Code :
<HttpError 403 when requesting https://analytics.googleapis.com/analytics/v3/management/accounts/XXXXX/entityUserLinks?alt=json returned "User does not have permission to perform this operation.". Details: "User does not have permission to perform this operation."
I would check what access the currently authenticated user has to the account in question.
If the user you are authenticated with only has read access. To the Analytica account there not going to be able to give your application the ability to add another user.
I am working with Meteor User accounts to create users. I have implemented two ways of creating users.
By using accounts-password to create (default one ).
OAuth Services (accounts-google and accounts-facebook)
A user account generated with accounts-password have the document shown below
{
"_id": "DQnDpEag2kPevSdJY",
"createdAt": "2015-12-10T22:34:17.610Z",
"services": {
"password": {
"bcrypt": "XXX"
},
"resume": {
"loginTokens": [
{
"when": "2015-12-10T22:34:17.615Z",
"hashedToken": "XXX"
}
]
}
},
-----
----
}
Where as a user account generated with accounts-google or account-facebook have the document shown below.
{
"_id": "Ap85ac4r6Xe3paeAh",
"createdAt": "2015-12-10T22:29:46.854Z",
"services": {
"facebook": {
"accessToken": "XXX",
"expiresAt": 1454970581716,
"id": "XXX",
"email": "myname#gmail.com",
"name": "Ada Lovelace",
"first_name": "Ada",
"last_name": "Lovelace",
"link": "https://www.facebook.com/app_scoped_user_id/XXX/",
"gender": "female",
"locale": "en_US",
"age_range": {
"min": 21
}
},
---
---
---
Now the real issue is, Although the email address used is same for both accounts-password and accounts-google (in my case email is myname#gmail.com), two different user accounts are being created.
I am looking for solution Something like below. (Note: Services has both "Password" and "Facebook" sections under single account)
{
"_id": "DQnDpEag2kPevSdJY",
"createdAt": "2015-12-10T22:34:17.610Z",
"services": {
"password": {
"bcrypt": "XXX"
},
"facebook": {
"accessToken": "XXX",
"expiresAt": 1454970581716,
"id": "XXX",
"email": "myname#gmail.com",
"name": "Ada Lovelace",
"first_name": "Ada",
"last_name": "Lovelace",
"link": "https://www.facebook.com/app_scoped_user_id/XXX/",
"gender": "female",
"locale": "en_US",
"age_range": {
"min": 21
}
},
},
-----
----
}
Is there a way where only one account is being generated in both cases, means if a user is already existed and the same is trying with OAuth service, first account should be used to accommodate the service ?
link-accounts package is the recommended way to allow users to add additional services to their account.
You can use Accounts.setPassword on the server in order to generate a proper bcrypt hash for the accounts:
Accounts.setPassword('Ap85ac4r6Xe3paeAh', 'the-new-password')
which will result in
{
"_id": "Ap85ac4r6Xe3paeAh",
"createdAt": "2015-12-10T22:29:46.854Z",
"services": {
"password": {
"bcrypt": "$2b$10$nzHCivxVqxbuFBBPWewPPu.r5x7OR5gJB8PIklU4OoU.WK0MT8jt2"
},
"facebook": {
"accessToken": "XXX",
"expiresAt": 1454970581716,
"id": "XXX",
"email": "myname#gmail.com",
"name": "Ada Lovelace",
"first_name": "Ada",
"last_name": "Lovelace",
"link": "https://www.facebook.com/app_scoped_user_id/XXX/",
"gender": "female",
"locale": "en_US",
"age_range": {
"min": 21
}
}
}
}
I have solved the above issue with a hack.
In imports/startup/server/accounts.js I have added the below validation logic which always validates the newly created account.
The idea is, this process checks if user is already existed in database. If the user exists, further checks if its created from accounts-password or accounts-google/facebook .
Based on the existing type modify the existing fields with new fields and throw an error with a fancy message (This actually prevents the new account to be created).
Accounts.validateNewUser(function (user) {
// first check what is the newly creating service
var service =
user.services.google || user.services.facebook || user.services.password;
if (!service) return true;
var existingUser = null;
// due to some issues both `Meteor.users.findOne(email)` as well `Account.findUserByEmail(email)` methods have been used to find the existing user status
if (user.services.password) {
var email = user.emails[0].address;
existingUser = Meteor.users.findOne({
$or: [
{ "registered_emails[0].address": email },
{ "services.google.email": email },
{ "services.facebook.email": email },
],
});
} else {
var email = service.email;
//console.log(" retrieved email ", email);
existingUser = Accounts.findUserByEmail(email);
}
//console.log(" existingUser : ", existingUser);
if (!existingUser) return true;
if (user.services.google) {
Meteor.users.update(
{ _id: existingUser._id },
{
$set: {
profile: user.profile,
"services.google": user.services.google,
},
}
);
} else if (user.services.facebook) {
Meteor.users.update(
{ _id: existingUser._id },
{
$set: {
profile: user.profile,
"services.facebook": user.services.facebook,
},
}
);
} else {
Meteor.users.update(
{ _id: existingUser._id },
{
$set: {
profile: user.profile,
"services.password": user.services.password,
"services.email": user.services.email,
emails: user.emails,
},
}
);
}
throw new Meteor.Error(
205,
"Merged with your existing Social Login accounts now. Try refresh the page and sign in again. That should work !!"
);
});
I'm using JAGQL to build a JSON API compatible express server. My database behind it is MongoDB (jsonapi-store-mongodb). I posted my question here as well: https://github.com/holidayextras/jsonapi-store-mongodb/issues/59
According to the JAGQL documentation, https://jagql.github.io/pages/project_setup/resources.html#generateid,
I am told that
generateId
By default, the server autogenerates a UUID for resources which are created without specifying an ID. To disable this behavior (for example, if the database generates an ID by auto-incrementing), set generateId to false. If the resource's ID is not a UUID, it is also necessary to specify an id attribute with the correct type. See /examples/resorces/autoincrement.js for an example of such a resource.
But when I send a POST request to one of my resources, I get this:
"jsonapi": {
"version": "1.0"
},
"meta": {},
"links": {
"self": "/myresource"
},
"errors": [
{
"status": "403",
"code": "EFORBIDDEN",
"title": "Param validation failed",
"detail": [
{
"message": "\"id\" is required",
"path": [
"id"
],
"type": "any.required",
"context": {
"key": "id",
"label": "id"
}
}
]
}
]
What am I missing?
See here for more details: https://github.com/jagql/framework/issues/106
In your resource definition, you want to add primaryKey: 'uuid':
{
resource: 'things',
handlers,
primaryKey: 'uuid',
attributes: {
...
}
}
I am trying to understand direct mapping on OpenStack. I want to map a user to a domain other than Federated domain. But I always get user mapped to Federated domain. Here follows the link for direct mapping that I am using:
https://specs.openstack.org/openstack/keystone-specs/specs/kilo/federated-direct-user-mapping.html
Here follows the rule for mapping that I am using:
[
{
"local": [
{
"user": {
"name": "{0}",
"domain": {"name": "Default"}
}
},
{
"group": {
"id": "GROUP_ID"
}
}
],
"remote": [
{
"type": "HTTP_OIDC_SUB"
}
]
}
]
I have configured OpenID connect Idp for federation.
Could someone help me how I can do direct mapping to map a federated user to a domain other than Federated ?
the only way I've been able to get it to not be in the 'Federate' domain, is to force the user to be of type local, but then they need to exist in the backend (SQL/LDAP).
[
{
"local": [
{
"user": {
"name": "{0}",
"type": "local",
"domain": {"name": "Default"}
}
},
{
"group": {
"id": "GROUP_ID"
}
}
],
"remote": [
{
"type": "HTTP_OIDC_SUB"
}
]
}
]
The following bit of code in keystone is the culprit for doing this:
if user_type is None:
user_type = user['type'] = UserType.EPHEMERAL
if user_type == UserType.EPHEMERAL:
user['domain'] = {
'id': CONF.federation.federated_domain_name
}
It Basically overrides the domain to a pre-configured domain if your user doesn't have a type, or is ephemeral.