visin/laravel-entra-auth
Composer 安装命令:
composer require visin/laravel-entra-auth
包简介
Shared Microsoft Entra ID client-credentials JWT validation and token acquisition for Laravel services.
README 文档
README
Shared Microsoft Entra ID client-credentials auth for Laravel services.
One central implementation of both halves of service-to-service auth, so every backend validates and acquires tokens the exact same way:
- Resource server: validate incoming Entra client-credentials JWTs
(signature via tenant JWKS, expiry, audience, tenant, issuer, app roles),
with the OpenID discovery document and JWKS cached in the app cache so
validation does not call
login.microsoftonline.comon every request. - Client: acquire app-only access tokens with the OAuth2 client credentials flow, cached until shortly before expiry.
Battle-tested in production service-to-service auth, so validation behavior mirrors what Entra actually emits for client-credentials tokens rather than only what the docs describe.
Why a shared package
Every service hand-rolling JWT validation is a drift risk: small differences (audience forms, tenant pinning, caching, role extraction) creep in per service and are invisible until one of them becomes a security hole. This package centralizes the logic behind one tested implementation:
- Accepts both audience forms Entra emits for the same app registration:
the app ID URI (
api://<guid>) and the bare app/client guid. - Pins the token to the configured tenant (
tid) and validates the issuer against the tenant's published OpenID configuration. - Extracts the application client id from
azp/appid/client_id. - Extracts app roles from the
rolesclaim, optionally filtered against a configured whitelist.
Installation
The package is available on Packagist:
composer require visin/laravel-entra-auth
The service provider is auto-discovered. Publish the config if you need to override defaults:
php artisan vendor:publish --tag=entra-auth-config
Configuration
# Resource server (incoming token validation) ENTRA_AUTH_TENANT_ID=<entra-tenant-guid> ENTRA_AUTH_AUDIENCE=api://<this-api-app-registration-guid> # Client (outgoing token acquisition), only needed when calling other APIs ENTRA_AUTH_CLIENT_ID=<this-service-app-registration-guid> ENTRA_AUTH_CLIENT_SECRET=<client-secret> ENTRA_AUTH_SCOPE=api://<target-api-app-registration-guid>/.default
The protected API's app registration must issue v2 access tokens
("api": { "requestedAccessTokenVersion": 2 } in the app manifest).
Protecting routes (resource server)
Two middleware aliases are registered: entra.auth validates the bearer
token, entra.role:<role> requires an app role from the token's roles
claim.
Route::middleware(['entra.auth', 'entra.role:invoices.write']) ->post('/v1/invoices', [InvoiceController::class, 'store']);
The validated token is available on the request:
use Visin\EntraAuth\AccessToken; use Visin\EntraAuth\Http\Middleware\AuthenticateEntraToken; /** @var AccessToken $token */ $token = $request->attributes->get(AuthenticateEntraToken::REQUEST_ATTRIBUTE); $token->tenantId; // issuing tenant guid $token->clientId; // calling app registration guid $token->roles; // app roles from the token $token->claims; // full decoded claims (stdClass)
Services that map callers to database records (client registries, audit logging, per-client authorization) can skip the middleware and consume the validator directly:
use Visin\EntraAuth\Contracts\AccessTokenValidator; use Visin\EntraAuth\Exceptions\InvalidAccessToken; $accessToken = app(AccessTokenValidator::class)->validate($bearerToken); // then e.g. look up the caller by ($accessToken->tenantId, $accessToken->clientId)
To restrict which roles are ever passed through, set a whitelist in
config/entra-auth.php:
'allowed_roles' => ['invoices.read', 'invoices.write', 'invoices.delete'],
Calling another Entra-protected API (client)
use Illuminate\Support\Facades\Http; use Visin\EntraAuth\Client\ClientCredentialsTokenProvider; $token = app(ClientCredentialsTokenProvider::class)->token(); $response = Http::withToken($token)->post('https://api.example.com/v1/invoices', [ 'number' => 'INV-2026-001', 'amount' => 1250, ]);
Tokens are cached in the app cache and refreshed 60 seconds before expiry.
Testing your app against this package
Bind a fake validator in your tests instead of faking HTTP:
use Visin\EntraAuth\AccessToken; use Visin\EntraAuth\Contracts\AccessTokenValidator; $this->app->singleton(AccessTokenValidator::class, fn () => new class implements AccessTokenValidator { public function validate(string $token): AccessToken { return new AccessToken('tenant', 'client-a', ['invoices.read'], new \stdClass); } });
Running the package tests
composer install vendor/bin/phpunit
Without host PHP, via Docker:
docker run --rm -v "$PWD":/app -w /app composer:2 sh -c 'composer install && vendor/bin/phpunit'
Requirements
- PHP 8.3+
- Laravel 12 or 13
- firebase/php-jwt 6.10+ or 7.x
License
MIT
统计信息
- 总下载量: 0
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 1
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-07-14