Membuat Token Kustom

Firebase memberi Anda kontrol penuh terhadap autentikasi melalui kemampuan mengautentikasi pengguna atau perangkat menggunakan Token Web JSON (JWT) yang aman. Anda dapat membuat token ini di server, meneruskannya kembali 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 saat mengakses layanan Firebase lain, seperti Firebase Realtime Database dan Cloud Storage. Selanjutnya, konten JWT akan tersedia di objek auth di bagian Realtime Database Security Rules dan request.auth di bagian Cloud Storage Security Rules

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

Sebelum memulai

Token kustom adalah JWT bertanda tangan, dan kunci pribadi yang digunakan untuk menandatanganinya adalah milik akun layanan Google. Ada beberapa cara 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 mengharuskan Anda untuk 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.
  • Mengizinkan Admin SDK menemukan akun layanan -- Metode ini dapat digunakan dalam lingkungan yang dikelola oleh Google, seperti Google Cloud Functions dan App Engine. Anda mungkin harus mengonfigurasi sejumlah izin tambahan melalui Google Cloud 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 sejumlah izin tambahan untuk akun layanan ini melalui Google Cloud 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 terkait cara menginisialisasi Admin SDK dengan file JSON akun layanan.

Metode inisialisasi ini cocok untuk berbagai deployment Admin SDK. Selain itu, dengan metode ini, Admin SDK juga dapat membuat dan menandatangani token kustom secara lokal, tanpa melakukan 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 tambahkan 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 secara otomatis menemukan cara menandatangani token kustom:

  • Jika kode Anda di-deploy di lingkungan standar 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 suatu lingkungan terkelola lain (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 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

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

Jika Firebase Admin SDK harus menemukan string ID akun layanan, hal itu akan dilakukan 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:

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 and admin di Google Cloud console untuk memberikan izin yang diperlukan akun layanan default. Baca bagian pemecahan masalah di bawah untuk mengetahui informasi lebih lanjut.

Menggunakan ID akun layanan

Untuk menjaga konsistensi antarbagian aplikasi, 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. Selain itu, file JSON akun layanan tidak perlu disertakan dalam kode Anda.

ID akun layanan dapat ditemukan di Google Cloud 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.

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

Node.js

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

Java

FirebaseOptions options = 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 merupakan informasi sensitif. Jadi, jika ID ini terungkap, tidak ada konsekuensinya. 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 untuk mengetahui informasi lebih lanjut.

Membuat token kustom menggunakan Firebase Admin SDK

Firebase Admin SDK memiliki metode bawaan untuk membuat token kustom. Setidaknya, Anda 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

const uid = 'some-uid';

getAuth()
  .createCustomToken(uid)
  .then((customToken) => {
    // Send token back to client
  })
  .catch((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

Jika diinginkan, Anda juga bisa menetapkan klaim tambahan 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 Security Rules:

Node.js

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

getAuth()
  .createCustomToken(userId, additionalClaims)
  .then((customToken) => {
    // Send token back to client
  })
  .catch((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

Nama token kustom yang dicadangkan

Login menggunakan token kustom pada klien

Setelah membuat token kustom, Anda harus mengirimkannya ke aplikasi klien. Aplikasi klien akan 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.AuthResult result = task.Result;
  Debug.LogFormat("User signed in successfully: {0} ({1})",
      result.User.DisplayName, result.User.UserId);
});

C++

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

Web

firebase.auth().signInWithCustomToken(token)
  .then((userCredential) => {
    // Signed in
    var user = userCredential.user;
    // ...
  })
  .catch((error) => {
    var errorCode = error.code;
    var errorMessage = error.message;
    // ...
  });

Web

import { getAuth, signInWithCustomToken } from "firebase/auth";

const auth = getAuth();
signInWithCustomToken(auth, token)
  .then((userCredential) => {
    // Signed in
    const user = userCredential.user;
    // ...
  })
  .catch((error) => {
    const errorCode = error.code;
    const errorMessage = error.message;
    // ...
  });

Jika autentikasi berhasil, pengguna akan login ke aplikasi klien Anda, dan akun yang ditentukan oleh uid 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 bagian Realtime Database Security Rules dan objek request.auth di bagian Cloud Storage Security Rules akan diisi dengan uid pengguna. Dalam hal ini, uid adalah 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 != null && request.auth.uid == "some-uid";
    }
  }
}

Jika token kustom berisi klaim tambahan, klaim tersebut dapat dirujuk dari 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 Audiens "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 saat token sudah tidak berlaku lagi, dalam satuan detik sejak epoch UNIX. Waktu ini bisa mencapai maksimum 3600 detik lebih lama daripada iat.
Catatan: ini hanya mengontrol kapan token kustom berhenti berlaku. Namun, setelah Anda memproses login menggunakan signInWithCustomToken(), pengguna akan tetap login di perangkatnya hingga validitas sesi berakhir atau pengguna tersebut logout.
uid ID unik dari pengguna yang login harus berupa string dengan panjang 1-128 karakter. uid yang lebih singkat menawarkan performa yang lebih baik.
claims (opsional) Klaim kustom opsional yang akan disertakan di variabel auth/request.auth Security Rules

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

Menggunakan 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 token kustom dibuat, 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 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 "Enable 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 "Service Account Token Creator" ke akun layanan yang bersangkutan, biasanya {project-name}@appspot.gserviceaccount.com:

  1. Buka halaman IAM dan admin di Google Cloud console.
  2. Pilih project Anda lalu klik "Lanjutkan".
  3. Klik ikon edit yang sesuai dengan akun layanan yang ingin Anda perbarui.
  4. Klik "Add Another Role".
  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 mengubah peran menggunakan alat command line gcloud.

Gagal menentukan akun layanan

Jika Anda mendapatkan pesan error yang mirip 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 di-deploy 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.