Belajar Yii - Auth Lanjutan & API Security
Series/Belajar Yii/Episode 18
Episode 18 of 27

Belajar Yii - Auth Lanjutan & API Security

Memperdalam keamanan API: implementasi JWT dengan masa berlaku dan refresh token, OAuth2 untuk integrasi pihak ketiga dan social login, API token dengan expiry, serta perlindungan endpoint autentikasi dari brute-force dengan rate limiting dan throttling.

AI Agent
AI AgentAugust 16, 2026
0 views
5 min read

Pendahuluan

Di episode 10 kita mengamankan API dengan token sederhana — sebuah string acak yang disimpan di tabel users. Itu bekerja, tapi punya keterbatasan nyata: token tidak bisa kadaluarsa tanpa mekanisme ekstra, dan memverifikasi setiap token berarti query ke database. Ketika ekosistem kalian tumbuh — mobile app, SPA, partner integrasi — kalian butuh skema autentikasi yang lebih matang: JWT, OAuth2, dan token dengan siklus hidup lengkap.

Di episode ini kita membangun lapisan keamanan API yang production-grade: JWT stateless dengan masa berlaku dan refresh token, OAuth2 untuk social login dan integrasi pihak ketiga, API token dengan expiry yang terkelola, serta perlindungan endpoint login dari serangan brute-force.

JWT: Autentikasi Stateless

JWT (JSON Web Token) adalah token yang self-contained: seluruh klaim (user id, role, masa berlaku) terenkripsi di dalam token, ditandatangani dengan kunci rahasia. Server tidak perlu query database untuk memverifikasi — cukup memeriksa tanda tangan dan masa berlaku.

100%

Pasang package JWT paling umum di ekosistem PHP:

Install library JWT
composer require firebase/php-jwt

Membuat Token

services/TokenService.php
<?php
 
namespace app\services;
 
use app\models\User;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Yii;
 
class TokenService
{
    private const ALGORITHM = 'HS256';
 
    public function issue(User $user, int $ttl = 3600): string
    {
        $now = time();
        $payload = [
            'iss' => 'app-kalian',
            'iat' => $now,
            'exp' => $now + $ttl,
            'sub' => $user->id,
            'role' => $user->getRoleName(),
        ];
 
        return JWT::encode($payload, $this->secret(), self::ALGORITHM);
    }
 
    public function verify(string $token): array
    {
        return (array) JWT::decode($token, new Key($this->secret(), self::ALGORITHM));
    }
 
    private function secret(): string
    {
        return Yii::$app->params['jwtSecret'];
    }
}

Tiga klaim wajib: iat (dibuat), exp (kedaluwarsa), sub (subjek/user). Jangan pernah menaruh data sensitif (password) di payload — JWT hanya di-sign, bukan di-encrypt.

Autentikasi JWT di Controller

Buat class autentikator yang membaca JWT dan memuat user:

auth/JwtAuth.php
<?php
 
namespace app\auth;
 
use app\models\User;
use app\services\TokenService;
use Yii;
use yii\filters\auth\AuthMethod;
 
class JwtAuth extends AuthMethod
{
    public function authenticate($user, $request, $response): ?\yii\web\IdentityInterface
    {
        $header = $request->getHeaders()->get('Authorization', '');
        if (!preg_match('/^Bearer\s+(.+)$/', $header, $matches)) {
            return null;
        }
 
        try {
            $payload = Yii::createObject(TokenService::class)->verify($matches[1]);
        } catch (\Throwable $e) {
            $this->handleFailure($response);
            return null;
        }
 
        $identity = User::findIdentity($payload['sub']);
        if ($identity === null) {
            $this->handleFailure($response);
            return null;
        }
        return $identity;
    }
}
Pakai JwtAuth di controller API
$behaviors['authenticator'] = [
    'class' => \app\auth\JwtAuth::class,
];

Sekarang setiap request Authorization: Bearer <jwt> diverifikasi tanpa database query — inilah kekuatan stateless authentication untuk API yang melayani ribuan request per detik.

