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.
Related
The session is stored in cookies. When I use this {session ? `${session.user.email}` : ''} it keeps saying undefined.
This is my JSON object:
{
"user": {
"token": {
"responseCode": 0,
"responseMessage": "success",
"data": {
"id": 6,
"userId": "SYS-9d502c43-9ef3-432d-ab93-be0b8236cdff",
"fullName": "yeshewas string",
"username": "yes#yes.com",
"userRole": "Super User",
"permissions": []
}
}
},
"expires": "2022-06-24T06:14:39.011Z"
}
Try to change your jwt callback like this:
callbacks: {
jwt: async ({ token, user }) => {
user && (token.user = user.user)
return token;
},
},
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.
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 braintree for payment and I have done this.
gateway.customer.create({
firstName: "Sachin",
lastName: "Shah",
company: "Qwerty",
email: "Qwerty#example.com",
phone: "114.555.1234",
fax: "614.555.1234",
website: "www.example.com",
}, function (err, result) {
if (err) {
res.send({code:0, status:'Error', message:err});
}else{
res.send({code:1, status:'Success', data: result});
}
});
I followed it's official doc and they show that when req is success I'll get token but I'm gtting result.customer.paymentMethods[]
Response
{
"code": 1,
"status": "Success",
"data": {
"customer": {
"id": "569549779",
"merchantId": "XXXXXXXXXXXXXXXXX",
"firstName": "Sachin",
"lastName": "Shah",
"company": "Qwerty",
"email": "Qwerty#example.com",
"phone": "114.555.1234",
"fax": "614.555.1234",
"website": "www.example.com",
"createdAt": "2019-10-10T05:13:42Z",
"updatedAt": "2019-10-10T05:13:42Z",
"customFields": "",
"globalId": "XXXXXXXXXXXXXXXXX",
"creditCards": [],
"addresses": [],
"paymentMethods": []
},
"success": true
}
}
Expected Output
I need to get paymentMethodToken for further API calls.
Full disclosure: I work at Braintree. If you have any further questions, feel free to contact support.
You're currently creating a customer without a payment method. You'll need to create the customer with a payment method to retrieve a paymentMethodToken.
I'm doing the sign up integration with LinkedIn for a personal application and i have a big trouble.
We need the vanityName in order to make people visible via linkedin.
How we can redirect them to profiles without using vanityName? I tried with ID unsuccessfully.
I use this endpoint with r_liteprofile scope:
https://api.linkedin.com/v2/me?projection=(id,firstName,lastName,vanityName,profilePicture(displayImage~:playableStreams))
and it returns me:
{
"firstName": {
"localized": {
"es_ES": "XXX"
},
"preferredLocale": {
"country": "ES",
"language": "es"
}
},
"lastName": {
"localized": {
"es_ES": "XXX"
},
"preferredLocale": {
"country": "ES",
"language": "es"
}
},
"profilePicture": {
"displayImage": "XXX",
"displayImage~": {
"elements": [...]
}
},
"id": "AX-Wv6r0Ku"
}
You can use the me endpoint of the Profile API in order to Retrieve Current Member's Profile. As example:
https://api.linkedin.com/v2/me?oauth2_access_token=<user_access_token>
With the result:
{
"firstName":{
"localized":{
"en_US":"Bob"
},
"preferredLocale":{
"country":"US",
"language":"en"
}
},
"localizedFirstName": "Bob",
"headline":{
"localized":{
"en_US":"API Enthusiast at LinkedIn"
},
"preferredLocale":{
"country":"US",
"language":"en"
}
},
"localizedHeadline": "API Enthusiast at LinkedIn",
"vanityName": "bsmith",
"id":"yrZCpj2Z12",
"lastName":{
"localized":{
"en_US":"Smith"
},
"preferredLocale":{
"country":"US",
"language":"en"
}
},
"localizedLastName": "Smith",
"profilePicture": {
"displayImage": "urn:li:digitalmediaAsset:C4D00AAAAbBCDEFGhiJ"
}
}
If you want to retrieve only the vanityName field you can use the Field Projections as follow:
https://api.linkedin.com/v2/me?projection=(vanityName)&oauth2_access_token
With the result:
{
"vanityName": "bsmith"
}
Hope this help