API token guard
Use this when your own API accepts access tokens issued by Anikeen ID – e.g. a mobile app or another service calls your API with the user's Anikeen ID token.
Configure the guard
The package registers an anikeen guard driver and an anikeen user provider. Reference them in config/auth.php:
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'anikeen',
'provider' => 'anikeen',
],
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
'anikeen' => [
'driver' => 'anikeen',
'model' => App\Models\User::class,
'column' => 'anikeen_id', // optional, see below
],
],The guard accepts Authorization: Bearer <access token> with an access token issued by Anikeen ID (a personal access token or an OAuth token, a JWT). It
- verifies the RS256 signature with Anikeen ID's public key and the time claims (
exp,nbf,iat, 60 seconds leeway), - looks the token's
sub(the user's Anikeen ID) up in the columnanikeen_idof the user model: the provider'scolumn, elseservices.anikeen.user_id_column, elseAnikeenId::useUserIdField().
Tokens of unknown users, client credentials tokens (no user), expired or forged tokens leave the request unauthenticated, so auth:api answers 401 {"message": "Unauthenticated."} to JSON requests.
Calling your API from your own frontend
To call your API from your own frontend with the user's session, append the middleware to the web group in bootstrap/app.php:
->withMiddleware(function (Middleware $middleware) {
$middleware->web(append: [
\Anikeen\Id\Http\Middleware\CreateFreshApiToken::class,
]);
})Scopes
Add Anikeen\Id\HasAnikeenTokens to the user model; the token's scopes are then available:
$request->user()->anikeenTokenCan('user'); // has the scope (or `*`)
$request->user()->anikeenTokenCanAny(['user', 'user:read']);or as middleware (403 without the scope):
use Anikeen\Id\Http\Middleware\CheckForAnyScope;
use Anikeen\Id\Http\Middleware\CheckScopes;
Route::middleware(['auth:api', CheckScopes::using('user')])->post(/* … */); // all scopes
Route::middleware(['auth:api', CheckForAnyScope::using('user', 'user:read')])->get(/* … */); // any scopeWhich app the token belongs to beta
The token also tells which app it was issued to. Anikeen ID adds a client claim with the app's name, whether Anikeen trusts it and whether it is a personal access token; older tokens have none:
$request->user()->anikeenTokenClientId(); // the OAuth client (the token's `aud`)
$request->user()->anikeenTokenClientName(); // e.g. "Shop", or null
$request->user()->anikeenTokenClientTrusted(); // true only for trusted apps
$request->user()->anikeenTokenIsPersonalAccess(); // a personal access token, not an app'sThe guard also sets the request attribute oauth_client_name.
Users that are not known yet beta
By default a token of a user your app does not know leaves the request unauthenticated. To create the user instead – e.g. because a trusted app calls your API for one of its users, who never signed in to your app – register a callback, e.g. in AppServiceProvider::boot(). It gets the token's claims, the user's profile (GET /api/v1/user read with that token; with verify_revocation the answer of that check is reused) and the raw token, and returns the new user or null:
use Anikeen\Id\AnikeenId;
AnikeenId::provisionUsersUsing(function (stdClass $claims, array $profile, string $token) {
if (($claims->client->trusted ?? null) !== true || empty($profile['email_verified_at'])) {
return null; // 401 as before
}
return User::create([
'anikeen_id' => $claims->sub,
'name' => $profile['name'],
'email' => $profile['email'],
]);
});Exceptions the callback throws are not caught (e.g. an HttpException with 409 for an email address that belongs to another account). It is not called for the token cookie or for client credentials tokens.
Options
In config/services.php under anikeen:
| Key | Default | |
|---|---|---|
public_key | – | Anikeen ID's public key (PEM; \n escapes allowed) |
public_key_path | – | path to that key; without both, the key shipped for the mode is used |
user_id_column | anikeen_id | column holding the user's Anikeen ID |
verify_revocation | false | also ask Anikeen ID (GET /api/v1/user) whether the token is still valid, so revoked tokens stop working before they expire; fails closed when Anikeen ID is unreachable |
revocation_cache_ttl | 300 | seconds that answer is cached per token |
The key is read once per process.
Testing
AnikeenId::actingAs($user, ['user']) signs a user in without a token. Further claims go in the fourth argument beta:
AnikeenId::actingAs($user, ['console-containers'], 'api', [
'aud' => $clientId,
'client' => ['name' => 'Shop', 'trusted' => true],
]);