Membuat Token Kustom

Firebase memberi Anda kontrol penuh terhadap autentikasi melalui kemampuan mengautentikasi pengguna atau perangkat menggunakan JSON Web Token (JWT) yang aman. Anda membuat token ini di server, meneruskannya ke perangkat klien, lalu menggunakannya untuk melakukan autentikasi dengan metode signInWithCustomToken().

Untuk melakukannya, Anda harus membuat endpoint server yang menerima kredensial login, seperti nama pengguna dan sandi. Jika kredensial valid, endpoint server akan menampilkan JWT kustom. JWT kustom yang ditampilkan dari server Anda kemudian dapat digunakan oleh perangkat klien untuk melakukan autentikasi menggunakan Firebase (iOS, Android, web). Setelah diautentikasi, identitas ini akan digunakan ketika mengakses layanan Firebase lain, seperti Firebase Realtime Database, dan Cloud Storage. Selanjutnya, konten JWT akan tersedia di objek auth di Aturan Keamanan Firebase Realtime Database dan objek request.auth di Aturan Keamanan Cloud Storage.

Anda dapat membuat token kustom dengan Firebase Admin SDK atau Anda bisa menggunakan library JWT pihak ketiga jika server ditulis dalam bahasa yang tidak didukung oleh Firebase secara default.

Sebelum memulai

Token kustom adalah JWT yang ditandatangani, dan kunci pribadi yang digunakan untuk login adalah milik akun layanan Google. Ada beberapa cara guna menentukan akun layanan Google yang harus digunakan oleh Firebase Admin SDK untuk menandatangani token kustom:

  • Menggunakan file JSON akun layanan -- Metode ini dapat digunakan di lingkungan apa pun, tetapi Anda harus mengemas file JSON akun layanan bersama dengan kode Anda. Anda harus sangat berhati-hati saat memastikan agar file JSON akun layanan tidak terungkap ke pihak eksternal.
  • Membiarkan Admin SDK menemukan akun layanan -- Metode ini dapat digunakan dalam lingkungan yang dikelola oleh Google seperti Google Cloud Functions dan Google App Engine. Anda mungkin harus mengonfigurasi sejumlah izin tambahan melalui Google Cloud Platform Console.
  • Menggunakan ID akun layanan -- Ketika digunakan di lingkungan yang dikelola Google, metode ini akan menandatangani token menggunakan kunci akun layanan yang ditentukan. Namun, metode ini menggunakan layanan web jarak jauh dan Anda mungkin harus mengonfigurasi izin tambahan untuk akun layanan ini melalui Google Cloud Platform Console.

Menggunakan file JSON akun layanan

File JSON akun layanan berisi semua informasi yang terkait dengan akun layanan (termasuk kunci pribadi RSA). File JSON tersebut dapat didownload dari Firebase Console. Ikuti petunjuk penyiapan Admin SDK untuk mengetahui informasi lebih lanjut cara menginisialisasi Admin SDK dengan file JSON akun layanan.

Metode inisialisasi ini cocok untuk berbagai penerapan Admin SDK. Selain itu, dengan metode ini, Admin SDK juga dapat membuat dan menandatangani token kustom secara lokal, tanpa membuat panggilan API jarak jauh. Kekurangan utama dari pendekatan ini adalah Anda harus mengemas file JSON akun layanan bersama dengan kode Anda. Perlu diperhatikan juga bahwa kunci pribadi dalam file JSON akun layanan adalah informasi sensitif, dan Anda harus sangat berhati-hati dalam menjaga kerahasiaannya. Secara khusus, jangan menambahkan file JSON akun layanan ke kontrol versi publik.

Mengizinkan Admin SDK menemukan akun layanan

Jika kode Anda di-deploy di lingkungan yang dikelola oleh Google, Admin SDK dapat mencoba untuk otomatis menemukan cara guna menandatangani token kustom:

  • Jika kode Anda di-deploy di lingkungan standar Google App Engine untuk Java, Python, atau Go, Admin SDK dapat menggunakan layanan App Identity yang ada di lingkungan tersebut untuk menandatangani token kustom. Layanan App Identity menandatangani data menggunakan akun layanan yang disediakan untuk aplikasi Anda oleh Google App Engine.

  • Jika kode Anda di-deploy di beberapa lingkungan terkelola lainnya (misalnya, Google Cloud Functions, Google Compute Engine), Firebase Admin SDK dapat menemukan string ID akun layanan secara otomatis dari server metadata lokal. ID akun layanan yang ditemukan tersebut kemudian akan digunakan bersama dengan layanan IAM untuk menandatangani token dari jarak jauh.

