Benutzersitzungen verwalten

Firebase-Authentifizierungssitzungen sind langlebig. Jedes Mal, wenn sich ein Benutzer anmeldet, werden die Benutzeranmeldeinformationen an das Firebase Authentication-Backend gesendet und gegen ein Firebase-ID-Token (ein JWT) und ein Aktualisierungstoken ausgetauscht. Firebase-ID-Tokens sind kurzlebig und eine Stunde lang gültig. Mit dem Aktualisierungstoken können neue ID-Tokens abgerufen werden. Aktualisierungstoken verfallen nur, wenn einer der folgenden Fälle eintritt:

  • Der Benutzer wird gelöscht
  • Der Benutzer ist deaktiviert
  • Für den Benutzer wurde eine größere Kontoänderung festgestellt. Dazu gehören Ereignisse wie Passwort- oder E-Mail-Adressaktualisierungen.

Das Firebase Admin SDK bietet die Möglichkeit, Aktualisierungstokens für einen bestimmten Benutzer zu widerrufen. Darüber hinaus wird auch eine API zur Prüfung auf ID-Token-Sperrung zur Verfügung gestellt. Mit diesen Funktionen haben Sie mehr Kontrolle über Benutzersitzungen. Das SDK bietet die Möglichkeit, Einschränkungen hinzuzufügen, um zu verhindern, dass Sitzungen unter verdächtigen Umständen verwendet werden, sowie einen Mechanismus zur Wiederherstellung nach einem möglichen Token-Diebstahl.

Aktualisierungstoken widerrufen

Sie können das vorhandene Aktualisierungstoken eines Benutzers widerrufen, wenn ein Benutzer ein verlorenes oder gestohlenes Gerät meldet. Wenn Sie eine allgemeine Sicherheitslücke entdecken oder einen großflächigen Verlust aktiver Token vermuten, können Sie die listUsers -API verwenden, um alle Benutzer zu suchen und ihre Token für das angegebene Projekt zu widerrufen.

Durch das Zurücksetzen von Passwörtern werden auch die vorhandenen Token eines Benutzers widerrufen. Allerdings verarbeitet das Firebase Authentication-Backend den Widerruf in diesem Fall automatisch. Beim Widerruf wird der Benutzer abgemeldet und zur erneuten Authentifizierung aufgefordert.

Hier ist eine Beispielimplementierung, die das Admin SDK verwendet, um das Aktualisierungstoken eines bestimmten Benutzers zu widerrufen. Um das Admin SDK zu initialisieren, befolgen Sie die Anweisungen auf der Setup-Seite .

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

Gehen

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

Erkennen Sie den Widerruf des ID-Tokens

Da es sich bei Firebase-ID-Tokens um zustandslose JWTs handelt, können Sie nur feststellen, ob ein Token widerrufen wurde, indem Sie den Status des Tokens vom Firebase Authentication-Backend anfordern. Aus diesem Grund ist die Durchführung dieser Überprüfung auf Ihrem Server ein kostspieliger Vorgang, der einen zusätzlichen Netzwerk-Roundtrip erfordert. Sie können diese Netzwerkanforderung vermeiden, indem Sie Firebase-Sicherheitsregeln einrichten, die auf einen Widerruf prüfen, anstatt die Prüfung mit dem Admin SDK durchzuführen.

Erkennen Sie den Widerruf von ID-Tokens in den Firebase-Sicherheitsregeln

Um den Widerruf des ID-Tokens mithilfe von Sicherheitsregeln erkennen zu können, müssen wir zunächst einige benutzerspezifische Metadaten speichern.

Aktualisieren Sie benutzerspezifische Metadaten in der Firebase-Echtzeitdatenbank.

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

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

Fügen Sie eine Prüfung zu den Firebase-Sicherheitsregeln hinzu

Um diese Prüfung zu erzwingen, richten Sie eine Regel ohne Client-Schreibzugriff ein, um die Sperrzeit pro Benutzer zu speichern. Dies kann mit dem UTC-Zeitstempel der letzten Sperrzeit 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, die einen authentifizierten Zugriff erfordern, muss die folgende Regel konfiguriert sein. Diese Logik ermöglicht nur authentifizierten Benutzern 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()
        )",
      }
    }
  }
}

Erkennen Sie den Widerruf des ID-Tokens im SDK.

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

Wenn das ID-Token eines Benutzers überprüft werden soll, muss das zusätzliche boolesche Flag checkRevoked an verifyIdToken übergeben werden. Wenn das Token des Benutzers widerrufen wird, sollte der Benutzer auf dem Client abgemeldet oder aufgefordert werden, sich mithilfe der von den Firebase Authentication-Client-SDKs bereitgestellten Reauthentifizierungs-APIs erneut zu authentifizieren.

Um das Admin SDK für Ihre Plattform zu initialisieren, befolgen Sie die Anweisungen auf der Setup-Seite . Beispiele zum Abrufen des ID-Tokens finden Sie 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

Gehen

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

Reagieren Sie auf den Token-Widerruf auf dem Client

Wenn das Token über das Admin SDK widerrufen wird, wird der Client über den Widerruf informiert und der Benutzer muss sich erneut 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: IP-Adressbeschränkungen erzwingen

Ein gängiger Sicherheitsmechanismus zur Erkennung von Token-Diebstahl besteht darin, die Herkunft der IP-Adressen der Anfrage zu verfolgen. Wenn Anfragen beispielsweise immer von derselben IP-Adresse kommen (Server, der den Anruf ausführt), können Einzel-IP-Adresssitzungen erzwungen werden. Oder Sie können das Token eines Benutzers widerrufen, wenn Sie feststellen, dass sich die IP-Adresse des Benutzers plötzlich geändert hat, oder wenn Sie eine Anfrage von einem verdächtigen Ursprung erhalten.

Um Sicherheitsüberprüfungen basierend auf der IP-Adresse durchzuführen, überprüfen Sie bei jeder authentifizierten Anfrage das ID-Token und prüfen Sie, ob die IP-Adresse der Anfrage mit früheren vertrauenswürdigen IP-Adressen übereinstimmt oder innerhalb eines vertrauenswürdigen Bereichs liegt, bevor Sie den Zugriff auf eingeschränkte Daten zulassen. Zum 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!' })
      });
    }
  });
});