authn-sh / sdk-php-laravel
Laravel package for authn.sh — service provider, middleware, Blade directives, facade. Wraps authn-sh/sdk-php for Laravel 11+.
Requires
- php: ^8.2
- ext-json: *
- authn-sh/sdk-php: ^0.7
- illuminate/cache: ^11.0 || ^12.0
- illuminate/contracts: ^11.0 || ^12.0
- illuminate/support: ^11.0 || ^12.0
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.10
- mockery/mockery: ^1.6
- nyholm/psr7: ^1.8
- orchestra/testbench: ^9.0 || ^10.0
- pestphp/pest: ^3.0
- phpstan/phpstan: ^2.1
README
Laravel package for authn.sh — auto-discovered service provider, container bindings, middleware, Blade directives, and an Authn facade. Wraps authn-sh/sdk-php for Laravel 11+.
Requirements
- PHP 8.2+
- Laravel 11+ (
^11.0 || ^12.0) - A PSR-18 HTTP client (Guzzle, Symfony HttpClient, etc.) — required by the underlying
authn-sh/sdk-phpSDK and auto-discovered viaphp-http/discovery.
Install
composer require authn-sh/sdk-php-laravel
The service provider auto-discovers. Publish the config:
php artisan vendor:publish --tag=authn-config
Middleware
Register authn on routes that require a valid session JWT:
Route::middleware('authn')->group(function () { Route::get('/dashboard', DashboardController::class); });
Blade directives
@authnSignedIn / @authnSignedOut
@authnSignedIn <p>Welcome back, {{ Authn::auth()->sub }}</p> @endauthnSignedIn @authnSignedOut <a href="/login">Sign in</a> @endauthnSignedOut
@authnHas
Evaluates organization roles and permissions from the active session JWT:
@authnHas('role:org:admin') <a href="/admin">Admin panel</a> @else <p>You do not have admin access.</p> @endauthnHas @authnHas('permission:org:billing:manage') <a href="/billing">Billing</a> @endauthnHas
The argument must be prefixed with role: or permission:. Any other prefix raises an InvalidArgumentException at render time so typos fail loudly.
@authnHasConnectedAccount
Renders the inner block when the active session JWT advertises an ExternalAccount linked to the named OAuth provider:
@authnHasConnectedAccount('google') <p>Connected to Google.</p> @else <a href="/sign-in/sso-callback?provider=google">Connect Google</a> @endauthnHasConnectedAccount
The matching route middleware authn.connected:<provider_key> fail-closes by redirecting to authn.connected_accounts.redirect_url (the {provider} placeholder is substituted with the route argument):
Route::get('/integrations/google', [GoogleController::class, 'show']) ->middleware(['authn', 'authn.connected:google']);
@authnHasPasskey
Renders the inner block based on the active session's passkey signal. Two modes:
verified(default) — matches when the current session was authenticated by a passkey first-factor (VerifiedClaims::wasVerifiedByPasskey()).enrolled— matches whenever the user has at least one verified passkey on file (VerifiedClaims::hasPasskey()).
@authnHasPasskey <a href="/account/api-keys">Manage API keys</a> @else <p>This area requires a passkey sign-in.</p> @endauthnHasPasskey @authnHasPasskey('enrolled') <p>You already have a passkey on file.</p> @else <a href="/user/security/passkeys">Add your first passkey</a> @endauthnHasPasskey
The matching route middleware authn.requires_passkey[:<mode>] fail-closes by redirecting unauthenticated requests to authn.url.sign_in and signed-in-but-under-authenticated requests to authn.passkey.enroll_url:
Route::get('/account/security', [SecurityController::class, 'show']) ->middleware(['authn', 'authn.requires_passkey']); // verified (default) Route::get('/account/api-keys', [ApiKeyController::class, 'index']) ->middleware(['authn', 'authn.requires_passkey:verified']); Route::get('/account/passkey-required-area', [Controller::class, 'show']) ->middleware(['authn', 'authn.requires_passkey:enrolled']);
The default mode is configurable via authn.passkey.default_strict_mode (env AUTHN_PASSKEY_DEFAULT_STRICT_MODE).
@authnHasEnterpriseAccount
Renders the inner block based on the active session's enterprise-SSO signal. Two modes for the no-argument form:
verified(default) — matches when the current session was authenticated through an enterprise IdP (VerifiedClaims->enterpriseConnectionId !== null).linked— matches whenever the user has at least one linkedEnterpriseAccount, whether or not the current session is enterprise-SSO.
@authnHasEnterpriseAccount <p>You signed in via enterprise SSO.</p> @else <a href="/sign-in/enterprise-sso">Sign in with your organization</a> @endauthnHasEnterpriseAccount
With a connection-id argument, the directive matches when the active session's enterpriseConnectionId equals it OR when the user has a linked EnterpriseAccount for that connection:
@authnHasEnterpriseAccount('entcon_01HKX9SY9V7H7TF8C8K7J9X4ZA') <a href="/admin/enterprise">Org admin</a> @endauthnHasEnterpriseAccount
The matching route middleware authn.requires_enterprise_sso[:<mode>] fail-closes by redirecting unauthenticated requests to authn.url.sign_in and signed-in-but-under-authenticated requests to authn.enterprise_sso.redirect_url:
Route::get('/workspace', [WorkspaceController::class, 'show']) ->middleware(['authn', 'authn.requires_enterprise_sso']); // verified (default) Route::get('/workspace/admin', [WorkspaceAdminController::class, 'show']) ->middleware(['authn', 'authn.requires_enterprise_sso:verified']); Route::get('/workspace/profile', [ProfileController::class, 'show']) ->middleware(['authn', 'authn.requires_enterprise_sso:linked']);
The default mode is configurable via authn.enterprise_sso.default_strict_mode (env AUTHN_ENTERPRISE_SSO_DEFAULT_STRICT_MODE).
Facade methods
use Authn\Sdk\Laravel\Facades\Authn; $claims = Authn::auth(); // ?VerifiedClaims — null outside an authn-authenticated request Authn::hasRole('org:admin'); // bool Authn::hasPermission('org:foo:bar'); // bool Authn::hasPasskey(); // bool — defaults to the configured strict mode ("verified") Authn::hasPasskey('enrolled'); // bool — true when the user has any verified passkey Authn::hasEnterpriseSso(); // bool — defaults to the configured strict mode ("verified") Authn::hasEnterpriseSso('linked'); // bool — true when the user has any linked EnterpriseAccount
Development
composer install composer test # Pest + Orchestra Testbench composer phpstan # PHPStan + Larastan @ level 10 composer pint # code style check composer pint:fix # apply fixes
CI runs the full suite on the PHP 8.2 / 8.3 / 8.4 / 8.5 × Laravel 11 / 12 matrix.