Agar dapat menggunakan metode penandatanganan ini, lakukan inisialisasi SDK dengan kredensial default Aplikasi Google dan jangan tentukan string ID akun layanan:

Node.js

admin.initializeApp();

Java

FirebaseApp.initializeApp();

Python

default_app = firebase_admin.initialize_app()

Go

app, err := firebase.NewApp(context.Background(), nil)
if err != nil {
	log.Fatalf("error initializing app: %v\n", err)
}

C#

FirebaseApp.Create();

Untuk menguji kode yang sama secara lokal, download file JSON akun layanan lalu tetapkan variabel lingkungan GOOGLE_APPLICATION_CREDENTIALS untuk diarahkan ke sana.

Jika Firebase Admin SDK harus menemukan string ID akun layanan, SDK tersebut akan melakukannya saat kode Anda membuat token kustom untuk pertama kalinya. Hasilnya disimpan dalam cache dan digunakan kembali untuk operasi penandatanganan token berikutnya. ID akun layanan yang ditemukan secara otomatis biasanya adalah salah satu akun layanan default yang disediakan oleh Google Cloud Platform:

Sama seperti ID akun layanan yang ditentukan secara eksplisit, ID akun layanan yang ditemukan secara otomatis harus memiliki izin iam.serviceAccounts.signBlob agar pembuatan token kustom dapat berjalan dengan baik. Anda mungkin harus menggunakan bagian IAM dan admin dari Google Cloud Platform Console untuk memberikan izin yang diperlukan kepada akun layanan default. Baca bagian pemecahan masalah di bawah ini untuk mengetahui informasi lebih lanjut.

Menggunakan ID akun layanan

Untuk menjaga konsistensi antarbagian aplikasi Anda, Anda dapat menentukan ID akun layanan yang kuncinya akan digunakan untuk menandatangani token ketika berjalan di lingkungan yang dikelola Google. Tindakan ini dapat membuat kebijakan IAM lebih simpel dan lebih aman, serta mencegah file JSON akun layanan disertakan dalam kode Anda.

ID akun layanan dapat ditemukan di Google Cloud Platform Console, atau dalam kolom client_email dari file JSON akun layanan yang didownload. ID akun layanan adalah alamat email yang memiliki format berikut: <client-id>@<project-id>.iam.gserviceaccount.com. ID tersebut secara unik mengidentifikasi akun layanan dalam project Firebase dan Google Cloud Platform.

Untuk membuat token kustom dengan ID akun layanan terpisah, lakukan inisialisasi SDK seperti yang ditunjukkan di bawah ini:

Node.js

admin.initializeApp({
  serviceAccountId: 'my-client-id@my-project-id.iam.gserviceaccount.com',
});

Java

FirebaseOptions options = new FirebaseOptions.Builder()
    .setCredentials(GoogleCredentials.getApplicationDefault())
    .setServiceAccountId("my-client-id@my-project-id.iam.gserviceaccount.com")
    .build();
FirebaseApp.initializeApp(options);

Python

options = {
    'serviceAccountId': 'my-client-id@my-project-id.iam.gserviceaccount.com',
}
firebase_admin.initialize_app(options=options)

Go

conf := &firebase.Config{
	ServiceAccountID: "my-client-id@my-project-id.iam.gserviceaccount.com",
}
app, err := firebase.NewApp(context.Background(), conf)
if err != nil {
	log.Fatalf("error initializing app: %v\n", err)
}

C#

FirebaseApp.Create(new AppOptions()
{
    Credential = GoogleCredential.GetApplicationDefault(),
    ServiceAccountId = "my-client-id@my-project-id.iam.gserviceaccount.com",
});

ID akun layanan bukan informasi yang sensitif. Jadi, jika ID ini terungkap bukanlah hal yang penting. Namun, untuk menandatangani token kustom dengan akun layanan yang ditentukan, Firebase Admin SDK harus memanggil layanan jarak jauh. Selain itu, Anda juga harus memastikan bahwa akun layanan yang digunakan oleh Admin SDK untuk melakukan panggilan ini —biasanya {project-name}@appspot.gserviceaccount.com—memiliki izin iam.serviceAccounts.signBlob. Baca bagian pemecahan masalah di bawah ini untuk mengetahui informasi lebih lanjut.

