Zarządzaj sesjami użytkowników

Firebase Authentication sesje są długotrwałe. Za każdym razem, gdy użytkownik się loguje, jego dane logowania są wysyłane do backendu Firebase Authentication i wymieniane na token identyfikatora Firebase (JWT) oraz token odświeżania. Tokeny identyfikacyjne Firebase mają krótki okres ważności (1 godzinę). Token odświeżania może służyć do pobierania nowych tokenów identyfikacyjnych. Tokeny odświeżania wygasają tylko w tych przypadkach:

  • Użytkownik został usunięty
  • Użytkownik jest wyłączony
  • Wykryto znaczącą zmianę na koncie użytkownika. Obejmuje to zdarzenia takie jak aktualizacje hasła lub adresu e-mail.

Pakiet Firebase Admin SDK umożliwia unieważnienie tokenów odświeżania dla określonego użytkownika. Udostępniamy też interfejs API do sprawdzania, czy token tożsamości został unieważniony. Dzięki tym możliwościom masz większą kontrolę nad sesjami użytkowników. Pakiet SDK umożliwia dodawanie ograniczeń, które zapobiegają używaniu sesji w podejrzanych okolicznościach, a także mechanizm odzyskiwania w przypadku potencjalnej kradzieży tokena.

Unieważnianie tokenów odświeżania

Możesz cofnąć istniejący token odświeżania użytkownika, gdy zgłosi on utratę lub kradzież urządzenia. Podobnie, jeśli wykryjesz ogólną lukę w zabezpieczeniach lub podejrzewasz wyciek na dużą skalę aktywnych tokenów, możesz użyć interfejsu API listUsers, aby wyszukać wszystkich użytkowników i unieważnić ich tokeny w określonym projekcie.

Resetowanie hasła powoduje też unieważnienie dotychczasowych tokenów użytkownika, ale w tym przypadku Firebase Authenticationbackend automatycznie obsługuje unieważnienie. Po unieważnieniu tokena użytkownik zostanie wylogowany i zobaczy prośbę o ponowne uwierzytelnienie.

Oto przykładowa implementacja, która używa pakietu 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}`);
  });

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(f'Tokens revoked at: {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 identyfikatora

Tokeny identyfikatorów Firebase to bezstanowe tokeny JWT, więc informację o tym, że token został unieważniony, możesz uzyskać tylko przez wysłanie do Firebase AuthenticationbackenduFirebase Authentication żądania dotyczącego stanu tokena. Z tego powodu przeprowadzenie tego sprawdzenia na serwerze jest kosztowną operacją, która wymaga dodatkowej podróży w sieci. Możesz uniknąć tego żądania sieciowego, konfigurując Firebase Security Rules, które sprawdzają unieważnienie, zamiast używać pakietu Admin SDK do przeprowadzania tego sprawdzenia.

Wykrywanie unieważnienia tokena tożsamości w Firebase Security Rules

Aby wykrywać odwołanie tokena identyfikatora za pomocą reguł bezpieczeństwa, musimy najpierw zapisać metadane dotyczące konkretnego użytkownika.

Aktualizowanie metadanych dotyczących użytkownika w regionie Firebase Realtime Database.

Zapisz sygnaturę czasową unieważnienia tokena odświeżania. Jest to wymagane do śledzenia unieważnienia tokena identyfikatora za pomocą Firebase Security Rules. Umożliwia to sprawne sprawdzanie danych w bazie. W przykładowych kodach poniżej użyj identyfikatora UID i czasu unieważnienia uzyskanych 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, która będzie przechowywać czas unieważnienia dla każdego użytkownika. Możesz ją zaktualizować za pomocą sygnatury czasowej UTC ostatniego czasu 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",
      }
    }
  }
}

W przypadku wszystkich danych, które wymagają uwierzytelnionego dostępu, musi być skonfigurowana ta reguła: Ta logika umożliwia dostęp do chronionych danych tylko uwierzytelnionym użytkownikom z nieodwołanymi 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()
        )",
      }
    }
  }
}

Wykrywanie unieważnienia tokena identyfikacji w pakiecie SDK.

Na serwerze zaimplementuj tę logikę odwoływania tokena odświeżania i weryfikacji tokena identyfikatora:

Podczas weryfikacji tokena identyfikatora użytkownika do funkcji verifyIdToken należy przekazać dodatkową checkRevokedflagę logiczną. Jeśli token użytkownika zostanie cofnięty, użytkownik powinien zostać wylogowany z klienta lub poproszony o ponowne uwierzytelnienie za pomocą interfejsów API do ponownego uwierzytelniania udostępnianych przez Firebase Authentication pakiety SDK klienta.

Aby zainicjować pakiet Admin SDK na swojej platformie, postępuj zgodnie z instrukcjami na stronie konfiguracji. Przykłady pobierania tokena identyfikatora znajdziesz 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.
    }
  });

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

Odpowiadanie na unieważnienie tokena na kliencie

Jeśli token zostanie unieważniony za pomocą pakietu Admin SDK, klient zostanie o tym poinformowany, a użytkownik będzie musiał ponownie się uwierzytelnić 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: wymuszanie ograniczeń adresów IP

Popularnym mechanizmem zabezpieczającym przed kradzieżą tokena jest śledzenie adresów IP, z których pochodzą żądania. Jeśli na przykład żądania zawsze pochodzą z tego samego adresu IP (serwera, który wywołuje funkcję), można wymusić sesje z pojedynczym adresem IP. Możesz też cofnąć token użytkownika, jeśli wykryjesz, że jego adres IP nagle zmienił geolokalizację, lub jeśli otrzymasz żądanie z podejrzanego źródła.

Aby przeprowadzać kontrole bezpieczeństwa na podstawie adresu IP, w przypadku każdego uwierzytelnionego żądania sprawdź token tożsamości i zobacz, czy adres IP żądania pasuje do poprzednich zaufanych adresów IP lub mieści się w zaufanym zakresie, zanim zezwolisz na dostęp do danych o ograniczonym dostępie. 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!' })
      });
    }
  });
});