sumer5020/laravel-keycloak-guard
Composer 安装命令:
composer require sumer5020/laravel-keycloak-guard
包简介
A Keycloak Guard for Laravel — robsontenorio/laravel-keycloak-guard fork
README 文档
README
Note: This Version is not production ready yet, still in active development
robsontenorio/laravel-keycloak-guard fork — Full support for Laravel 13, Keycloak 26+, and the Keycloak 26 Organizations feature (
--features=organization).
What's New VS the Original Package:
| Feature | Original | This Version |
|---|---|---|
| Laravel 13 support | ❌ | ✅ |
| Keycloak 26 JWKS auto-discovery | ❌ | ✅ |
| Automatic key rotation support | ❌ | ✅ |
| Keycloak 26 Organizations | ❌ | ✅ |
KeycloakOrg facade |
❌ | ✅ |
keycloak.org middleware |
❌ | ✅ |
keycloak.org.role middleware |
❌ | ✅ |
HasOrganizations trait |
❌ | ✅ |
TokenUser (no-DB mode) |
Partial | ✅ |
Auth::payload() |
❌ | ✅ |
Auth::roles() |
❌ | ✅ |
RealmResource (API Resource) |
❌ | ✅ |
keycloak:doctor command |
❌ | ✅ |
ActingAsKeycloak (Testing Trait) |
❌ | ✅ |
| PHP 8.3 constructor promotion | ❌ | ✅ |
Requirements
- PHP 8.3+
- Laravel 12.x / 13.x
- Keycloak 21+ (Organizations require 26+)
Installation
composer require sumer5020/laravel-keycloak-guard
Publish the config:
php artisan vendor:publish --tag=keycloak-config
Optionally publish migrations (only if using organizations.sync_to_database):
php artisan vendor:publish --tag=keycloak-migrations php artisan migrate
Health & Diagnostics
The package includes a "Doctor" command to help you troubleshoot connectivity and configuration issues.
php artisan keycloak:doctor
It performs the following checks:
- Configuration: Verifies that required environment variables are set.
- Connectivity: Attempts to fetch the OIDC discovery document (
.well-known/openid-configuration) from Keycloak. - Organizations: Checks if the organization feature is correctly configured.
API Resources
If you need to expose your Keycloak configuration (realm name, base URL, issuer, JWKS URI) to your frontend (e.g., for keycloak-js), you can use the built-in RealmResource.
use KeycloakGuard\Http\Resources\RealmResource; Route::get('/keycloak/config', function () { return new RealmResource([]); });
This returns a standardized JSON response:
{
"realm": "my-realm",
"base_url": "https://keycloak.example.com",
"jwks_uri": "https://keycloak.example.com/realms/my-realm/protocol/openid-connect/certs",
"issuer": "https://keycloak.example.com/realms/my-realm"
}
Basic Setup
1. Configure the Guard
config/auth.php:
'guards' => [ 'api' => [ 'driver' => 'keycloak', 'provider' => 'users', ], ], 'providers' => [ 'users' => [ 'driver' => 'eloquent', 'model' => App\Models\User::class, ], ],
2. Environment Variables
# ── Required ──────────────────────────────────────────────── KEYCLOAK_BASE_URL=https://keycloak.example.com KEYCLOAK_REALM=my-realm KEYCLOAK_ALLOWED_RESOURCES=my-laravel-api # ── Key / Token Verification ──────────────────────────────── # Option A: Static public key (from Realm Settings → Keys → RS256 → Public Key) KEYCLOAK_REALM_PUBLIC_KEY=MIIBIjANBgkq... # Option B: JWKS auto-discovery (recommended for Keycloak 26+, supports key rotation) # Leave KEYCLOAK_REALM_PUBLIC_KEY empty — the package constructs the JWKS URI automatically # Or set it explicitly: KEYCLOAK_JWKS_URI=https://keycloak.example.com/realms/my-realm/protocol/openid-connect/certs KEYCLOAK_JWKS_CACHE_TTL=3600 # ── User Matching ──────────────────────────────────────────── KEYCLOAK_TOKEN_PRINCIPAL_ATTRIBUTE=sub # JWT claim used as identifier KEYCLOAK_USER_PROVIDER_CREDENTIAL=keycloak_id # DB column to match against # ── Optional ───────────────────────────────────────────────── KEYCLOAK_APPEND_DECODED_TOKEN=true # Attach $user->token (stdClass) KEYCLOAK_LEEWAY=30 # Clock skew tolerance (seconds) KEYCLOAK_LOAD_USER_FROM_DATABASE=true KEYCLOAK_TOKEN_ENCRYPTION_ALGORITHM=RS256 # ── Organizations (Keycloak 26+) ───────────────────────────── KEYCLOAK_ORGANIZATIONS_ENABLED=true KEYCLOAK_ORGANIZATION_HEADER=X-Organization # Header for selecting active org KEYCLOAK_ORGANIZATION_REQUIRE=false # 403 if no org in token? KEYCLOAK_ORGANIZATION_SYNC_DB=false # Persist orgs to database?
3. Protect Routes
// routes/api.php // Basic auth Route::middleware('auth:api')->group(function () { Route::get('/me', fn(Request $r) => $r->user()); }); // Role-based access Route::middleware(['auth:api', 'keycloak.role:admin'])->group(function () { Route::get('/admin', AdminController::class); }); // Organization-scoped routes (Keycloak 26+) Route::middleware(['auth:api', 'keycloak.org'])->group(function () { Route::apiResource('projects', ProjectController::class); // Org admin only Route::middleware('keycloak.org.role:admin')->group(function () { Route::apiResource('members', MemberController::class); }); });
Keycloak 26 Organizations
How It Works
When Keycloak is started with --features=organization, it injects an organization claim into the JWT:
{
"sub": "f7a8b9c0-...",
"preferred_username": "john@acme.com",
"organization": {
"acme-corp": {
"id": "3fa85f64-...",
"name": "Acme Corporation",
"roles": ["admin", "member"]
}
}
}
For users in multiple organizations, the client sends an X-Organization header to select the active one.
The KeycloakOrg Facade
use KeycloakGuard\Facades\KeycloakOrg; // Get the active organization for this request $org = KeycloakOrg::current(); // => ['alias' => 'acme-corp', 'id' => '3fa85f64-...', 'name' => 'Acme Corporation', 'roles' => ['admin']] // Get all organizations the user belongs to (from the token) $orgs = KeycloakOrg::all(); // Check membership KeycloakOrg::belongsTo('acme-corp'); // bool KeycloakOrg::hasOrganizations(); // bool // Role checks within the active organization KeycloakOrg::hasRole('admin'); // bool KeycloakOrg::hasRole('admin', 'acme-corp'); // bool (explicit org) KeycloakOrg::hasAnyRole(['admin', 'billing-manager']); // bool // Sync to DB (requires sync_to_database = true) KeycloakOrg::syncToDatabase(auth()->user());
HasOrganizations Trait
Add to your User model for database relationships and convenient helpers:
use KeycloakGuard\Traits\HasOrganizations; class User extends Authenticatable { use HasOrganizations; }
Then use:
// From JWT (no DB query) $user->currentOrganization(); // array|null $user->hasOrgRole('admin'); // bool $user->belongsToOrg('acme-corp'); // bool // From database (requires sync_to_database) $user->organizations()->get(); $user->organizations()->where('alias', 'acme-corp')->first();
Using Organization Context in Controllers
class ProjectController extends Controller { public function index(): JsonResponse { // Array from JWT — no DB query $org = KeycloakOrg::current(); // Eloquent model — only if sync_to_database = true $orgModel = app('current_organization_model'); return response()->json( Project::where('organization_id', $orgModel->id)->paginate() ); } public function store(StoreProjectRequest $request): JsonResponse { $org = app('current_organization_model'); $project = Project::create([ ...$request->validated(), 'organization_id' => $org->id, ]); return response()->json($project, 201); } }
Auth Guard Methods
use Illuminate\Support\Facades\Auth; // Standard Laravel Auth::user(); // Authenticatable|null Auth::check(); // bool Auth::id(); // mixed // Keycloak-specific Auth::token(); // string|null — full decoded token as JSON Auth::payload(); // stdClass|null — decoded token object Auth::roles(); // array — all realm + resource roles (with inheritance) Auth::roles('my-api'); // array — resource-specific roles (includes realm roles) Auth::hasRole('admin'); // bool Auth::hasRole('editor', 'my-api'); // bool — resource-scoped // Organizations Auth::organizations(); // OrganizationService
Middleware Reference
| Middleware | Description | Example |
|---|---|---|
keycloak.role:admin |
Requires realm or any resource role | middleware('keycloak.role:admin,editor') |
keycloak.role:editor|my-api |
Requires role in specific resource | middleware('keycloak.role:editor|my-api') |
keycloak.org |
Resolves active org from JWT + header | middleware('keycloak.org') |
keycloak.org.role:admin |
Requires org-level role | middleware('keycloak.org.role:admin') |
Note: The
keycloak.rolemiddleware supports role inheritance. If a user has a realm-level role (e.g.,admin), they will pass checks for any resource-specific role requirements.
No-Database Mode
If your API doesn't have a users table, set:
KEYCLOAK_LOAD_USER_FROM_DATABASE=false KEYCLOAK_APPEND_DECODED_TOKEN=true
Auth::user() returns a TokenUser built from JWT claims:
$user = Auth::user(); $user->sub; // UUID $user->preferred_username; // username $user->email; // email $user->token; // full stdClass of JWT claims
Custom User Provider
// app/Providers/CustomUserProvider.php class CustomUserProvider implements UserProvider { public function customRetrieveUser(stdClass $token, array $credentials): ?User { return User::updateOrCreate( ['keycloak_id' => $token->sub], [ 'name' => $token->name ?? $token->preferred_username, 'email' => $token->email, ] ); } // ... other required methods }
KEYCLOAK_USER_PROVIDER_CUSTOM_RETRIEVE_METHOD=customRetrieveUser
Keycloak 26 — Admin Configuration Checklist
In your Keycloak 26 admin console:
Realm Settings → Keys → RS256: Copy the public key for KEYCLOAK_REALM_PUBLIC_KEY, or leave blank to use JWKS auto-discovery.
Client Settings (for your Laravel API client):
- Client protocol:
openid-connect - Access type:
confidential - Standard flow: OFF (API only)
- Direct access grants: OFF
- Service accounts enabled: ON (for M2M)
Organizations (Keycloak 26+):
- Realm Settings → General → Enable Organizations: ON
- OR start Keycloak with
--features=organization - Add the
organizationmapper to your client's token scope
Token Mappers — ensure your client has:
organizationclaim mapper (type: Organization Membership)realm rolesmapperclient rolesmapper
Testing
The package provides a powerful ActingAsKeycloak trait to mock authenticated states in your feature tests without needing a live Keycloak server.
namespace Tests\Feature; use KeycloakGuard\Testing\ActingAsKeycloak; use Tests\TestCase; class ExampleTest extends TestCase { use ActingAsKeycloak; public function test_api_is_protected(): void { // Simple authentication $this->withKeycloakToken(['name' => 'John Doe']) ->getJson('/api/me') ->assertOk(); // Authentication with specific roles $this->withKeycloakToken([ 'realm_access' => ['roles' => ['admin']] ])->getJson('/api/admin')->assertOk(); } public function test_organization_access(): void { // Authenticate with organization membership $this->withKeycloakOrganization([ 'acme' => ['id' => 'org-1', 'name' => 'Acme Corp', 'roles' => ['member']] ]) ->withOrganization('acme') // Select active org via header ->getJson('/api/projects') ->assertOk(); } }
License
sumer5020/laravel-keycloak-guard 适用场景与选型建议
sumer5020/laravel-keycloak-guard 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 5 次下载、GitHub Stars 达 0, 最近一次更新时间为 2026 年 03 月 23 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「oauth2」 「auth」 「laravel」 「saas」 「jwt」 「guard」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 sumer5020/laravel-keycloak-guard 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 sumer5020/laravel-keycloak-guard 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 sumer5020/laravel-keycloak-guard 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
Laravel Multiauth package
A lightweight and powerful OAuth 2.0 authorization and resource server library with support for all the core specification grants. This library will allow you to secure your API with OAuth and allow your applications users to approve apps that want to access their data from your API.
This package provides a flexible way to add Role-based Permissions to Laravel 6.x
Email Toolkit Plugin for CakePHP
The missing social authentication for thephpleague/oauth2-server, adapted from schedula/league-oauth2-social
Alfabank REST API integration
统计信息
- 总下载量: 5
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 42
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-03-23