Firebase is back at Google I/O on May 14! Register now.
Firebase Authentication sessions are long lived. Every time a user signs in, theuser credentials are sent to the Firebase Authentication backend and exchanged for aFirebase ID token (a JWT) and refresh token. Firebase ID tokens are short livedand last for an hour; the refresh token can be used to retrieve new ID tokens.Refresh tokens expire only when one of the following occurs:
- The user is deleted
- The user is disabled
- A major account change is detected for the user. This includes events like password or email address updates.
The Firebase Admin SDK provides the ability to revoke refresh tokens for aspecified user. In addition, an API to check for ID token revocation is alsomade available. With these capabilities, you have more control over usersessions. The SDK provides the ability to add restrictions to prevent sessionsfrom being used in suspicious circ*mstances, as well as a mechanism for recoveryfrom potential token theft.
Revoke refresh tokens
You might revoke a user's existing refresh token when a user reports a lost orstolen device. Similarly, if you discover a general vulnerability or suspect awide-scale leak of active tokens, you can use thelistUsers
API to look up all users and revoke their tokens for the specified project.
Password resets also revoke a user's existing tokens; however, theFirebase Authentication backend handles the revocation automatically in that case.On revocation, the user is signed out and prompted to reauthenticate.
Here is an example implementation that uses the Admin SDK to revoke the refreshtoken of a given user. To initialize the Admin SDK follow the instructions onthe setup page.
Node.js
// Revoke all refresh tokens for a specified user for whatever reason.// Retrieve the timestamp of the revocation, in seconds since the epoch.getAuth() .revokeRefreshTokens(uid) .then(() => { return getAuth().getUser(uid); }) .then((userRecord) => { return new Date(userRecord.tokensValidAfterTime).getTime() / 1000; }) .then((timestamp) => { console.log(`Tokens revoked at: ${timestamp}`); });
Java
FirebaseAuth.getInstance().revokeRefreshTokens(uid);UserRecord user = FirebaseAuth.getInstance().getUser(uid);// Convert to seconds as the auth_time in the token claims is in seconds too.long revocationSecond = user.getTokensValidAfterTimestamp() / 1000;System.out.println("Tokens revoked at: " + revocationSecond);
Python
# Revoke tokens on the backend.auth.revoke_refresh_tokens(uid)user = auth.get_user(uid)# Convert to seconds as the auth_time in the token claims is in seconds.revocation_second = user.tokens_valid_after_timestamp / 1000print('Tokens revoked at: {0}'.format(revocation_second))
Go
client, err := app.Auth(ctx)if err != nil {log.Fatalf("error getting Auth client: %v\n", err)}if err := client.RevokeRefreshTokens(ctx, uid); err != nil {log.Fatalf("error revoking tokens for user: %v, %v\n", uid, err)}// accessing the user's TokenValidAfteru, err := client.GetUser(ctx, uid)if err != nil {log.Fatalf("error getting user %s: %v\n", uid, err)}timestamp := u.TokensValidAfterMillis / 1000log.Printf("the refresh tokens were revoked at: %d (UTC seconds) ", timestamp)
C#
await FirebaseAuth.DefaultInstance.RevokeRefreshTokensAsync(uid);var user = await FirebaseAuth.DefaultInstance.GetUserAsync(uid);Console.WriteLine("Tokens revoked at: " + user.TokensValidAfterTimestamp);
Detect ID token revocation
Because Firebase ID tokens are stateless JWTs, you can determine a token hasbeen revoked only by requesting the token's status from the Firebase Authenticationbackend. For this reason, performing this check on your server is an expensiveoperation, requiring an extra network round trip. You can avoid making thisnetwork request by setting up Firebase Security Rules that check for revocationrather than using the Admin SDK to make the check.
Detect ID token revocation in Firebase Security Rules
To be able to detect the ID token revocation using Security Rules, we mustfirst store some user-specific metadata.
Update user-specific metadata in Firebase Realtime Database.
Save the refresh token revocation timestamp. This is needed to track ID tokenrevocation via Firebase Security Rules. This allows for efficient checks withinthe database.In the code samples below, use the uid and the revocation time obtained in theprevious section.
Node.js
const metadataRef = getDatabase().ref('metadata/' + uid);metadataRef.set({ revokeTime: utcRevocationTimeSecs }).then(() => { console.log('Database updated successfully.');});
Java
DatabaseReference ref = FirebaseDatabase.getInstance().getReference("metadata/" + uid);Map<String, Object> userData = new HashMap<>();userData.put("revokeTime", revocationSecond);ref.setValueAsync(userData);
Python
metadata_ref = firebase_admin.db.reference("metadata/" + uid)metadata_ref.set({'revokeTime': revocation_second})
Add a check to Firebase Security Rules
To enforce this check, set up a rule with no client write access to store therevocation time per user. This can be updated with the UTC timestamp of thelast revocation time as shown in the previous examples:
{ "rules": { "metadata": { "$user_id": { // this could be false as it is only accessed from backend or rules. ".read": "$user_id === auth.uid", ".write": "false", } } }}
Any data that requires authenticated access must have the following ruleconfigured. This logic only allows authenticated users with unrevoked IDtokens to access the protected data:
{ "rules": { "users": { "$user_id": { ".read": "auth != null && $user_id === auth.uid && ( !root.child('metadata').child(auth.uid).child('revokeTime').exists() || auth.token.auth_time > root.child('metadata').child(auth.uid).child('revokeTime').val() )", ".write": "auth != null && $user_id === auth.uid && ( !root.child('metadata').child(auth.uid).child('revokeTime').exists() || auth.token.auth_time > root.child('metadata').child(auth.uid).child('revokeTime').val() )", } } }}
Detect ID token revocation in the SDK.
In your server, implement the following logic for refresh token revocationand ID token validation:
When a user's ID token is to be verified, the additional checkRevoked
boolean flag has to be passed to verifyIdToken
. If the user's token isrevoked, the user should be signed out on the client or asked to reauthenticateusing reauthentication APIs provided by the Firebase Authentication client SDKs.
To initialize the Admin SDK for your platform, follow the instructions on thesetup page. Examples of retrieving the IDtoken are in theverifyIdToken
section.
Node.js
// Verify the ID token while checking if the token is revoked by passing// checkRevoked true.let checkRevoked = true;getAuth() .verifyIdToken(idToken, checkRevoked) .then((payload) => { // Token is valid. }) .catch((error) => { if (error.code == 'auth/id-token-revoked') { // Token has been revoked. Inform the user to reauthenticate or signOut() the user. } else { // Token is invalid. } });
Java
try { // Verify the ID token while checking if the token is revoked by passing checkRevoked // as true. boolean checkRevoked = true; FirebaseToken decodedToken = FirebaseAuth.getInstance() .verifyIdToken(idToken, checkRevoked); // Token is valid and not revoked. String uid = decodedToken.getUid();} catch (FirebaseAuthException e) { if (e.getAuthErrorCode() == AuthErrorCode.REVOKED_ID_TOKEN) { // Token has been revoked. Inform the user to re-authenticate or signOut() the user. } else { // Token is invalid. }}
Python
try: # Verify the ID token while checking if the token is revoked by # passing check_revoked=True. decoded_token = auth.verify_id_token(id_token, check_revoked=True) # Token is valid and not revoked. uid = decoded_token['uid']except auth.RevokedIdTokenError: # Token revoked, inform the user to reauthenticate or signOut(). passexcept auth.UserDisabledError: # Token belongs to a disabled user record. passexcept auth.InvalidIdTokenError: # Token is invalid pass
Go
client, err := app.Auth(ctx)if err != nil {log.Fatalf("error getting Auth client: %v\n", err)}token, err := client.VerifyIDTokenAndCheckRevoked(ctx, idToken)if err != nil {if err.Error() == "ID token has been revoked" {// Token is revoked. Inform the user to reauthenticate or signOut() the user.} else {// Token is invalid}}log.Printf("Verified ID token: %v\n", token)
C#
try{ // Verify the ID token while checking if the token is revoked by passing checkRevoked // as true. bool checkRevoked = true; var decodedToken = await FirebaseAuth.DefaultInstance.VerifyIdTokenAsync( idToken, checkRevoked); // Token is valid and not revoked. string uid = decodedToken.Uid;}catch (FirebaseAuthException ex){ if (ex.AuthErrorCode == AuthErrorCode.RevokedIdToken) { // Token has been revoked. Inform the user to re-authenticate or signOut() the user. } else { // Token is invalid. }}
Respond to token revocation on the client
If the token is revoked via the Admin SDK, the client is informed of therevocation and the user is expected to reauthenticate or is signed out:
function onIdTokenRevocation() { // For an email/password user. Prompt the user for the password again. let password = prompt('Please provide your password for reauthentication'); let credential = firebase.auth.EmailAuthProvider.credential( firebase.auth().currentUser.email, password); firebase.auth().currentUser.reauthenticateWithCredential(credential) .then(result => { // User successfully reauthenticated. New ID tokens should be valid. }) .catch(error => { // An error occurred. });}
Advanced Security: Enforce IP address restrictions
A common security mechanism for detecting token theft is to keep track ofrequest IP address origins. For example, if requests are always coming fromthe same IP address (server making the call), single IP address sessions canbe enforced. Or, you might revoke a user's token if you detect that the user'sIP address suddenly changed geolocation or you receive a request from asuspicious origin.
To perform security checks based on IP address, for every authenticated requestinspect the ID token and check if the request's IP address matches previoustrusted IP addresses or is within a trusted range before allowing access torestricted data. For example:
app.post('/getRestrictedData', (req, res) => { // Get the ID token passed. const idToken = req.body.idToken; // Verify the ID token, check if revoked and decode its payload. admin.auth().verifyIdToken(idToken, true).then((claims) => { // Get the user's previous IP addresses, previously saved. return getPreviousUserIpAddresses(claims.sub); }).then(previousIpAddresses => { // Get the request IP address. const requestIpAddress = req.connection.remoteAddress; // Check if the request IP address origin is suspicious relative to previous // IP addresses. The current request timestamp and the auth_time of the ID // token can provide additional signals of abuse especially if the IP address // suddenly changed. If there was a sudden location change in a // short period of time, then it will give stronger signals of possible abuse. if (!isValidIpAddress(previousIpAddresses, requestIpAddress)) { // Invalid IP address, take action quickly and revoke all user's refresh tokens. revokeUserTokens(claims.uid).then(() => { res.status(401).send({error: 'Unauthorized access. Please login again!'}); }, error => { res.status(401).send({error: 'Unauthorized access. Please login again!'}); }); } else { // Access is valid. Try to return data. getData(claims).then(data => { res.end(JSON.stringify(data); }, error => { res.status(500).send({ error: 'Server error!' }) }); } });});
Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 4.0 License, and code samples are licensed under the Apache 2.0 License. For details, see the Google Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.
Last updated 2024-04-09 UTC.
[{ "type": "thumb-down", "id": "missingTheInformationINeed", "label":"Missing the information I need" },{ "type": "thumb-down", "id": "tooComplicatedTooManySteps", "label":"Too complicated / too many steps" },{ "type": "thumb-down", "id": "outOfDate", "label":"Out of date" },{ "type": "thumb-down", "id": "samplesCodeIssue", "label":"Samples / code issue" },{ "type": "thumb-down", "id": "otherDown", "label":"Other" }] [{ "type": "thumb-up", "id": "easyToUnderstand", "label":"Easy to understand" },{ "type": "thumb-up", "id": "solvedMyProblem", "label":"Solved my problem" },{ "type": "thumb-up", "id": "otherUp", "label":"Other" }]