Join us in person and online for Firebase Summit on October 18, 2022. Learn how Firebase can help you accelerate app development, release your app with confidence, and scale with ease. Register now

Gérer les sessions utilisateur

Restez organisé à l'aide des collections Enregistrez et classez les contenus selon vos préférences.

Les sessions Firebase Authentication durent longtemps. 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 du jeton d'identification est également disponible. Grâce à 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 des sessions dans des circonstances suspectes, ainsi qu'un mécanisme de récupération en cas de vol de jeton potentiel.

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 suspectez une fuite à grande échelle de jetons actifs, vous pouvez utiliser l'API listUsers 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 SDK d'administration, suivez les instructions de 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 JWT sans état, vous pouvez déterminer qu'un jeton a été révoqué uniquement en demandant l'état 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 certaines 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 l'uid et l'heure de révocation obtenus 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 cette vérification, configurez une règle sans accès client en écriture 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 nécessitant 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étecter 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 :

Lorsque le jeton d'identification d'un utilisateur doit être vérifié, l'indicateur booléen checkRevoked supplémentaire 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 du client Firebase Authentication.

Pour initialiser le SDK d'administration pour votre plate-forme, suivez les instructions de la page de configuration . Des exemples de récupération du jeton d'ID se trouvent dans la section verifyIdToken .

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 jeton est révoqué via le SDK Admin, le client est informé de la révocation et l'utilisateur doit se réauthentifier ou est déconnecté :

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 à suivre les origines des adresses IP des demandes. Par exemple, si les demandes proviennent toujours de la même adresse IP (serveur effectuant l'appel), des sessions à adresse IP unique peuvent être appliqué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 trouve dans une plage approuvée avant d'autoriser l'accès aux données restreintes. Par 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!' })
      });
    }
  });
});