Membuat token kustom menggunakan Firebase Admin SDK

Firebase Admin SDK memiliki metode bawaan untuk membuat token kustom. Anda minimal perlu menyediakan uid, yang dapat berupa string apa pun, tetapi harus secara unik mengidentifikasi pengguna atau perangkat yang diautentikasi. Token ini tidak akan berlaku lagi setelah satu jam.

Node.js

let uid = 'some-uid';

admin.auth().createCustomToken(uid)
  .then(function(customToken) {
    // Send token back to client
  })
  .catch(function(error) {
    console.log('Error creating custom token:', error);
  });

Java

String uid = "some-uid";

String customToken = FirebaseAuth.getInstance().createCustomToken(uid);
// Send token back to client

Python

uid = 'some-uid'

custom_token = auth.create_custom_token(uid)

Go

client, err := app.Auth(context.Background())
if err != nil {
	log.Fatalf("error getting Auth client: %v\n", err)
}

token, err := client.CustomToken(ctx, "some-uid")
if err != nil {
	log.Fatalf("error minting custom token: %v\n", err)
}

log.Printf("Got custom token: %v\n", token)

C#

var uid = "some-uid";

string customToken = await FirebaseAuth.DefaultInstance.CreateCustomTokenAsync(uid);
// Send token back to client

Anda juga bisa menetapkan klaim tambahan secara opsional untuk disertakan dalam token kustom. Pada contoh di bawah ini, kolom premiumAccount telah ditambahkan ke token kustom, yang akan tersedia di objek auth/request.auth dalam Aturan Keamanan Anda:

Node.js

let userId = 'some-uid';
let additionalClaims = {
  premiumAccount: true
};

admin.auth().createCustomToken(userId, additionalClaims)
  .then(function(customToken) {
    // Send token back to client
  })
  .catch(function(error) {
    console.log('Error creating custom token:', error);
  });

Java

String uid = "some-uid";
Map<String, Object> additionalClaims = new HashMap<String, Object>();
additionalClaims.put("premiumAccount", true);

String customToken = FirebaseAuth.getInstance()
    .createCustomToken(uid, additionalClaims);
// Send token back to client

Python

uid = 'some-uid'
additional_claims = {
    'premiumAccount': True
}

custom_token = auth.create_custom_token(uid, additional_claims)

Go

client, err := app.Auth(context.Background())
if err != nil {
	log.Fatalf("error getting Auth client: %v\n", err)
}

claims := map[string]interface{}{
	"premiumAccount": true,
}

token, err := client.CustomTokenWithClaims(ctx, "some-uid", claims)
if err != nil {
	log.Fatalf("error minting custom token: %v\n", err)
}

log.Printf("Got custom token: %v\n", token)

C#

var uid = "some-uid";
var additionalClaims = new Dictionary<string, object>()
{
    { "premiumAccount", true },
};

string customToken = await FirebaseAuth.DefaultInstance
    .CreateCustomTokenAsync(uid, additionalClaims);
// Send token back to client

Login menggunakan token kustom pada klien

Setelah membuat token kustom, Anda harus mengirimkannya ke aplikasi klien. Aplikasi klien melakukan autentikasi dengan token kustom dengan memanggil signInWithCustomToken():

iOS

Objective-C
[[FIRAuth auth] signInWithCustomToken:customToken
                           completion:^(FIRAuthDataResult * _Nullable authResult,
                                        NSError * _Nullable error) {
  // ...
}];
Swift
Auth.auth().signIn(withCustomToken: customToken ?? "") { (user, error) in
  // ...
}

Android

mAuth.signInWithCustomToken(mCustomToken)
        .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
            @Override
            public void onComplete(@NonNull Task<AuthResult> task) {
                if (task.isSuccessful()) {
                    // Sign in success, update UI with the signed-in user's information
                    Log.d(TAG, "signInWithCustomToken:success");
                    FirebaseUser user = mAuth.getCurrentUser();
                    updateUI(user);
                } else {
                    // If sign in fails, display a message to the user.
                    Log.w(TAG, "signInWithCustomToken:failure", task.getException());
                    Toast.makeText(CustomAuthActivity.this, "Authentication failed.",
                            Toast.LENGTH_SHORT).show();
                    updateUI(null);
                }
            }
        });

