Firebase Authentication sesji trwa od dawna. Przy każdym logowaniu dane logowania użytkownika są wysyłane do backendu Firebase Authentication i wymieniane na Token identyfikatora Firebase (JWT) i token odświeżania. Tokeny identyfikatora Firebase są krótkotrwałe i trwają przez godzinę. Token odświeżania może służyć do pobierania nowych tokenów identyfikatora. Tokeny odświeżania wygasają tylko w jednej z tych sytuacji:
- Użytkownik został usunięty.
- Użytkownik jest wyłączony
- Wykryto dużą zmianę na koncie użytkownika. Obejmuje to zdarzenia takie jak aktualizacje hasła lub adresu e-mail.
Pakiet SDK Firebase Admin umożliwia unieważnianie tokenów odświeżania określonego użytkownika. Udostępniliśmy też interfejs API do sprawdzania unieważnienia tokena tożsamości. Te funkcje pozwalają skuteczniej kontrolować sesji. Pakiet SDK umożliwia dodawanie ograniczeń, które zapobiegają używaniu sesji w podejrzanych okolicznościach, a także mechanizm odzyskiwania w przypadku kradzieży tokena.
Unieważnij tokeny odświeżania
Możesz cofnąć istniejący token odświeżania użytkownika, gdy zgłosi on zgubienie lub kradzież urządzenia. Podobnie, jeśli odkryjesz ogólną podatność na atak lub podejrzewasz, że doszło do wycieku aktywnych tokenów na dużą skalę, możesz użyć interfejsu API listUsers
, aby wyszukać wszystkich użytkowników i wycofać ich tokeny w wybranym projekcie.
Resetowanie hasła powoduje również unieważnienie istniejących tokenów użytkownika, ale w tym przypadku backend automatycznie przejmuje unieważnienie. Po cofnięciu unieważnienia użytkownik zostanie wylogowany i poprosi o ponowne uwierzytelnienie.
Oto przykładowa implementacja, która używa pakietu Admin SDK do odwoływania tokenu odświeżania danego użytkownika. Aby zainicjować pakiet Admin SDK, postępuj zgodnie z instrukcjami na stronie stronę 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}`);
});
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);
Wykrywanie unieważnienia tokena identyfikacyjnego
Tokeny Firebase ID są bezstanowe, więc możesz określić, czy token został cofnięty, tylko przez wysłanie żądania dotyczącego stanu tokena do zaplecza. Z tego powodu przeprowadzenie takiego sprawdzania na serwerze jest drogie co wymaga dodatkowego połączenia w obie strony. Aby uniknąć wysyłania tego żądania sieci, skonfiguruj Firebase Security Rules, które sprawdzają odebranie uprawnień, zamiast używać do tego celu pakietu Admin SDK.
Wykrywanie unieważnienia tokena tożsamości w Firebase Security Rules
Aby móc wykrywać odwołanie tokena identyfikacyjnego za pomocą reguł bezpieczeństwa, musimy najpierw przechowywać niektóre metadane dotyczące użytkownika.
Zaktualizuj metadane dotyczące konkretnego użytkownika w usłudze Firebase Realtime Database.
Zapisz sygnaturę czasową odwoływania tokena odświeżania. Jest to wymagane do śledzenia tokena tożsamości unieważnienie w usłudze Firebase Security Rules. Pozwala to na skuteczną kontrolę w bazie danych. W poniższych przykładach kodu użyj identyfikatora UID i czasu unieważnienia uzyskanego w poprzedniej sekcji.
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})
Dodaj kontrolę do Firebase Security Rules
Aby wymusić to sprawdzanie, skonfiguruj regułę bez dostępu klienta do zapisu, aby przechowywać czas odwołania na poziomie użytkownika. Można go zaktualizować o sygnaturę czasową UTC czas ostatniego unieważnienia – jak 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",
}
}
}
}
Wszystkie dane wymagające uwierzytelnionego dostępu muszą zawierać tę regułę skonfigurowany. Ta logika umożliwia dostęp do chronionych danych tylko uwierzytelnionych użytkownikom z nieodwołanymi tokenami identyfikatorów:
{
"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 tokena tożsamości w pakiecie SDK.
Na serwerze zastosuj tę logikę odwołującą token odświeżania i weryfikującą token ID:
W przypadku weryfikacji tokenu identyfikatora użytkownika dodatkowy checkRevoked
flagę wartości logicznej należy przekazać do verifyIdToken
. Jeśli token użytkownika to
użytkownik powinien zostać wylogowany w kliencie lub poprosić o ponowne uwierzytelnienie
za pomocą interfejsów API do ponownego uwierzytelniania udostępnianych przez pakiety SDK klienta Firebase Authentication.
Aby zainicjować pakiet Admin SDK dla swojej platformy, postępuj zgodnie z instrukcjami na
stronę konfiguracji. Przykłady pobierania identyfikatora
Tokeny
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.
}
}
Odpowiedź na odebranie tokena na kliencie
Jeśli token zostanie unieważniony za pomocą pakietu Admin SDK, klient zostanie poinformowany o unieważnienie adresu i oczekiwanie, aby użytkownik ponownie uwierzytelnił się 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.
});
}
Zabezpieczenia zaawansowane: wymuszanie ograniczeń adresów IP
Typowym mechanizmem zabezpieczającym przed kradzieżą tokenów jest śledzenie pochodzenia adresów IP żądań. Jeśli na przykład żądania zawsze pochodzą z tego samego adresu IP (serwer wywołujący serwer), sesje z jednym adresem IP mogą być egzekwowane. Możesz też unieważnić token użytkownika, jeśli stwierdzisz, że jego adres IP nagle zmienił geolokalizację lub otrzymujesz żądanie od podejrzanego źródła.
Przeprowadzanie kontroli zabezpieczeń na podstawie adresu IP dla każdego uwierzytelnionego żądania sprawdź token identyfikatora i sprawdź, czy adres IP żądania pasuje do poprzedniego zaufanych adresów IP lub należy do zaufanego zakresu, zanim zezwolisz na dostęp do ograniczonych danych. 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!' })
});
}
});
});