Custom provider claims in `additionalUserInfo.profile` are not available via firebase admin? - firebase

I'm following firebase/identity toolkit docs for a SAML identity provider. Upon successful login, the redirect result contains attributes derived from the provider:
provider = new firebase.auth.SAMLAuthProvider('saml.test-provider');
firebase.auth().signInWithRedirect(provider);
...
firebase.auth().getRedirectResult().then(function(result) {
if (result.credential) {
console.log(result.additionalUserInfo.profile) // Custom provider claims, e.g., {"provider-foo":"bar"}
}
}
From the docs, the same values are also available via
result.user.getIdTokenResult().idTokenResult.claims.firebase.sign_in_attributes
firebase.sign_in_attributes
These same attributes don't seem to be stored anywhere accessible in the firebase_admin SDK:
from firebase_admin import auth
user = auth.get_user(uid)
print(user.custom_claims) # => None ... *provider* claims aren't here
print(user.provider_data[0]) # => has `federatedId` and some others, but still no custom provider claims
Is there any way to get this info in the admin SDK? Any way to tell if it's even saved by Firebase? Do I need to capture it in firestore (and wouldn't that be risky since the user could fake claims coming from the provider?)
Thanks!

the additional SAML attributes are only persisted in the token claims accessible via:
result.user.getIdTokenResult().idTokenResult.claims.firebase.sign_in_attributes
They are not stored on the user record. Identity Platform/Firebase Auth does not persist additional user info in storage for privacy reasons.
However, you can always store the claims you need on the record via the Admin SDK.
You would send the ID token to your server, verify it, parse the claims you need and set them on the user record.
Here is sample via the Node.js Admin SDK.
app.post('/saveUserClaims', (req, res) => {
// Get the ID token passed.
const idToken = req.body.idToken;
admin.auth().verifyIdToken(idToken)
.then(function(decodedToken) {
const uid = decodedToken.uid;
// ...
const samlClaims = decodedToken.firebase.sign_in_attributes;
// You would filter the claims as there could be too many.
// You can also save these claims in your database, etc.
return admin.auth().setCustomUserClaims(uid, samlClaims)
.then(() => {
res.status(200).end();
});
}).catch(function(error) {
// Handle error
});
});
That said, in general there is no need to save these claims as they will always be available in the ID token and you can access them from your security rules or when you pass the ID token to your server for validation. This is a better way to do this as you don't run into synchronization issue where your DB is out of sync with the user's attributes.

Related

How to change the email and the password of the user in dart firebase

I want to change the email and the password of the user in the Firestore and authentication so the user can log in with the updated email and password
I want to change the email and the password of the user
I understand that you don't want these changes to be done by the user itself, but by another user, like an admin.
In this case you cannot use the Dart firebase_auth library because the updateEmail() and updatePassword() methods must be called by the user itself.
On the other hand, with the Firebase Admin SDK you can modify an existing user's data (without authenticating as this user). To use the Admin SDK you need to do that from a privileged environment, like a server you manage or via Cloud Functions.
The following Node.js code can be used in a Cloud Function:
const uid = .... // Specify the user's uid
getAuth()
.updateUser(uid, {
email: 'modifiedUser#example.com',
password: 'newPassword',
})
.then((userRecord) => {
// See the UserRecord reference doc for the contents of userRecord.
console.log('Successfully updated user', userRecord.toJSON());
})
.catch((error) => {
console.log('Error updating user:', error);
});

Generate undo email change link in firebase cloud functions

How can I generate a link to undo the email change in firebase cloud functions?
So when a user changes their email address, I want to generate a link to include in an automated email for them to click to undo this email change. Firebase sends an email when an email changes, but I want to be able to do that myself with my own code.
Currently, I can find that there are ways you can generate a link to change the user password, verify email, etc. However, I cannot find a method that I can use to generate a link to undo email change when the user changes their email.
When a user changes the email, you can store a document in Firestore containing their old email, a token and some metadata if you need to. That being said, you should update user's email from a Cloud function or your server using the Admin SDK only as there are no triggers on E-Mail change.
import jwt from "jsonwebtoken"
import {v4} from "uuid"
exports.changeEmail = functions.https.onCall(async (data, context) => {
const {newEmail} = data;
const {uid} = context.auth;
// change user's email
// send an email to verify new email is required
// generate a JWT
const token = jwt.sign({ uid, eventId: v4() }, 'jwt_signing_secret', { expiresIn: '24h' });
// add a document in Firestore containing details about this event
await admin.firestore().collection("emailChanges").doc(eventId).set({
uid, changedAt: Date.now()
})
const undoURL = `https://[YOUR_DOMAIN]/revert-email-change?token=${token}`
// E-Mail this URL to user
// Terminate this function
})
Replace [YOUR_DOMAIN] will the URL of your website. Once the user visits /revert-change-email email page of your website, call another function that verifies this token.
exports.revertEmailChange = functions.https.onCall((data, context) => {
// pass the token from frontend by checking URL params
const {token} = data
// Verify the token
const decoded = jwt.verify(token, 'jwt_signing_secret');
console.log(decoded)
const {uid, eventId} = decoded
// token is valid
// read the Firestore document using stateId and check old email
const snap = await admin.firestore().collection("emailChanges").doc(eventId).get()
if (!snap.exists) return {error: "Invalid Token"}
const {email} = snap.data()
// use updateUser() method to change email back
// delete that document from Firestore
return {data: "Email changed back successfully"}
});
You can change the lifespan of JWT token i.e. how long the URL should be valid. You can read more about JWT at jwt.io. The additional eventId token is just to prevent that JWT token so it cannot be reused.
When writing Cloud Functions for Firebase, one uses the Admin Node.js SDK.
AFAIK it is not possible, with this Admin SDK, to generate an email action link to undo an email change, as we can we can do, for example, for email verification with the generateEmailVerificationLink() method.
You will need to build your own mechanism yourself. You'll probably have to save somewhere (e.g. in Firestore) the previous email and expose an HTTP endpoint to trigger the action (HTTPS Cloud Function? Call to the Firestore REST API?). In any case you'll have to check the identity of the calling user (by either checking the Firebase ID token as a Bearer token in the Authorization header of the HTTP request or via a dedicated Firestore Security Rule).
There isn't enough details in your question to understand the exact flow of your complete use case (i.e. from the request to change email up to the action of undoing an effective change) and propose a sensible approach.