Unity

auth.SignInWithCustomTokenAsync(custom_token).ContinueWith(task => {
  if (task.IsCanceled) {
    Debug.LogError("SignInWithCustomTokenAsync was canceled.");
    return;
  }
  if (task.IsFaulted) {
    Debug.LogError("SignInWithCustomTokenAsync encountered an error: " + task.Exception);
    return;
  }

  Firebase.Auth.FirebaseUser newUser = task.Result;
  Debug.LogFormat("User signed in successfully: {0} ({1})",
      newUser.DisplayName, newUser.UserId);
});

C++

firebase::Future<firebase::auth::User*> result =
    auth->SignInWithCustomToken(custom_token);

Web

firebase.auth().signInWithCustomToken(token).catch(function(error) {
  // Handle Errors here.
  var errorCode = error.code;
  var errorMessage = error.message;
  // ...
});

Jika autentikasi berhasil, pengguna akan login ke aplikasi klien Anda dengan akun yang ditentukan oleh uid yang disertakan dalam token kustom. Jika akun tersebut sebelumnya tidak ada, data untuk pengguna tersebut akan dibuat.

Dengan cara yang sama seperti metode login lainnya (seperti signInWithEmailAndPassword() dan signInWithCredential()) objek auth di Aturan Keamanan Firebase Realtime Database dan objek request.auth di Aturan Keamanan Cloud Storage Anda akan diisi dengan uid pengguna. Dalam hal ini, uid akan menjadi objek yang Anda tentukan saat membuat token kustom.

Aturan Database

{
  "rules": {
    "adminContent": {
      ".read": "auth.uid === 'some-uid'"
    }
  }
}

Aturan Storage

service firebase.storage {
  match /b/<your-firebase-storage-bucket>/o {
    match /adminContent/{filename} {
      allow read, write: if request.auth.uid == "some-uid";
    }
  }
}

Jika token kustom berisi klaim tambahan, token tersebut dapat direferensikan ke objek auth.token (Firebase Realtime Database) atau request.auth.token (Cloud Storage) dalam aturan Anda:

Aturan Database

{
  "rules": {
    "premiumContent": {
      ".read": "auth.token.premiumAccount === true"
    }
  }
}

Aturan Storage

service firebase.storage {
  match /b/<your-firebase-storage-bucket>/o {
    match /premiumContent/{filename} {
      allow read, write: if request.auth.token.premiumAccount == true;
    }
  }
}

Membuat token kustom menggunakan library JWT pihak ketiga

Jika backend Anda menggunakan bahasa yang tidak memiliki Firebase Admin SDK resmi, Anda masih dapat membuat token kustom secara manual. Pertama, temukan library JWT pihak ketiga untuk bahasa Anda. Kemudian, gunakan library JWT tersebut untuk membuat JWT yang mencakup klaim berikut:

Klaim Token Kustom
alg Algoritme "RS256"
iss Penerbit Alamat email akun layanan project Anda
sub Subjek Alamat email akun layanan project Anda
aud Audience "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"
iat Waktu penerbitan Waktu saat ini, dalam satuan detik sejak epoch UNIX
exp Waktu habis masa berlaku Waktu, dalam satuan detik sejak epoch UNIX, saat token sudah tidak berlaku. Lamanya waktu bisa mencapai maksimum 3.600 detik lebih lama daripada iat.
Catatan: ini hanya mengontrol kapan token kustom berhenti berlaku. Namun, setelah Anda membuat pengguna login menggunakan signInWithCustomToken(), dia akan tetap login di perangkat hingga sesi tidak berlaku lagi atau pengguna tersebut logout.
uid ID unik dari pengguna yang login harus berupa string dengan panjang karakter 1-36
claims (opsional) Klaim kustom opsional yang akan disertakan di variabel Aturan Keamanan auth/ request.auth

Berikut beberapa contoh implementasi mengenai cara membuat token kustom dalam berbagai bahasa yang tidak didukung Firebase Admin SDK:

PHP

Menggunakan php-jwt:

// Requires: composer require firebase/php-jwt
use Firebase\JWT\JWT;

// Get your service account's email address and private key from the JSON key file
$service_account_email = "abc-123@a-b-c-123.iam.gserviceaccount.com";
$private_key = "-----BEGIN PRIVATE KEY-----...";

