Catch up on everthing we announced at this year's Firebase Summit. Learn more

Gestisci sessioni utente

Le sessioni di autenticazione Firebase sono longeve. Ogni volta che un utente accede, le credenziali dell'utente vengono inviate al backend di autenticazione Firebase e scambiate con un token ID Firebase (un JWT) e un token di aggiornamento. I token ID Firebase sono di breve durata e durano un'ora; il token di aggiornamento può essere utilizzato per recuperare nuovi token ID. I token di aggiornamento scadono solo quando si verifica una delle seguenti condizioni:

  • L'utente è stato cancellato
  • L'utente è disabilitato
  • Viene rilevata una modifica importante dell'account per l'utente. Ciò include eventi come aggiornamenti di password o indirizzi e-mail.

Firebase Admin SDK offre la possibilità di revocare i token di aggiornamento per un utente specificato. Inoltre, è disponibile anche un'API per verificare la revoca del token ID. Con queste funzionalità, hai un maggiore controllo sulle sessioni utente. L'SDK offre la possibilità di aggiungere restrizioni per impedire l'utilizzo delle sessioni in circostanze sospette, nonché un meccanismo per il ripristino da un potenziale furto di token.

Revoca i token di aggiornamento

Potresti revocare il token di aggiornamento esistente di un utente quando un utente segnala un dispositivo smarrito o rubato. Allo stesso modo, se si scopre una vulnerabilità generale o sospetto di perdite una vasta scala di gettoni attivi, è possibile utilizzare l' listUsers API per cercare tutti gli utenti e revocare i loro gettoni per il progetto specificato.

Le reimpostazioni delle password revocano anche i token esistenti di un utente; tuttavia, in tal caso, il backend di autenticazione Firebase gestisce automaticamente la revoca. In caso di revoca, l'utente viene disconnesso e viene richiesto di eseguire nuovamente l'autenticazione.

Di seguito è riportato un esempio di implementazione che utilizza Admin SDK per revocare il token di aggiornamento di un determinato utente. Per inizializzare lo SDK di amministrazione seguire le istruzioni riportate sulla pagina di configurazione .

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

Giava

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

Pitone

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

andare

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

Rileva la revoca del token ID

Poiché i token ID Firebase sono JWT senza stato, puoi determinare che un token è stato revocato solo richiedendo lo stato del token dal back-end di autenticazione Firebase. Per questo motivo, eseguire questo controllo sul tuo server è un'operazione costosa, che richiede un round trip di rete aggiuntivo. Puoi evitare di effettuare questa richiesta di rete impostando le regole Firebase che verificano la revoca anziché utilizzare l'SDK Admin per effettuare il controllo.

Rileva la revoca del token ID nelle regole del database

Per essere in grado di rilevare la revoca del token ID utilizzando le regole del database, dobbiamo prima memorizzare alcuni metadati specifici dell'utente.

Aggiorna i metadati specifici dell'utente in Firebase Realtime Database.

Salva il timestamp di revoca del token di aggiornamento. Ciò è necessario per tenere traccia della revoca del token ID tramite le regole Firebase. Ciò consente controlli efficienti all'interno del database. Negli esempi di codice, usate l'UID e il tempo revoca ottenuto nella sezione precedente .

Nodo.js

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

Giava

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

Pitone

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

Aggiungi un segno di spunta alle Regole del database

Per applicare questo controllo, imposta una regola senza accesso in scrittura client per memorizzare il tempo di revoca per utente. Questo può essere aggiornato con il timestamp UTC dell'ora dell'ultima revoca, come mostrato negli esempi precedenti:

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

Tutti i dati che richiedono l'accesso autenticato devono avere la seguente regola configurata. Questa logica consente solo agli utenti autenticati con token ID non revocati di accedere ai dati protetti:

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

Rileva la revoca del token ID nell'SDK.

Nel tuo server, implementa la seguente logica per la revoca del token di aggiornamento e la convalida del token ID:

Quando Token ID di un utente è da verificare, l'ulteriore checkRevoked flag booleano deve essere passato a verifyIdToken . Se il token dell'utente viene revocato, l'utente deve essere disconnesso dal client o gli viene chiesto di eseguire nuovamente l'autenticazione utilizzando le API di riautenticazione fornite dagli SDK client di Firebase Authentication.

Per inizializzare l'Admin SDK per la piattaforma, seguire le istruzioni della pagina di configurazione . Esempi di recuperare il token ID sono nella verifyIdToken sezione.

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

Giava

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

Pitone

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

andare

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

Rispondere alla revoca del token sul client

Se il token viene revocato tramite Admin SDK, il client viene informato della revoca e l'utente dovrebbe eseguire nuovamente l'autenticazione o uscire:

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

Sicurezza avanzata: applica le restrizioni sull'indirizzo IP

Un meccanismo di sicurezza comune per rilevare il furto di token consiste nel tenere traccia delle origini dell'indirizzo IP della richiesta. Ad esempio, se le richieste provengono sempre dallo stesso indirizzo IP (server che effettua la chiamata), è possibile imporre sessioni di indirizzi IP singoli. In alternativa, potresti revocare il token di un utente se rilevi che l'indirizzo IP dell'utente ha cambiato improvvisamente la geolocalizzazione o se ricevi una richiesta da un'origine sospetta.

Per eseguire controlli di sicurezza basati sull'indirizzo IP, per ogni richiesta autenticata ispezionare il token ID e verificare se l'indirizzo IP della richiesta corrisponde a precedenti indirizzi IP attendibili o rientra in un intervallo attendibile prima di consentire l'accesso ai dati con restrizioni. Per esempio:

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