Ikuti semua informasi yang diumumkan di Firebase Summit, dan pelajari bagaimana Firebase dapat membantu Anda mempercepat pengembangan aplikasi dan menjalankan aplikasi dengan percaya diri. Pelajari Lebih Lanjut

Kelola Sesi Pengguna

Sesi Firebase Authentication berumur panjang. Setiap kali pengguna masuk, kredensial pengguna dikirim ke backend Firebase Authentication dan ditukar dengan token ID Firebase (JWT) dan token penyegaran. Token ID Firebase berumur pendek dan bertahan selama satu jam; token penyegaran dapat digunakan untuk mengambil token ID baru. Token penyegaran hanya kedaluwarsa jika salah satu hal berikut terjadi:

  • Pengguna dihapus
  • Pengguna dinonaktifkan
  • Perubahan akun utama terdeteksi untuk pengguna. Ini termasuk acara seperti pembaruan kata sandi atau alamat email.

Firebase Admin SDK menyediakan kemampuan untuk mencabut token penyegaran untuk pengguna tertentu. Selain itu, API untuk memeriksa pencabutan token ID juga tersedia. Dengan kemampuan ini, Anda memiliki kontrol lebih besar atas sesi pengguna. SDK menyediakan kemampuan untuk menambahkan batasan untuk mencegah sesi digunakan dalam keadaan yang mencurigakan, serta mekanisme untuk pemulihan dari potensi pencurian token.

Cabut token penyegaran

Anda dapat mencabut token penyegaran yang ada saat pengguna melaporkan perangkat yang hilang atau dicuri. Demikian pula, jika Anda menemukan kerentanan umum atau mencurigai kebocoran skala besar dari token aktif, Anda dapat menggunakan API listUsers untuk mencari semua pengguna dan mencabut token mereka untuk proyek yang ditentukan.

Penyetelan ulang kata sandi juga mencabut token pengguna yang ada; namun, backend Firebase Authentication menangani pencabutan secara otomatis dalam kasus tersebut. Saat pencabutan, pengguna keluar dan diminta untuk mengautentikasi ulang.

Berikut adalah contoh penerapan yang menggunakan Admin SDK untuk mencabut token penyegaran dari pengguna tertentu. Untuk menginisialisasi Admin SDK, ikuti petunjuk di halaman penyiapan .

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

Jawa

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

Pergi

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

Deteksi pencabutan token ID

Karena token ID Firebase adalah JWT stateless, Anda dapat menentukan bahwa token telah dicabut hanya dengan meminta status token dari backend Firebase Authentication. Untuk alasan ini, melakukan pemeriksaan ini di server Anda adalah operasi yang mahal, membutuhkan jaringan ekstra perjalanan pulang pergi. Anda dapat menghindari membuat permintaan jaringan ini dengan menyiapkan Aturan Keamanan Firebase yang memeriksa pencabutan daripada menggunakan Admin SDK untuk melakukan pemeriksaan.

Deteksi pencabutan token ID di Aturan Keamanan Firebase

Untuk dapat mendeteksi pencabutan token ID menggunakan Aturan Keamanan, pertama-tama kita harus menyimpan beberapa metadata khusus pengguna.

Perbarui metadata khusus pengguna di Firebase Realtime Database.

Simpan stempel waktu pencabutan token penyegaran. Ini diperlukan untuk melacak pencabutan token ID melalui Aturan Keamanan Firebase. Hal ini memungkinkan untuk pemeriksaan yang efisien dalam database. Dalam contoh kode di bawah ini, gunakan uid dan waktu pencabutan yang diperoleh di bagian sebelumnya .

Node.js

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

Jawa

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

Tambahkan tanda centang ke Aturan Keamanan Firebase

Untuk menerapkan pemeriksaan ini, siapkan aturan tanpa akses tulis klien untuk menyimpan waktu pencabutan per pengguna. Ini dapat diperbarui dengan stempel waktu UTC dari waktu pencabutan terakhir seperti yang ditunjukkan pada contoh sebelumnya:

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

Data apa pun yang memerlukan akses terotentikasi harus memiliki aturan berikut yang dikonfigurasi. Logika ini hanya mengizinkan pengguna yang diautentikasi dengan token ID yang tidak dicabut untuk mengakses data yang dilindungi:

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

Deteksi pencabutan token ID di SDK.

Di server Anda, terapkan logika berikut untuk pencabutan token penyegaran dan validasi token ID:

Saat token ID pengguna akan diverifikasi, tanda boolean checkRevoked tambahan harus diteruskan ke verifyIdToken . Jika token pengguna dicabut, pengguna harus logout di klien atau diminta untuk mengautentikasi ulang menggunakan API autentikasi ulang yang disediakan oleh SDK klien Firebase Authentication.

Untuk menginisialisasi Admin SDK untuk platform Anda, ikuti petunjuk di halaman penyiapan . Contoh pengambilan token ID ada di bagian 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.
    }
  });

Jawa

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

Pergi

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

Menanggapi pencabutan token pada klien

Jika token dicabut melalui Admin SDK, klien akan diberitahu tentang pencabutan dan pengguna diharapkan untuk mengautentikasi ulang atau keluar:

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

Keamanan Tingkat Lanjut: Terapkan batasan alamat IP

Mekanisme keamanan umum untuk mendeteksi pencurian token adalah dengan melacak asal alamat IP permintaan. Misalnya, jika permintaan selalu datang dari alamat IP yang sama (server yang melakukan panggilan), sesi alamat IP tunggal dapat diterapkan. Atau, Anda mungkin mencabut token pengguna jika Anda mendeteksi bahwa alamat IP pengguna tiba-tiba berubah geolokasi atau Anda menerima permintaan dari sumber yang mencurigakan.

Untuk melakukan pemeriksaan keamanan berdasarkan alamat IP, untuk setiap permintaan yang diautentikasi, periksa token ID dan periksa apakah alamat IP permintaan cocok dengan alamat IP tepercaya sebelumnya atau berada dalam rentang tepercaya sebelum mengizinkan akses ke data yang dibatasi. Sebagai contoh:

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