Kullanıcı Oturumlarını Yönet

Firebase Kimlik Doğrulama oturumları uzun ömürlüdür. Bir kullanıcı her oturum açtığında, kullanıcı kimlik bilgileri Firebase Authentication arka ucuna gönderilir ve bir Firebase ID belirteci (JWT) ve yenileme belirteci ile değiştirilir. Firebase ID jetonları kısa ömürlüdür ve bir saat sürer; yenileme jetonu yeni kimlik jetonlarını almak için kullanılabilir. Yenileme belirteçlerinin süresi yalnızca aşağıdakilerden biri meydana geldiğinde sona erer:

  • Kullanıcı silindi
  • Kullanıcı devre dışı bırakıldı
  • Kullanıcı için önemli bir hesap değişikliği algılandı. Buna şifre veya e-posta adresi güncellemeleri gibi olaylar da dahildir.

Firebase Admin SDK, belirli bir kullanıcı için yenileme jetonlarını iptal etme olanağı sağlar. Ayrıca, kimlik belirteci iptalini kontrol edecek bir API de kullanıma sunuldu. Bu yetenekler sayesinde kullanıcı oturumları üzerinde daha fazla kontrole sahip olursunuz. SDK, oturumların şüpheli durumlarda kullanılmasını önlemek için kısıtlamalar ekleme yeteneğinin yanı sıra potansiyel token hırsızlığından kurtarma için bir mekanizma sağlar.

Yenileme jetonlarını iptal et

Bir kullanıcı cihazın kaybolduğunu veya çalındığını bildirdiğinde kullanıcının mevcut yenileme jetonunu iptal edebilirsiniz. Benzer şekilde, genel bir güvenlik açığı keşfederseniz veya etkin belirteçlerin geniş ölçekli bir sızıntısından şüphelenirseniz, tüm kullanıcıları aramak ve belirtilen proje için belirteçleri iptal etmek için listUsers API'sini kullanabilirsiniz.

Parola sıfırlamaları aynı zamanda kullanıcının mevcut belirteçlerini de iptal eder; ancak bu durumda Firebase Authentication arka ucu iptal işlemini otomatik olarak gerçekleştirir. İptal durumunda kullanıcının oturumu kapatılır ve yeniden kimlik doğrulaması yapması istenir.

Belirli bir kullanıcının yenileme belirtecini iptal etmek için Yönetici SDK'sını kullanan örnek bir uygulamayı burada bulabilirsiniz. Admin SDK'yı başlatmak için kurulum sayfasındaki talimatları izleyin.

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

Gitmek

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

Kimlik belirtecinin iptalini algılama

Firebase ID belirteçleri durum bilgisi olmayan JWT'ler olduğundan, yalnızca Firebase Authentication arka ucundan belirtecin durumunu isteyerek bir belirtecin iptal edildiğini belirleyebilirsiniz. Bu nedenle sunucunuzda bu kontrolün yapılması pahalı bir işlemdir ve ekstra ağ gidiş-dönüş gerektirir. Denetimi yapmak için Yönetici SDK'sını kullanmak yerine iptali denetleyen Firebase Güvenlik Kuralları ayarlayarak bu ağ isteğinin yapılmasını önleyebilirsiniz.

Firebase Güvenlik Kurallarında kimlik belirtecinin iptalini algılama

Güvenlik Kurallarını kullanarak kimlik belirtecinin iptalini tespit edebilmek için öncelikle kullanıcıya özel bazı meta verileri saklamamız gerekir.

Firebase Gerçek Zamanlı Veritabanında kullanıcıya özel meta verileri güncelleyin.

Yenileme belirtecinin iptali zaman damgasını kaydedin. Bu, Firebase Güvenlik Kuralları aracılığıyla kimlik belirtecinin iptalini izlemek için gereklidir. Bu, veritabanı içinde etkili kontrollere olanak tanır. Aşağıdaki kod örneklerinde, önceki bölümde elde edilen kullanıcı kimliğini ve iptal süresini kullanın.

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

Firebase Güvenlik Kurallarına bir onay ekleyin

Bu kontrolü zorunlu kılmak için kullanıcı başına iptal süresini depolamak üzere istemci yazma erişimi olmayan bir kural ayarlayın. Bu, önceki örneklerde gösterildiği gibi, son iptal zamanının UTC zaman damgasıyla güncellenebilir:

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

Kimliği doğrulanmış erişim gerektiren tüm verilerde aşağıdaki kural yapılandırılmış olmalıdır. Bu mantık yalnızca kimliği doğrulanmış ve iptal edilmemiş kimlik belirteçlerine sahip kullanıcıların korunan verilere erişmesine izin verir:

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

SDK'da kimlik belirtecinin iptalini tespit edin.

Sunucunuzda, yenileme belirteci iptali ve kimlik belirteci doğrulaması için aşağıdaki mantığı uygulayın:

Bir kullanıcının kimlik belirtecinin doğrulanması gerektiğinde, ek checkRevoked boole bayrağının verifyIdToken öğesine iletilmesi gerekir. Kullanıcının jetonu iptal edilirse kullanıcıdan istemcideki oturumu kapatılmalı veya Firebase Authentication istemci SDK'ları tarafından sağlanan yeniden kimlik doğrulama API'lerini kullanarak yeniden kimlik doğrulaması yapması istenmelidir.

Platformunuz için Yönetici SDK'sını başlatmak için kurulum sayfasındaki talimatları izleyin. Kimlik belirtecini alma örnekleri, verifyIdToken bölümünde yer almaktadır.

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

Gitmek

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

İstemcideki belirteç iptaline yanıt verme

Belirteç Yönetici SDK'sı aracılığıyla iptal edilirse müşteriye iptal hakkında bilgi verilir ve kullanıcının yeniden kimlik doğrulaması yapması beklenir veya oturum kapatılır:

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

Gelişmiş Güvenlik: IP adresi kısıtlamalarını uygulayın

Belirteç hırsızlığını tespit etmek için yaygın bir güvenlik mekanizması, istek IP adresi kökenlerini takip etmektir. Örneğin, istekler her zaman aynı IP adresinden geliyorsa (çağrıyı yapan sunucu), tek IP adresi oturumları uygulanabilir. Veya kullanıcının IP adresinin aniden coğrafi konumunu değiştirdiğini tespit ederseniz veya şüpheli bir kaynaktan istek alırsanız, kullanıcının belirtecini iptal edebilirsiniz.

IP adresine dayalı güvenlik kontrolleri gerçekleştirmek için, kimliği doğrulanmış her istek için kimlik belirtecini inceleyin ve kısıtlı verilere erişime izin vermeden önce isteğin IP adresinin önceki güvenilen IP adresleriyle eşleşip eşleşmediğini veya güvenilir bir aralıkta olup olmadığını kontrol edin. Örneğin:

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