I have the following codes in the backend of my web app made with node.js:
admin.auth().verifyIdToken(req.body.token).then((user) => {
admin.auth().setCustomUserClaims(user.uid, {
identity: "ns",
approved: false,
reviewed: false
}).then(() => {
console.log(user);
})
})
Although, I am logging the user token after the custom user claims have been set. The custom user claims approved and reviewed do not appear in my token. I think the claims are set correctly, but why they are not appearing in my terminal log?
The token that you verifed contains a copy of the claims at the time the token was generated. If you update the claims, that doesn't change what was delivered inside the payload of the token. If you want to see the updated claims inside a token, the client will have to refresh the token and provide a new one to the backend for another verification.
Related
I was trying to acquire token from our Microsoft tenant. I have no knowledge about the Azure AD or whatsoever, because I only tasked to develop front end for our Microsoft Dynamics App in React. I only got some of the credential like tenant id, client id, client secret and resource.
I used MSAL Node library and function ConfidentialClientApplication() to acquire the token
But when I check it in the Ms. Edge's console log it throw an error
{"errorCode":"endpoints_resolution_error","errorMessage":"Error: could
not resolve endpoints. Please check network and try again. Detail:
ClientAuthError: openid_config_error: Could not retrieve endpoints.
Check your authority and verify the .well-known/openid-configuration
endpoint returns the required endpoints. Attempted to retrieve
endpoints from: verify
url","subError":"","name":"ClientAuthError","correlationId":""}
When I click the veryfy url (Cannot show you the url because it might contain sensitive information)
It shows all the metadata of the open id so I thought maybe it's normal.
But why is the error endpoints_resolution_error throwed when everything is normal?
Here is some snapshot of my code
const config = {
auth: {
clientId: clientID
authority: "https://login.microsoftonline.com/{tenantID}/",
clientSecret: clientSecret,
knownAuthorities: ["login.microsoftonline.com"],
protocolMode: "OIDC"
}
};
// Create msal application object
const cca = new msal.ConfidentialClientApplication(config);
// With client credentials flows permissions need to be granted in the portal by a tenant administrator.
// The scope is always in the format "<resource>/.default"
const clientCredentialRequest = {
scopes: ["resource/.default"], // replace with your resource
};
cca.acquireTokenByClientCredential(clientCredentialRequest).then((response) => {
console.log("Response: ", response);
}).catch((error) => {
console.log(JSON.stringify(error));
});
I've tried changing the authority and the protocol mode several times, but same result
Details
If you use the function createCustomToken to sign in the user and setting his custom claim, updating the custom claims later using the function setCustomUserClaims will not update the claims even after forcing the idToken to refresh using the function firebase.auth().currentUser.getIdTokenResult(true)
How to reproduce?
Sign in the user on firebase using a custom token generated with the function createCustomToken including the custom claims
firebase.auth().createCustomToken(uid, {myClaim: "test"}).then((customToken) => console.log(customToken))
Sign in the user on the frontend using the custom token
// copy paste the customToken manually for testing
firebase.auth().signInWithCustomToken(customToken)
Update the claim on the backend using setCustomUserClaims
firebase.auth().setCustomUserClaims(uid, {myClaim: "updateTest"})
Refresh the idToken on the frontEnd and log the custom claims
firebase.auth().currentUser
.getIdTokenResult(/*force refresh*/ true)
.then((idTokenResult) => {
console.log(`custom claims`, idTokenResult.claims)
})
You should see that the claim is still { myClaim: "test" } instead of { myClaim: "updateTest" }
Edit: This is actually an intended behavior. The claims set with createCustomToken have a higher priority. The doc mentions it here https://firebase.google.com/docs/auth/admin/custom-claims#set_and_validate_custom_user_claims_via_the_admin_sdk
Setting the custom claims separately at sign in instead of using the function createCustomToken to set them will allow you to edit these claims later.
Working code:
firestore
.doc(`users/${uid}`)
.get()
.then((clientSnapshot) => {
// give user the claims he has
const { permissions = {} } = clientSnapshot.data()
// use setCustomUserClaims to set the claims
return auth.setCustomUserClaims(uid, { permissions })
})
// generate the custom token
// ⚠️ don't use createCustomToken to set permission as you won't be able to update them
.then(() => auth.createCustomToken(uid))
.then((customToken) => {
// send the custom token to the frontend to sign the user in
return res.status(200).json({ customToken })
})
On my website, I am asking for google calendar access. I can edit the user calendar but, I don't want to ask for user permission, again and again, so once the user authorized and give access to google calendar, I can edit it anytime until the user revokes the access. Should I implement it on the frontend or the backend and how? I checked few answers where they mention we can use a service account but, it is not clear how can I edit or read the individual user's calendar events and how can I remove it once the user revokes access. This question was deleted because code was missing so adding code below.
I tried this so once user login I get access token and I am using it
window.gapi.load("client:auth2", () => {
window.gapi.client.setApiKey("api_key");
window.gapi.client.load("https://content.googleapis.com/discovery/v1/apis/calendar/v3/rest")
.then(() => {
window.gapi.auth.setToken({ access_token: access_token })
window.gapi.client.calendar.events.insert({
"calendarId": "id",
'resource': event
}).then((res) => {
console.log("calendar data res "+JSON.stringify(res))
}).catch(err => console.log("error getting calendar data "+JSON.stringify(err)))
}).catch(err => console.error("Error loading GAPI client for API", err) )
})
but once access token expires how can I get a new access token( I don't want to show login popup to the user again and again. I want to know how can I do it using refresh token on client-side).
You can't get a refresh token on the client-side without exposing your secret key to the public.
You can create an endpoint that accepts oAuth code and return the token, save the refresh token for later. You set up a corn job that checks for expired token and refreshes them.
Every time the user accesses your app, you grab a fresh token from the server and proceed to work normally.
As per Google guidelines. You do POST to https://oauth2.googleapis.com/token. Assuming your server-side stack is in Node.js, you do something like this using an HTTP client like Axios:
const Axios = require('axios');
const Qs = require('querystring');
const GOOGLE_CLIENT_ID = 'abc';
const GOOGLE_CLIENT_SECRET = '123';
let refreshToken = getFromDataBase(); // should be stored in database
Axios.post('https://oauth2.googleapis.com/token', Qs.stringify({
client_id: GOOGLE_CLIENT_ID,
client_secret: GOOGLE_CLIENT_SECRET,
refresh_token: refreshToken,
grant_type: 'refresh_token'
}), {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
}
})
.then(({ data }) => console.log(data.access_token)) // new token that expires in ~1 hour
.catch(console.log)
Firstly, do you (a) want to update the calendar when the user is not logged in, for example in response to an external event? ... OR ... do you (b) only want to update the calendar from within a browser session?
If (a), then you need to ask the user for offline access which will give you a Refresh Token , which you can securely store on a server and use whenever you need to. (Forget all about Service Accounts).
If (b), then you need the following pieces of information :-
When the access token expires, request access again, but add the flag prompt=none. This will give you a fresh Access Token without the user seeing any UX.
Do this in a hidden iframe so that it is happening in the background and is invisible to the user. Your iframe will therefore always have an up to date Access Token which it can share with your app via localStorage or postMessage.
Action:
- signInWithPhoneNumber(NUMBER NOT IN DB, recaptchaVerifier)
Expected Behavior:
- Since number not in DB, it should not log me in.
Current Behavior:
- If the number does not exist in DB, it CREATES a new user after going through recaptcha + sms verification. WHY?
Code:
function loginWithSMS(phoneNumber) {
firebase.auth().useDeviceLanguage();
//#ts-ignore
window.recaptchaVerifier = new firebase.auth.RecaptchaVerifier("recaptcha-container");
//#ts-ignore
window.recaptchaVerifier.render().then(function (widgetId) {
//#ts-ignore
window.recaptchaWidgetId = widgetId;
});
// #ts-ignore
firebase
.signInWithPhoneNumber(phoneNumber, window.recaptchaVerifier)
.then((confirmationResult) => {
console.log("Login success", confirmationResult);
window.recaptchaVerifier.clear();
// SMS sent. Prompt user to type the code from the message, then sign the
// user in with confirmationResult.confirm(code).
const verificationCode = window.prompt(
"Please enter the verification " + "code that was sent to your mobile device."
);
return confirmationResult.confirm(verificationCode);
})
.catch((error) => {
console.error(error);
// Error; SMS not sent
// Handle Errors Here
window.recaptchaVerifier.clear();
return Promise.reject(error);
});
}
This is just how the API is defined: by sending a text to the number, Firebase allows the user to verify that they have access to that phone number. If they do, they're allowed to sign in.
This is the same for the email+password provider in Firebase Authentication. Calling firebase.auth().createUserWithEmailAndPassword(email, password) creates the user, even if they didn't exist yet. And while your code may not call this API, any developer can take the Firebase configuration data from your app and call the API themselves.
Most often when developers are asking about this they're confusing authentication with authorization.
When you authenticate, you are proving that you are you. So in the examples above, that you have access to a certain phone number, or that you know the email+password combination of the account.
Based on knowing who the user is, the application then authorizes that user to perform certain actions or to access certain data.
For example, if you're using Realtime Database, Cloud Storage, or Cloud Firestore, you can control access with Firebase's server-side security rules.
If you have a different back-end, you'd control it there by checking the information in the ID token of the user (which you get from Firebase Authentication) against some set of authorization rules for your application.
Also see:
Prevent user account creation with sign in by email in firestore (similar question, but then for passwordless email signin)
How to disable Signup in Firebase 3.x
How does the firebase authentication and realtime application database secure itself?
I have created node.js backend. On Login i am sending a jwt token. For user experience i don't want them to re-login but instead get their tokens refreshed, which i have set to expire in 4 hours.
However i am not getting a good lead on how to do this effectively. My idea is to provide a button in client side, by clicking on which user can get their tokens refreshed. Assuming a rest call that i can make from client side, i need help in its implementation. Appreciate it.
if (response) {
bcrypt.compare(req.body.password, response.password, (error, result) => {
if (result) {
const token = jwt.sign(
{
email: response.email,
userId: response._id
},
process.env.JWT_KEY,
{
expiresIn: '4h'
});
return res.status(200).json({
message: 'Auth Successful! User Found. ',
token
})
} else {
return res.status(404).json({
message: 'Auth Failed! User Not found'
})
}
}
You would need two tokens:
Refresh Token (will be saved in db)
Access Token (your JWT which will expire quickly e.g. 10 mins)
Refresh token typically does not expire quickly. However, there may be a challenge on how to secure the refresh token.
you also need to change the refresh token in the database every time the user refreshed their token / logs in.
You also need to store expiry_date of your access token (you can make it a response from your login api).
Then, in your front-end, you can store those tokens in localStorage / sessionStorage depending on your security requirements.
Then, each API call would check the expiry date that you've set. And if it reaches a certain threshold (e.g. 5 mins before expiry_date), you'd call the refresh token API.
This is a method that I've used. However, it may not considered as a best practice.