function create_custom_token($uid, $is_premium_account) {
  global $service_account_email, $private_key;

  $now_seconds = time();
  $payload = array(
    "iss" => $service_account_email,
    "sub" => $service_account_email,
    "aud" => "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
    "iat" => $now_seconds,
    "exp" => $now_seconds+(60*60),  // Maximum expiration time is one hour
    "uid" => $uid,
    "claims" => array(
      "premium_account" => $is_premium_account
    )
  );
  return JWT::encode($payload, $private_key, "RS256");
}

Ruby

Using ruby-jwt:

require "jwt"

# Get your service account's email address and private key from the JSON key file
$service_account_email = "service-account@my-project-abc123.iam.gserviceaccount.com"
$private_key = OpenSSL::PKey::RSA.new "-----BEGIN PRIVATE KEY-----\n..."

def create_custom_token(uid, is_premium_account)
  now_seconds = Time.now.to_i
  payload = {:iss => $service_account_email,
             :sub => $service_account_email,
             :aud => "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
             :iat => now_seconds,
             :exp => now_seconds+(60*60), # Maximum expiration time is one hour
             :uid => uid,
             :claims => {:premium_account => is_premium_account}}
  JWT.encode payload, $private_key, "RS256"
end

Setelah membuat token kustom, kirimkan token tersebut ke aplikasi klien Anda agar digunakan untuk melakukan autentikasi dengan Firebase. Lihat contoh kode di atas untuk mengetahui cara melakukannya.

Pemecahan masalah

Bagian ini berisi penjelasan mengenai beberapa masalah umum yang mungkin dihadapi developer saat membuat token kustom, serta cara menyelesaikannya.

IAM API tidak diaktifkan

Jika Anda menentukan ID akun layanan untuk menandatangani token, Anda mungkin akan mendapatkan error yang serupa dengan yang berikut:

Identity and Access Management (IAM) API has not been used in project
1234567890 before or it is disabled. Enable it by visiting
https://console.developers.google.com/apis/api/iam.googleapis.com/overview?project=1234567890
then retry. If you enabled this API recently, wait a few minutes for the action
to propagate to our systems and retry.

Firebase Admin SDK menggunakan IAM API untuk menandatangani token. Error ini menunjukkan bahwa IAM API saat ini tidak diaktifkan untuk project Firebase Anda. Buka link dalam pesan error di browser web, lalu klik tombol "Aktifkan API" guna mengaktifkannya untuk project Anda.

Akun layanan tidak memiliki izin yang diperlukan

Jika akun layanan yang dijalankan Firebase Admin SDK tidak memiliki izin iam.serviceAccounts.signBlob, Anda mungkin akan mendapatkan pesan error seperti berikut:

Permission iam.serviceAccounts.signBlob is required to perform this operation
on service account projects/-/serviceAccounts/{your-service-account-id}.

Cara termudah untuk menyelesaikan masalah ini adalah dengan memberikan peran IAM "Pembuat Token Akun Layanan" ke akun layanan yang bersangkutan, seperti {project-name}@appspot.gserviceaccount.com:

  1. Buka halaman IAM dan admin di Google Cloud Platform Console.
  2. Pilih project Anda, lalu klik "Lanjutkan".
  3. Klik ikon edit yang sesuai dengan akun layanan yang ingin Anda update.
  4. Klik "Tambahkan Peran Lain".
  5. Ketik "Service Account Token Creator" ke dalam filter penelusuran, lalu pilih dari hasilnya.
  6. Klik "Simpan" untuk mengonfirmasi pemberian peran.

Baca dokumentasi IAM untuk mengetahui informasi lebih lanjut tentang proses ini, atau pelajari cara melakukan peran update menggunakan fitur command-line gcloud.

Gagal menentukan akun layanan

Jika Anda mendapatkan pesan error yang serupa dengan yang berikut ini, berarti Firebase Admin SDK belum diinisialisasi dengan benar.

Failed to determine service account ID. Initialize the SDK with service account
credentials or specify a service account ID with iam.serviceAccounts.signBlob
permission.

Jika Anda mengandalkan SDK untuk menemukan ID akun layanan secara otomatis, pastikan kode diterapkan di lingkungan yang dikelola Google dengan server metadata. Jika tidak, pastikan untuk menentukan file JSON akun layanan atau ID akun layanan saat melakukan inisialisasi SDK.