Jwt-auth: Adding multiple columns as custom claims

Created on 17 Apr 2015  路  11Comments  路  Source: tymondesigns/jwt-auth

Is it possible to add multiple model columns as claims ?
I would like to add a second column from the model to the token payload (for instance: "role")
I found a post here: http://stackoverflow.com/questions/29014681/add-the-user-role-to-the-jwt-laravel-5-jwt-auth but I don't like the idea of hitting the database twice, is there another way of achieving this ?

Most helpful comment

@kitensei you should use the attempt method from JWTAuth. As it utilises laravel Guard to verify the credentials, and also allows you to pass an array of custom claims as the second param. E.g.

$token = JWTAuth::attempt($credentials, $customClaims);

All 11 comments

it is possible.
but u have to pay attention for token length. that means you should not add too much columns. it will affect token length

How should I implement the custom columns without hitting the database twice ?
At moment I log my user in without using JWTAuth, and create the token manually with my custom claims as I have a model, but is it possible to do it by using directly the JWTAuth::attempt() ?

Edit: here what I have done actually:

<?php namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Auth\Guard;
use Illuminate\Http\Request;

class AuthController extends Controller
{
    /**
     * @var Guard
     */
    protected $auth;

    /**
     * Create a new authentication controller instance.
     * @param Guard $auth
     */
    public function __construct(Guard $auth)
    {
        $this->auth = $auth;
    }

    /**
     * Handle a login request to the application.
     * @param  \Illuminate\Http\Request $request
     * @return \Response
     */
    public function login(Request $request)
    {
        $credentials = $request->only('username', 'password');

        if (!$this->auth->attempt($credentials)) {
            // something went wrong whilst attempting to encode the token
            return failure(
                \Lang::get('api.err.auth.failed'),
                \Config::get('status.err.unauthorized')
            );
        }

        if (!$user = $this->auth->user()) {
            // something went wrong whilst attempting to retrieving the user
            return failure(
                \Lang::get('api.err.generic'),
                \Config::get('status.err.unauthorized')
            );
        }

        try {

            $token = \JWTAuth::fromUser($user, ['id' => $user->getKey(), 'permissions' => $user->permissions]);

        } catch (JWTException $e) {
            // something went wrong whilst attempting to encode the token
            return failure(
                \Lang::get('api.err.generic'),
                \Config::get('status.err.unauthorized')
            );
        }

        return success(compact('token'));
    }
}

@kitensei you should use the attempt method from JWTAuth. As it utilises laravel Guard to verify the credentials, and also allows you to pass an array of custom claims as the second param. E.g.

$token = JWTAuth::attempt($credentials, $customClaims);

Also by using the attempt method on Laravel's Guard class, you are unnecessarily creating a session entry.

Guard's once() method is intended for stateless authentication. And is what is used under the hood for JWTAuth.

I am aware of this problem, but the problem is that, by using

$token = JWTAuth::attempt($credentials, $customClaims);

I cannot add custom claims from the user model that I'm trying to authentify, as I don't have an instance yet, the only way would be to instantiate user from database with a simple find() and then authenticate with credentials, which mean hitting the database twice

Thanks for pointing out the session entry, I didn't know that, the problem doesn't change.
Ideally I think we should be able to pass custom claims that JWT retrieve from the model instead of a simple array of previously computed data, is this feasible in your opinion ?

I have this problem too and I think that it could be resolved if we passed closures as custom claims, that would be resolved after the user is retrieved, so it could be injected. The above then would be possible:

$token = JWTAuth::attempt($credentials, [
    'role' => function($user) {
        return $user->role;
    }
]);

What you guys think?

This will be improved when the new release drops - see develop

is it possible now for using closure as custom claims?

thanks

@tymondesigns this still unknown.

Can we pass user data to be a custom claim on attempt method?

I might recommend making a reuseable utility method that outputs the claim set, something like:

protected function getCustomClaims(User $user, ?array $extras = []) : array
{
    $defaults = [
        'exp' => Carbon::now()->addMinutes(config('jwt.ttl'))->timestamp,
        'sub' => $user->id,
        'role' => $user->role,
        'csrf' => Str::random(42),
        'iat' => Carbon::now()->timestamp,
        'nbf' => Carbon::now()->timestamp, // not before
    ];

    return array_merge($defaults, $extras);
}

// normal
Trait::getCustomClaims($user);

// overwrite a default
Trait::getCustomClaims(User::firstWhere('email', '[email protected]'), [
    'nbf' => Carbon::now()->addWeek(1)->timestamp,
]);

// add radical bonus claim
Trait::getCustomClaims($user, [
    'nbf' => Carbon::now()->addWeek(1)->timestamp,
    'abc' => 123,
    'iss' => $request->url(),
    'use' => 'CONFERENCE_WORKSHOP',
]);

You could add validation logic into the method, and it should also make it easier to mock arbitrary state in unit tests.

One of interest to me is generating tokens that expire near-instantly, so I can unit test error 401s during a database transaction while the refresh grace period is 0 to trigger a 401 TOKEN_EXPIRED and have it processed in the Authenticate middleware. It's not possible to set config('jwt.ttl') to less than 1 minute and greater than 0 without passing in a custom exp claim.

$customClaims = [
    'csrf-token' => Str::random(32),
    'exp' => 1599000000,
];

$token = JWTAuth::claims($customClaims)->attempt($this->credentials($request));

The important observation is that JWTAuth is adding its claims as normal/default, but it's also adding csrf-token onto the token, and it's also overriding the exp with that custom value.

Source: https://jwt-auth.readthedocs.io/en/develop/auth-guard/#adding-custom-claims

Was this page helpful?
0 / 5 - 0 ratings

Related issues

loic-lopez picture loic-lopez  路  3Comments

aofdev picture aofdev  路  3Comments

CBR09 picture CBR09  路  3Comments

hfalucas picture hfalucas  路  3Comments

marciomansur picture marciomansur  路  3Comments