Zarządzaj sesjami użytkowników

Sesje uwierzytelniania Firebase są długotrwałe. Za każdym razem, gdy użytkownik się loguje, poświadczenia użytkownika są wysyłane do zaplecza uwierzytelniania Firebase i wymieniane na token identyfikatora Firebase (JWT) i token odświeżania. Tokeny identyfikacyjne Firebase są krótkotrwałe i działają przez godzinę; token odświeżania może służyć do pobierania nowych tokenów identyfikacyjnych. Tokeny odświeżania wygasają tylko wtedy, gdy wystąpi jedna z poniższych sytuacji:

  • Użytkownik został usunięty
  • Użytkownik jest wyłączony
  • Wykryto poważną zmianę konta użytkownika. Obejmuje to zdarzenia takie jak aktualizacje haseł lub adresów e-mail.

Pakiet Firebase Admin SDK umożliwia unieważnianie tokenów odświeżania dla określonego użytkownika. Ponadto udostępniono także interfejs API umożliwiający sprawdzenie unieważnienia tokenu identyfikacyjnego. Dzięki tym funkcjom masz większą kontrolę nad sesjami użytkowników. SDK zapewnia możliwość dodawania ograniczeń zapobiegających używaniu sesji w podejrzanych okolicznościach, a także mechanizm odzyskiwania po potencjalnej kradzieży tokena.

Unieważnij tokeny odświeżania

Możesz unieważnić istniejący token odświeżania użytkownika, gdy użytkownik zgłosi zgubienie lub kradzież urządzenia. Podobnie, jeśli odkryjesz ogólną lukę lub podejrzewasz wyciek aktywnych tokenów na szeroką skalę, możesz użyć API listUsers , aby wyszukać wszystkich użytkowników i unieważnić ich tokeny dla określonego projektu.

Resetowanie hasła powoduje również unieważnienie istniejących tokenów użytkownika; jednakże w takim przypadku backend Firebase Authentication automatycznie obsługuje unieważnienie. Po odwołaniu użytkownik zostaje wylogowany i poproszony o ponowne uwierzytelnienie.

Oto przykładowa implementacja wykorzystująca pakiet Admin SDK do unieważnienia tokena odświeżania danego użytkownika. Aby zainicjować pakiet Admin SDK, postępuj zgodnie z instrukcjami na stronie konfiguracji .

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

Jawa

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

Pyton

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

Iść

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

Wykryj unieważnienie tokenu identyfikacyjnego

Ponieważ tokeny identyfikatora Firebase są bezstanowymi tokenami JWT, możesz stwierdzić, że token został unieważniony, tylko żądając statusu tokena z zaplecza uwierzytelniania Firebase. Z tego powodu sprawdzenie tego na serwerze jest kosztowną operacją wymagającą dodatkowej komunikacji sieciowej. Możesz uniknąć wysyłania tego żądania sieciowego, konfigurując reguły bezpieczeństwa Firebase, które sprawdzają unieważnienie, zamiast używać pakietu Admin SDK do sprawdzania.

Wykryj unieważnienie tokena identyfikacyjnego w regułach bezpieczeństwa Firebase

Aby móc wykryć unieważnienie tokena identyfikacyjnego za pomocą reguł bezpieczeństwa, musimy najpierw zapisać pewne metadane specyficzne dla użytkownika.

Zaktualizuj metadane specyficzne dla użytkownika w bazie danych Firebase Realtime Database.

Zapisz sygnaturę czasową unieważnienia tokenu odświeżania. Jest to potrzebne do śledzenia unieważnienia tokena identyfikacyjnego za pomocą reguł bezpieczeństwa Firebase. Pozwala to na sprawną kontrolę w bazie danych. W poniższych przykładach kodu użyj identyfikatora użytkownika i czasu odwołania uzyskanych w poprzedniej sekcji .

Node.js

const metadataRef = getDatabase().ref('metadata/' + uid);
metadataRef.set({ revokeTime: utcRevocationTimeSecs }).then(() => {
  console.log('Database updated successfully.');
});

Jawa

DatabaseReference ref = FirebaseDatabase.getInstance().getReference("metadata/" + uid);
Map<String, Object> userData = new HashMap<>();
userData.put("revokeTime", revocationSecond);
ref.setValueAsync(userData);

Pyton

metadata_ref = firebase_admin.db.reference("metadata/" + uid)
metadata_ref.set({'revokeTime': revocation_second})

Dodaj zaznaczenie do reguł bezpieczeństwa Firebase

Aby wymusić tę kontrolę, skonfiguruj regułę bez dostępu klienta do zapisu, aby przechowywać czas odwołania dla każdego użytkownika. Można to zaktualizować za pomocą znacznika czasu UTC ostatniego odwołania, jak pokazano w poprzednich przykładach:

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

Wszelkie dane wymagające uwierzytelnionego dostępu muszą mieć skonfigurowaną następującą regułę. Ta logika umożliwia dostęp do chronionych danych jedynie uwierzytelnionym użytkownikom z nieunieważnionymi tokenami identyfikacyjnymi:

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

Wykryj unieważnienie tokenu identyfikatora w zestawie SDK.

Zaimplementuj na swoim serwerze następującą logikę unieważniania tokenu odświeżania i sprawdzania tokenu identyfikatora:

Gdy token ID użytkownika ma zostać zweryfikowany, należy przekazać dodatkową flagę logiczną checkRevoked do verifyIdToken . Jeśli token użytkownika zostanie unieważniony, użytkownik powinien zostać wylogowany na kliencie lub poproszony o ponowne uwierzytelnienie przy użyciu interfejsów API ponownego uwierzytelniania dostarczonych przez pakiety SDK klienta Firebase Authentication.

Aby zainicjować pakiet Admin SDK dla swojej platformy, postępuj zgodnie z instrukcjami na stronie konfiguracji . Przykłady pobrania tokena ID znajdują się w sekcji 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.
    }
  });

Jawa

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

Pyton

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

Iść

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

Odpowiedz na unieważnienie tokenu na kliencie

Jeśli token zostanie unieważniony za pośrednictwem pakietu Admin SDK, klient zostanie poinformowany o unieważnieniu i oczekuje się, że użytkownik dokona ponownego uwierzytelnienia lub zostanie wylogowany:

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

Zaawansowane zabezpieczenia: wymuszaj ograniczenia adresów IP

Powszechnym mechanizmem bezpieczeństwa służącym do wykrywania kradzieży tokenów jest śledzenie pochodzenia adresów IP żądań. Na przykład, jeśli żądania zawsze przychodzą z tego samego adresu IP (serwer wykonujący połączenie), można wymusić sesje z jednym adresem IP. Możesz też unieważnić token użytkownika, jeśli wykryjesz, że adres IP użytkownika nagle zmienił geolokalizację lub otrzymasz żądanie z podejrzanego źródła.

Aby przeprowadzić kontrolę bezpieczeństwa na podstawie adresu IP, dla każdego uwierzytelnionego żądania sprawdź token identyfikacyjny i sprawdź, czy adres IP żądania jest zgodny z poprzednimi zaufanymi adresami IP lub mieści się w zaufanym zakresie, zanim umożliwisz dostęp do zastrzeżonych danych. Na przykład:

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