Catch up on everything we announced at this year's Firebase Summit. Learn more

Gérer les sessions utilisateur

Les sessions d'authentification Firebase sont de longue durée. Chaque fois qu'un utilisateur se connecte, les informations d'identification de l'utilisateur sont envoyées au backend Firebase Authentication et échangées contre un jeton d'identification Firebase (un JWT) et un jeton d'actualisation. Les jetons d'identification Firebase sont de courte durée et durent une heure ; le jeton d'actualisation peut être utilisé pour récupérer de nouveaux jetons d'identification. Les jetons d'actualisation n'expirent que lorsque l'un des événements suivants se produit :

  • L'utilisateur est supprimé
  • L'utilisateur est désactivé
  • Un changement de compte majeur est détecté pour l'utilisateur. Cela inclut des événements tels que les mises à jour de mot de passe ou d'adresse e-mail.

Le SDK Firebase Admin offre la possibilité de révoquer les jetons d'actualisation pour un utilisateur spécifié. De plus, une API pour vérifier la révocation des jetons d'identification est également mise à disposition. Avec ces fonctionnalités, vous avez plus de contrôle sur les sessions utilisateur. Le SDK offre la possibilité d'ajouter des restrictions pour empêcher l'utilisation de sessions dans des circonstances suspectes, ainsi qu'un mécanisme de récupération en cas de vol potentiel de jetons.

Révoquer les jetons d'actualisation

Vous pouvez révoquer le jeton d'actualisation existant d'un utilisateur lorsqu'un utilisateur signale un appareil perdu ou volé. De même, si vous découvrez une vulnérabilité générale ou suspecte une fuite à grande échelle des jetons actifs, vous pouvez utiliser la listUsers API pour rechercher tous les utilisateurs et révoquer leurs jetons pour le projet spécifié.

Les réinitialisations de mot de passe révoquent également les jetons existants d'un utilisateur ; cependant, le backend Firebase Authentication gère automatiquement la révocation dans ce cas. Lors de la révocation, l'utilisateur est déconnecté et invité à se ré-authentifier.

Voici un exemple d'implémentation qui utilise le SDK Admin pour révoquer le jeton d'actualisation d'un utilisateur donné. Pour initialiser le Admin SDK suivez les instructions sur la page de configuration .

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 / 1000
print('Tokens revoked at: {0}'.format(revocation_second))

Aller

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 TokenValidAfter
u, err := client.GetUser(ctx, uid)
if err != nil {
	log.Fatalf("error getting user %s: %v\n", uid, err)
}
timestamp := u.TokensValidAfterMillis / 1000
log.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);

Détecter la révocation du jeton d'identification

Étant donné que les jetons d'identification Firebase sont des jetons JWT sans état, vous pouvez déterminer qu'un jeton a été révoqué uniquement en demandant le statut du jeton au backend Firebase Authentication. Pour cette raison, effectuer cette vérification sur votre serveur est une opération coûteuse, nécessitant un aller-retour réseau supplémentaire. Vous pouvez éviter de faire cette demande de réseau en configurant des règles de sécurité Firebase qui vérifient la révocation plutôt que d'utiliser le SDK Admin pour effectuer la vérification.

Détecter la révocation du jeton d'identification dans les règles de sécurité Firebase

Pour pouvoir détecter la révocation du jeton d'identification à l'aide des règles de sécurité, nous devons d'abord stocker des métadonnées spécifiques à l'utilisateur.

Mettez à jour les métadonnées spécifiques à l'utilisateur dans Firebase Realtime Database.

Enregistrez l'horodatage de révocation du jeton d'actualisation. Cela est nécessaire pour suivre la révocation du jeton d'identification via les règles de sécurité Firebase. Cela permet des vérifications efficaces au sein de la base de données. Dans les exemples de code ci - dessous, utilisez le uid et le temps de révocation obtenu dans la section précédente .

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})

Ajouter une vérification aux règles de sécurité Firebase

Pour appliquer ce contrôle, configurez une règle sans accès en écriture client pour stocker l'heure de révocation par utilisateur. Cela peut être mis à jour avec l'horodatage UTC de la dernière heure de révocation, comme indiqué dans les exemples précédents :

{
  "rules": {
    "metadata": {
      "$user_id": {
        // this could be false as it is only accessed from backend or rules.
        ".read": "$user_id === auth.uid",
        ".write": "false",
      }
    }
  }
}

Toutes les données qui nécessitent un accès authentifié doivent avoir la règle suivante configurée. Cette logique permet uniquement aux utilisateurs authentifiés avec des jetons d'identification non révoqués d'accéder aux données protégées :

{
  "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()
        )",
      }
    }
  }
}

Détectez la révocation du jeton d'ID dans le SDK.

Dans votre serveur, implémentez la logique suivante pour la révocation du jeton d'actualisation et la validation du jeton d'ID :

Quand à vérifier le jeton d'identification d'un utilisateur, le plus checkRevoked booléen doit être transmis à verifyIdToken . Si le jeton de l'utilisateur est révoqué, l'utilisateur doit être déconnecté du client ou invité à se réauthentifier à l'aide des API de réauthentification fournies par les SDK client Firebase Authentication.

Pour initialiser le Admin SDK pour votre plate - forme, suivez les instructions sur la page de configuration . Des exemples de récupérer le jeton d'identification sont dans la verifyIdToken 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().
    pass
except auth.UserDisabledError:
    # Token belongs to a disabled user record.
    pass
except auth.InvalidIdTokenError:
    # Token is invalid
    pass

Aller

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.
    }
}

Répondre à la révocation du jeton sur le client

Si le token est révoqué via le SDK Admin, le client est informé de la révocation et l'utilisateur doit se ré-authentifier ou se déconnecter :

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.
    });
}

Sécurité avancée : appliquer les restrictions d'adresse IP

Un mécanisme de sécurité courant pour détecter le vol de jetons consiste à garder une trace des origines de l'adresse IP de la demande. Par exemple, si les demandes proviennent toujours de la même adresse IP (serveur effectuant l'appel), des sessions d'adresse IP unique peuvent être imposées. Ou, vous pouvez révoquer le jeton d'un utilisateur si vous détectez que l'adresse IP de l'utilisateur a soudainement changé de géolocalisation ou si vous recevez une demande d'une origine suspecte.

Pour effectuer des contrôles de sécurité basés sur l'adresse IP, pour chaque demande authentifiée, inspectez le jeton d'identification et vérifiez si l'adresse IP de la demande correspond aux adresses IP approuvées précédentes ou se situe dans une plage de confiance avant d'autoriser l'accès aux données restreintes. Par exemple:

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!' })
      });
    }
  });
});