How to always get latest Firebase Auth Token

Currently I am using this code to get the latest auth token for firebase, which I use in a header with Apollo or URQL to query something else to be validated...
async getToken(): Promise<any> {
return await new Promise((resolve: any, reject: any) => {
this.afa.onAuthStateChanged((user: any) => {
if (user) {
user.getIdToken(true).then((token: string) => {
resolve(token);
}, (e: any) => reject(e));
}
});
});
}
I am use getIdToken(true) to always make sure I get a valid token since the token expires after one hour and the custom claims could be updated at some point.
However, my code gets a new token every time, when really I only need to get a new token when the old one is expired, or there is new information in the token's custom claim.
Should I be using some for of onIdTokenChanged() ? Does firebase store all this automatically in the firebase localstoreage db (IndexedDB), or should I be using some form of localstorage and calculating the expiry time ?
Basically, what is the best way to minimize the number of refreshes to the token to speed up my app instead of getting a new token every time?
Thanks,
J
Unless you are using a custom solution with the REST API, the firebase client modules will automatically refresh the auth token with the refresh token when the old one expires.
As for updating the custom claims, you will have to communicate with the client app through some means such as a server response if you invoke a cloud function or a realtime database listener that the user is subscribed to if you are updating it based on 'external' conditions.

Firebase Web Admin

First of all, I am using nodejs for the backend. I use firebase hosting and firebase functions to deploy an express() app.
What I am trying to achieve is to make an admin website, which is connected to Firebase. so I have a route /admin/ like this:
adminApp.get("/", (request, response) => {
return response.redirect("/admin/login");
});
Here I basically want to check if a current user is logged in - or not.
I know firebase supports client side authentication using:
firebase.auth().onAuthStateChanged(user => {
if (user) {
} else {
}
});
And using
function login() {
var userEmail = document.getElementById("email").value;
var userPass = document.getElementById("password").value;
firebase.auth().signInWithEmailAndPassword(userEmail, userPass).catch(function(error) {
var errorCode = error.code;
var errorMessage = error.message;
if (error) {
document.getElementById('loginError').innerHTML = `Error signing in to firebase`;
}
});
}
However image this case:
Someone (not an admin) is visiting /admin/some_secret_website/ which he obviously does not have access to.
If I rely on client side authentication, it first loads the entire website and the scripts and then notices - hey I am not authenticated, let me redirect to /login. By then however anyone knows the source code of an admin page.
I'd rather have something like:
adminApp.get("/admin/some_secret_website", (request, response) => {
if (request.user) {
// user is authenticated we can check if the user is an admin and give access to the admin page
}
});
I know that you can get the user's token and validate that token using the AdminSDK, but the token must be send by the client code, meaning the website was already loaded.
I came across Authorized HTTPS Endpoint by firebase, but it only allows a middleware when using a bearer token.
Does anybody know how I can maintain a server side user object to not even return admin html to the browser but only allow access to admins?
Like Doug indicated, the way your admin website/webapp would function with Firebase Cloud Functions (which is effectively a Nodejs server) is that you get the request, then use the headers token to authenticate them against Firebase Auth. See this answer for a code snippet on this.
In your case, I'm thinking you would create a custom claim for an "administrator" group and use that to determine whether to send a pug templated page as a response upon authentication. As far as Authorization, your db rules will determine what said user can CRUD.

Where to find auth.token data, inside firebase objects

I am using signInWithCustomToken, after authentication I can not find where is stored my custom claims data which I have set in the server side(createCustomToken).
I can see them in firebase rules via auth.token, but how can I access them through firebase objects from within my javascript code.
The information in the token is not automatically available to your application code. But it is embedded in the token, so you can decode it yourself:
function parseJwt (token) {
var base64Url = token.split('.')[1];
var base64 = base64Url.replace('-', '+').replace('_', '/');
return JSON.parse(window.atob(base64));
};
var user = firebase.auth().currentUser
user.getToken().then(data => {
console.log(parseJwt(data));
});
The function to parse the JWT comes from this question: How to decode jwt token in javascript
You'll note that it doesn't verify that the ID token is valid. That seems fine to me in client-side code, since the information will be used by the user themselves anyway. But if you do want to verify the token, you'll have to use a more involved method.

Resources