As sessões do Firebase Authentication duram muito. Cada vez que um usuário faz login, as credenciais do usuário são enviadas ao back-end do Firebase Authentication e trocadas por um token de ID do Firebase (um JWT) e um token de atualização. Os tokens de ID do Firebase têm vida curta e duram uma hora; o token de atualização pode ser usado para recuperar novos tokens de ID. Os tokens de atualização expiram somente quando ocorre uma das seguintes situações:
- O usuário é excluído
- O usuário está desabilitado
- Uma alteração importante na conta do usuário é detectada. Isso inclui eventos como atualizações de senha ou endereço de e-mail.
O Firebase Admin SDK oferece a capacidade de revogar tokens de atualização para um usuário especificado. Além disso, também é disponibilizada uma API para verificar a revogação do token de ID. Com esses recursos, você tem mais controle sobre as sessões do usuário. O SDK oferece a capacidade de adicionar restrições para evitar que sessões sejam usadas em circunstâncias suspeitas, bem como um mecanismo para recuperação de possível roubo de token.
Revogar tokens de atualização
Você pode revogar o token de atualização existente de um usuário quando um usuário relatar um dispositivo perdido ou roubado. Da mesma forma, se você descobrir uma vulnerabilidade geral ou suspeitar de um vazamento em grande escala de tokens ativos, poderá usar a API listUsers
para procurar todos os usuários e revogar seus tokens para o projeto especificado.
As redefinições de senha também revogam os tokens existentes de um usuário; no entanto, o back-end do Firebase Authentication lida com a revogação automaticamente nesse caso. Na revogação, o usuário é desconectado e solicitado a autenticar novamente.
Aqui está um exemplo de implementação que usa o Admin SDK para revogar o token de atualização de um determinado usuário. Para inicializar o SDK Admin, siga as instruções na página de configuração .
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);
Pitão
# 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))
Ir
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);
Detectar revogação de token de ID
Como os tokens de ID do Firebase são JWTs sem estado, você só pode determinar se um token foi revogado solicitando o status do token no back-end do Firebase Authentication. Por esse motivo, realizar essa verificação em seu servidor é uma operação cara, exigindo uma viagem extra de ida e volta à rede. Você pode evitar fazer essa solicitação de rede configurando regras de segurança do Firebase que verificam a revogação, em vez de usar o Admin SDK para fazer a verificação.
Detectar revogação de token de ID nas regras de segurança do Firebase
Para poder detectar a revogação do token de ID usando regras de segurança, devemos primeiro armazenar alguns metadados específicos do usuário.
Atualize metadados específicos do usuário no Firebase Realtime Database.
Salve o carimbo de data/hora de revogação do token de atualização. Isso é necessário para rastrear a revogação do token de ID por meio das regras de segurança do Firebase. Isso permite verificações eficientes no banco de dados. Nos exemplos de código abaixo, use o uid e o tempo de revogação obtidos na seção anterior .
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);
Pitão
metadata_ref = firebase_admin.db.reference("metadata/" + uid)
metadata_ref.set({'revokeTime': revocation_second})
Adicionar uma verificação às regras de segurança do Firebase
Para impor essa verificação, configure uma regra sem acesso de gravação do cliente para armazenar o tempo de revogação por usuário. Isso pode ser atualizado com o carimbo de data/hora UTC do horário da última revogação, conforme mostrado nos exemplos anteriores:
{
"rules": {
"metadata": {
"$user_id": {
// this could be false as it is only accessed from backend or rules.
".read": "$user_id === auth.uid",
".write": "false",
}
}
}
}
Quaisquer dados que exijam acesso autenticado devem ter a regra a seguir configurada. Esta lógica permite apenas que usuários autenticados com tokens de ID não revogados acessem os dados protegidos:
{
"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()
)",
}
}
}
}
Detecte a revogação do token de ID no SDK.
No seu servidor, implemente a seguinte lógica para revogação do token de atualização e validação do token de ID:
Quando o token de ID de um usuário deve ser verificado, o sinalizador booleano checkRevoked
adicional deve ser passado para verifyIdToken
. Se o token do usuário for revogado, ele deverá ser desconectado do cliente ou solicitado a se autenticar novamente usando APIs de reautenticação fornecidas pelos SDKs do cliente do Firebase Authentication.
Para inicializar o Admin SDK para sua plataforma, siga as instruções na página de configuração . Exemplos de recuperação do token de ID estão na seção 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.
}
}
Pitão
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
Ir
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.
}
}
Responder à revogação do token no cliente
Se o token for revogado por meio do Admin SDK, o cliente será informado da revogação e o usuário deverá autenticar novamente ou será desconectado:
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.
});
}
Segurança avançada: aplique restrições de endereço IP
Um mecanismo de segurança comum para detectar roubo de token é rastrear as origens do endereço IP da solicitação. Por exemplo, se as solicitações sempre vierem do mesmo endereço IP (servidor que faz a chamada), sessões de endereço IP único poderão ser impostas. Ou você pode revogar o token de um usuário se detectar que o endereço IP do usuário mudou repentinamente de geolocalização ou se receber uma solicitação de uma origem suspeita.
Para realizar verificações de segurança com base no endereço IP, para cada solicitação autenticada, inspecione o token de ID e verifique se o endereço IP da solicitação corresponde aos endereços IP confiáveis anteriores ou está dentro de um intervalo confiável antes de permitir o acesso a dados restritos. Por exemplo:
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!' })
});
}
});
});