Skip to content

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:

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 column anikeen_id of the user model: the provider's column, else services.anikeen.user_id_column, else AnikeenId::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:

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:

php
$request->user()->anikeenTokenCan('user');               // has the scope (or `*`)
$request->user()->anikeenTokenCanAny(['user', 'user:read']);

or as middleware (403 without the scope):

php
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 scope

Which 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:

php
$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's

The 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:

php
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:

KeyDefault
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_columnanikeen_idcolumn holding the user's Anikeen ID
verify_revocationfalsealso 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_ttl300seconds 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:

php
AnikeenId::actingAs($user, ['console-containers'], 'api', [
    'aud' => $clientId,
    'client' => ['name' => 'Shop', 'trusted' => true],
]);

Anikeen ID developer documentation