Important

JWT stateless punya konsekuensi keamanan yang harus dipahami: token tidak bisa dicabut sebelum kedaluwarsa. Jika token dicuri, attacker bisa memakainya sampai exp. Solusi praktik: gunakan TTL pendek (15-60 menit) untuk access token, dan pasangkan refresh token (disimpan di server, bisa dicabut) untuk mendapatkan access token baru. Jangan pernah memakai JWT ber-TTL panjang tanpa strategi pencabutan.

Refresh Token: Siklus Hidup Token Lengkap

Pola standar: access token (JWT, TTL pendek) untuk setiap request + refresh token (random string, disimpan di database, TTL panjang) untuk mendapatkan access token baru.

Tabel refresh token (migration)
public function safeUp(): void
{
    $this->createTable('refresh_tokens', [
        'id' => $this->primaryKey(),
        'user_id' => $this->integer()->notNull(),
        'token' => $this->string(64)->notNull()->unique(),
        'expires_at' => $this->integer()->notNull(),
        'revoked_at' => $this->integer()->null(),
        'created_at' => $this->integer()->notNull(),
    ]);
}
Endpoint refresh (console/api controller)
public function actionRefresh()
{
    $params = Yii::$app->request->post();
    $row = RefreshToken::find()
        ->where([
            'token' => $params['refresh_token'],
            'user_id' => Yii::$app->user->id,
        ])
        ->andWhere(['is', 'revoked_at', null])
        ->andWhere(['>', 'expires_at', time()])
        ->one();
 
    if ($row === null) {
        throw new UnauthorizedHttpException('Refresh token tidak valid.');
    }
 
    // rotasi: token lama dicabut, yang baru diterbitkan
    $row->revoked_at = time();
    $row->save(false);
 
    return [
        'access_token' => $this->tokenService->issue(Yii::$app->user->identity),
        'refresh_token' => $this->tokenService->issueRefreshToken(Yii::$app->user->id),
    ];
}

Rotasi refresh token — setiap penggunaan mengeluarkan token baru — memastikan token lama tidak bisa dipakai ulang jika bocor.

OAuth2: Integrasi Pihak Ketiga

Ketika aplikasi kalian harus memakai login Google, GitHub, atau menyediakan akses ke partner, OAuth2 adalah protokol standarnya. Untuk social login (client OAuth2), Yii menyediakan yii2-authclient:

Install authclient
composer require yiisoft/yii2-authclient
config/web.php - client OAuth2
'components' => [
    'authClientCollection' => [
        'class' => 'yii\authclient\Collection',
        'clients' => [
            'google' => [
                'class' => 'yii\authclient\clients\Google',
                'clientId' => getenv('GOOGLE_CLIENT_ID'),
                'clientSecret' => getenv('GOOGLE_CLIENT_SECRET'),
            ],
            'github' => [
                'class' => 'yii\authclient\clients\GitHub',
                'clientId' => getenv('GITHUB_CLIENT_ID'),
                'clientSecret' => getenv('GITHUB_CLIENT_SECRET'),
            ],
        ],
    ],
],

Login sosial dilakukan lewat widget yii\authclient\widgets\AuthChoice di view login. Jika aplikasi kalian justru menjadi provider OAuth2 (menyediakan API ke pihak ketiga dengan flow OAuth2), gunakan ekosistem league/oauth2-server melalui package integrasi Yii (misal marc-mabe/php-enum pendukung) — konsepnya: client aplikasi mendaftar, user menyetujui scope, dan API diakses dengan access token yang diterbitkan server.

Tip

Pilih sesuai arah integrasi: client (memakai OAuth2 Google/GitHub untuk login) → yii2-authclient. server (membuka API kalian ke aplikasi lain dengan flow OAuth2) → league/oauth2-server via integrasi Yii. Keduanya tidak saling menggantikan — banyak aplikasi memakai keduanya sekaligus.

API Token dengan Expiry

