Nutzersitzungen verwalten

Firebase Authentication-Sitzungen sind langlebig. Bei jeder Anmeldung eines Nutzers werden die Anmeldedaten des Nutzers an das Firebase Authentication-Backend gesendet und gegen ein Firebase-ID-Token (JWT) und ein Aktualisierungstoken eingetauscht. Firebase-ID-Tokens sind kurzlebig und haben eine Gültigkeitsdauer von einer Stunde. Mit dem Aktualisierungstoken können neue ID-Tokens abgerufen werden. Aktualisierungstokens laufen nur in folgenden Fällen ab:

  • Der Nutzer wird gelöscht
  • Der Nutzer ist deaktiviert
  • Es wurde eine wichtige Kontoänderung für den Nutzer erkannt. Dazu gehören Ereignisse wie das Aktualisieren von Passwörtern oder E-Mail-Adressen.

Mit dem Firebase Admin SDK können Sie Aktualisierungstokens für einen bestimmten Nutzer widerrufen. Außerdem wird eine API zum Prüfen des Widerrufs von ID-Tokens bereitgestellt. Mit diesen Funktionen haben Sie mehr Kontrolle über Nutzersitzungen. Mit dem SDK können Sie Einschränkungen hinzufügen, um zu verhindern, dass Sitzungen unter verdächtigen Umständen verwendet werden, sowie einen Mechanismus zur Wiederherstellung nach potenziellem Tokendiebstahl.

Aktualisierungstokens widerrufen

Sie können das vorhandene Aktualisierungstoken eines Nutzers widerrufen, wenn er ein verlorenes oder gestohlenes Gerät meldet. Wenn Sie eine allgemeine Sicherheitslücke entdecken oder einen groß angelegten Datenleck von aktiven Tokens vermuten, können Sie mit der listUsers API alle Nutzer abrufen und ihre Tokens für das angegebene Projekt widerrufen.

Beim Zurücksetzen des Passworts werden auch die vorhandenen Tokens des Nutzers widerrufen. Der Widerruf wird in diesem Fall jedoch automatisch vom Firebase Authentication-Backend verarbeitet. Nach dem Widerruf wird der Nutzer abgemeldet und aufgefordert, sich noch einmal zu authentifizieren.

Hier ist eine Beispielimplementierung, bei der das Admin SDK verwendet wird, um das Aktualisierungstoken eines bestimmten Nutzers zu widerrufen. Folgen Sie der Anleitung auf der Einrichtungsseite, um das Admin SDK zu initialisieren.

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

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

Widerruf von ID-Tokens erkennen

Da Firebase-ID-Tokens zustandslose JWTs sind, können Sie nur durch Anfordern des Status des Tokens vom Firebase Authentication-Backend feststellen, ob ein Token widerrufen wurde. Aus diesem Grund ist die Durchführung dieser Prüfung auf Ihrem Server eine kostspielige Operation, die einen zusätzlichen Netzwerk-Roundtrip erfordert. Du kannst diese Netzwerkanfrage vermeiden, indem du Firebase Security Rules so einrichtest, dass sie auf Widerruf prüft, anstatt die Überprüfung mit dem Admin SDK durchzuführen.

Widerruf von ID-Tokens in Firebase Security Rules erkennen

Damit wir den Widerruf des ID-Tokens mithilfe von Sicherheitsregeln erkennen können, müssen wir zuerst einige nutzerspezifische Metadaten speichern.

Aktualisieren Sie die nutzerspezifischen Metadaten in Firebase Realtime Database.

Speichern Sie den Zeitstempel für den Widerruf des Aktualisierungstokens. Dies ist erforderlich, um den Widerruf von ID-Tokens über Firebase Security Rules zu erfassen. Das ermöglicht effiziente Prüfungen innerhalb der Datenbank. Verwenden Sie in den folgenden Codebeispielen die im vorherigen Abschnitt ermittelte uid und Widerrufszeit.

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

Firebase Security Rules ein Häkchen hinzufügen

Um diese Prüfung zu erzwingen, richten Sie eine Regel ohne Schreibzugriff für den Client ein, um die Widerrufszeit pro Nutzer zu speichern. Dieser kann mit dem UTC-Zeitstempel der letzten Widerrufszeit aktualisiert werden, wie in den vorherigen Beispielen gezeigt:

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

Für alle Daten, für die ein authentifizierter Zugriff erforderlich ist, muss die folgende Regel konfiguriert sein. Diese Logik erlaubt nur authentifizierten Nutzern mit nicht widerrufenen ID-Tokens den Zugriff auf die geschützten Daten:

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

Widerruf von ID-Tokens im SDK erkennen

Implementieren Sie auf Ihrem Server die folgende Logik für den Widerruf von Aktualisierungstokens und die Validierung von ID-Tokens:

Wenn das ID-Token eines Nutzers überprüft werden soll, muss das zusätzliche boolesche Flag checkRevoked an verifyIdToken übergeben werden. Wenn das Token des Nutzers widerrufen wird, sollte er auf dem Client abgemeldet oder aufgefordert werden, sich mithilfe der APIs zur erneuten Authentifizierung, die von den Firebase Authentication-Client SDKs bereitgestellt werden, noch einmal zu authentifizieren.

Folgen Sie der Anleitung auf der Einrichtungsseite, um das Admin SDK für Ihre Plattform zu initialisieren. Beispiele zum Abrufen des ID-Tokens findest du im Abschnitt 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

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

Auf den Widerruf eines Tokens auf dem Client reagieren

Wenn das Token über das Admin SDK widerrufen wird, wird der Client über den Widerruf informiert und der Nutzer muss sich noch einmal authentifizieren oder wird abgemeldet:

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

Erweiterte Sicherheit: Einschränkungen für IP-Adressen erzwingen

Ein gängiger Sicherheitsmechanismus zum Erkennen von Tokendiebstahl besteht darin, die Herkunft der IP-Adressen von Anfragen im Blick zu behalten. Wenn Anfragen beispielsweise immer von derselben IP-Adresse stammen (Server, der den Aufruf ausführt), können Sitzungen mit einer einzelnen IP-Adresse erzwungen werden. Sie können das Token eines Nutzers auch widerrufen, wenn Sie feststellen, dass sich die IP-Adresse des Nutzers plötzlich geändert hat oder Sie eine Anfrage von einem verdächtigen Ursprung erhalten.

Wenn Sie Sicherheitsprüfungen basierend auf der IP-Adresse ausführen möchten, prüfen Sie bei jeder authentifizierten Anfrage das ID-Token und ob die IP-Adresse der Anfrage mit vorherigen vertrauenswürdigen IP-Adressen übereinstimmt oder sich in einem vertrauenswürdigen Bereich befindet, bevor Sie den Zugriff auf eingeschränkte Daten zulassen. Beispiel:

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