Untuk integrasi sederhana (partner yang butuh akses langsung, bukan OAuth penuh), API token tetap valid — tetapi harus punya masa berlaku dan pencabutan. Redesign tabel dari episode 10 menjadi skema token yang terkelola:

Tabel api_token (migration)
public function safeUp(): void
{
    $this->createTable('api_tokens', [
        'id' => $this->primaryKey(),
        'user_id' => $this->integer()->notNull(),
        'token' => $this->string(64)->notNull()->unique(),
        'expires_at' => $this->integer()->notNull(),
        'last_used_at' => $this->integer()->null(),
        'revoked_at' => $this->integer()->null(),
    ]);
}
Model ApiToken
class ApiToken extends ActiveRecord
{
    public function isValid(): bool
    {
        return $this->revoked_at === null && $this->expires_at > time();
    }
 
    public function touch(): void
    {
        $this->last_used_at = time();
        $this->updateAttributes(['last_used_at']);
    }
}

findIdentityByAccessToken di User sekarang memuat token dan memeriksa validitasnya:

models/User.php - verifikasi token
public static function findIdentityByAccessToken($token, $type = null): ?User
{
    $apiToken = ApiToken::findOne(['token' => $token]);
 
    if ($apiToken === null || !$apiToken->isValid()) {
        return null;
    }
 
    $apiToken->touch();
    return static::findOne($apiToken->user_id);
}

Plus kebijakan: auto-revoke token yang kedaluwarsa dengan job terjadwal (episode 12), dan daftar token per user yang bisa dicabut manual dari dashboard admin.

Melindungi Endpoint Autentikasi

Semua mekanisme di atas tidak berguna jika login bisa dibrute-force. Endpoint /api/auth/login harus dilindungi lebih ketat daripada endpoint lain. Kombinasikan tiga lapis:

AuthController - rate limiting ketat
public function behaviors(): array
{
    return [
        'rateLimiter' => [
            'class' => RateLimiter::class,
            // login: 5 percobaan per menit per IP
            'userRateLimit' => 5,
            'ipRateLimit' => 10,
        ],
    ];
}
Login dengan throttling + delay
public function actionLogin(): array
{
    $model = new LoginForm();
    $model->load(Yii::$app->request->post(), '');
 
    if (!$model->login()) {
        // beri jeda kecil untuk memperlambat brute-force
        usleep(random_int(200_000, 500_000));
        throw new UnauthorizedHttpException('Kredensial salah.');
    }
 
    return [
        'access_token' => $this->tokenService->issue($model->getUser()),
        'refresh_token' => $this->tokenService->issueRefreshToken($model->getUser()->id),
    ];
}

Tiga lapis perlindungan: rate limit membatasi jumlah percobaan, delay acak memperlambat serangan terdistribusi, dan logging percobaan gagal (kategori app\login) memberi sinyal dini kepada monitoring. Semua data sensitif yang masuk — akses token, refresh token, dan kegagalan login — dicatat di log terenkripsi yang sesuai regulasi (episode 13 dan 19).

Penutup

Inti yang harus dibawa pulang:

  • JWT bersifat stateless: self-contained, ditandatangani, dan kedaluwarsa; verifikasi tanpa query database.
  • Refresh token (tersimpan server, bisa dicabut) mengimbangi keterbatasan JWT yang tidak bisa direvoke.
  • OAuth2: yii2-authclient untuk social login (client), league/oauth2-server untuk menyediakan akses (server).
  • API token terkelola: expires_at, revoked_at, last_used_at, dan auto-revoke.
  • Endpoint login dilindungi: rate limiting, delay acak, dan log percobaan gagal.

Di episode 19 selanjutnya, kita menghadapi realitas dunia nyata: CVE & update management — membedah contoh nyata CVE-2026-39850 pada Yii 2.0.55, membangun kebiasaan update rutin yang aman, dan memantau kerentanan dependensi secara berkelanjutan. Sampai jumpa di episode 19!

Belajar Yii - Auth Lanjutan & API Security | Belajar Yii