elb98rm
elb98rm

Reputation: 800

Laravel tymondesigns/jwt-auth problems

I'm setting up an authentication route with my API. I am using laravel 5.5 with tymondesigns/jwt-auth 1.0.0-rc.1 and Postman to interact with the API.

The authentication route/method seems to work:

/**
 * Authenticates a json request, generating a token.
 *
 * @param Request $request
 * @return JsonResponse
 */
public function authenticate(Request $request)
{
    // grab credentials from the request
    $credentials = $request->only('email', 'password');

    try {
        // attempt to verify the credentials and create a token for the user
        if (! $token = JWTAuth::attempt($credentials)) {
            return response()->json(
                [
                    'error' => 'Invalid credentials.',
                    'detail' => 'Please use your email and password to generate a token.'
                ],
                401);
        }
    } catch (JWTException $e) {
        // something went wrong whilst attempting to encode the token
        return response()->json(
            [
                'error' => 'Could not create token',
                'detail' => 'There was an internal problem and your token could not be created.'
            ], 500
        );
    }

    // all good so return the token
    return response()->json(compact('token'));
}

A Postman API post request returns (what seems to be) a valid response, For example:

{
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vc29sZGVyc3RhcmFwaS5jb20ubG9jYWwvYXBpL2F1dGhlbnRpY2F0ZSIsImlhdCI6MTUwNzg4NjU2OSwiZXhwIjoxNTA3ODkwMTY5LCJuYmYiOjE1MDc4ODY1NjksImp0aSI6IkpFWjBkc0dNbEVydXRHcFciLCJzdWIiOiIwNzk2MjhDMC03QjBDLTExRTYtODRERC1DQjAzMzVGN0JBNUQiLCJwcnYiOiI4N2UwYWYxZWY5ZmQxNTgxMmZkZWM5NzE1M2ExNGUwYjA0NzU0NmFhIn0.Dl2EEaYZx3H5XXG9WUcPXYKuma0ZjCvcCsb99hgB6O4"
}

To begin with, for basic testing purposes, I am feeding this to an action using GET, with the following suffix:

?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vc29sZGVyc3RhcmFwaS5jb20ubG9jYWwvYXBpL2F1dGhlbnRpY2F0ZSIsImlhdCI6MTUwNzg4NjU2OSwiZXhwIjoxNTA3ODkwMTY5LCJuYmYiOjE1MDc4ODY1NjksImp0aSI6IkpFWjBkc0dNbEVydXRHcFciLCJzdWIiOiIwNzk2MjhDMC03QjBDLTExRTYtODRERC1DQjAzMzVGN0JBNUQiLCJwcnYiOiI4N2UwYWYxZWY5ZmQxNTgxMmZkZWM5NzE1M2ExNGUwYjA0NzU0NmFhIn0.Dl2EEaYZx3H5XXG9WUcPXYKuma0ZjCvcCsb99hgB6O4

In order to test this, if I do the following:

public function globalObjects(Request $request): JsonResponse {
    var_dump(JWTAuth::parseToken()->authenticate(), JWTAuth::getToken()); exit;

   // ... later code that never gets reached
}

I get the following:

bool(false) object(Tymon\JWTAuth\Token)#809 (1) { ["value":"Tymon\JWTAuth\Token":private]=> string(384) "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vc29sZGVyc3RhcmFwaS5jb20ubG9jYWwvYXBpL2F1dGhlbnRpY2F0ZSIsImlhdCI6MTUwNzg4NjU2OSwiZXhwIjoxNTA3ODkwMTY5LCJuYmYiOjE1MDc4ODY1NjksImp0aSI6IkpFWjBkc0dNbEVydXRHcFciLCJzdWIiOiIwNzk2MjhDMC03QjBDLTExRTYtODRERC1DQjAzMzVGN0JBNUQiLCJwcnYiOiI4N2UwYWYxZWY5ZmQxNTgxMmZkZWM5NzE1M2ExNGUwYjA0NzU0NmFhIn0.Dl2EEaYZx3H5XXG9WUcPXYKuma0ZjCvcCsb99hgB6O4" }

.. as in:

Items of note:

Following request: here's \config\jwt.php

return [
    'secret' => env('JWT_SECRET', 'AqAWUTYISA56lrl2vcRtZQn4M4zk9onl'),
    'ttl' => 60,
    'refresh_ttl' => 20160,
    'algo' => 'HS256',
    'user' => 'App\User',
    'identifier' => 'email',
    'required_claims' => ['iss', 'iat', 'exp', 'nbf', 'sub', 'jti'],
    'blacklist_enabled' => env('JWT_BLACKLIST_ENABLED', true),
    'providers' => [
        'user' => 'Tymon\JWTAuth\Providers\User\EloquentUserAdapter',
        'jwt' => 'Tymon\JWTAuth\Providers\JWT\Namshi',
        'auth' => 'Tymon\JWTAuth\Providers\Auth\Illuminate',
        'storage' => 'Tymon\JWTAuth\Providers\Storage\Illuminate',

    ],

];

Thanks

Upvotes: 0

Views: 7514

Answers (3)

scryba
scryba

Reputation: 21

Here is the solution I use in my API works perfectly well.

First rewrite the class Tymon\JWTAuth\Providers\Auth\Illuminate::class

<?php


namespace Scryba\Code\Laravel\Providers\Auth\Jwt;

use Tymon\JWTAuth\Contracts\Providers\Auth;
use Illuminate\Contracts\Auth\Guard as GuardContract;

class Illuminate implements Auth
{
/**
 * The authentication guard.
 *
 * @var \Illuminate\Contracts\Auth\Guard
 */
protected $auth;

/**
 * Constructor.
 *
 * @param  \Illuminate\Contracts\Auth\Guard  $auth
 *
 * @return void
 */
public function __construct(GuardContract $auth)
{
    $this->auth = $auth;
}

/**
 * Check a user's credentials.
 *
 * @param  array  $credentials
 *
 * @return bool
 */
public function byCredentials(array $credentials)
{
    return $this->auth->once($credentials);
}

/**
 * Authenticate a user via the id.
 *
 * @param  mixed  $id
 *
 * @return bool
 */
public function byId($id)
{
    //you can see i added hex2bin($id)because i save my UUID primary key as 
    //binary(16)
    return $this->auth->onceUsingId(hex2bin($id));
}

/**
 * Get the currently authenticated user.
 *
 * @return mixed
 */
public function user()
{
    return $this->auth->user();
}
}

Then update \config\jwt.php file to

 'providers' => [

    /*
    |--------------------------------------------------------------------------
    | JWT Provider
    |--------------------------------------------------------------------------
    |
    | Specify the provider that is used to create and decode the tokens.
    |
    */

    'jwt' => Tymon\JWTAuth\Providers\JWT\Namshi::class,

    /*
    |--------------------------------------------------------------------------
    | Authentication Provider
    |--------------------------------------------------------------------------
    |
    | Specify the provider that is used to authenticate users.
    |
    */

    //'auth' => Tymon\JWTAuth\Providers\Auth\Illuminate::class,
    'auth' => Scryba\Code\Laravel\Providers\Auth\Jwt\Illuminate::class,

    /*
    |--------------------------------------------------------------------------
    | Storage Provider
    |--------------------------------------------------------------------------
    |
    | Specify the provider that is used to store tokens in the blacklist.
    |
    */

    'storage' => Tymon\JWTAuth\Providers\Storage\Illuminate::class,

],

in my routes file

////// Protected methods (requires Authenticatication)
        $api->group(
            [
                'middleware' => ['api.auth'],
                'providers' => ['jwt'],

            ],function($api){

        $api->resource('cars', 'CarController', ['only' => [
            'index' ,'show'
        ]]);

        });

So in your case leave the source file \vendor\tymon\jwt-auth\src\JWTAuth.php as is and write your code as below in your custom class if applicable.

/**
* Authenticate a user via the id.
*
* @param  mixed  $id
*
* @return bool
*/
public function byId($id)
{
   $id_text = $this->getPayload()->get('sub');

   $uuid_helper = new UuidHelper();
   $id = $uuid_helper->textIdToId($id_text);

  return $this->auth->onceUsingId($id);
}

Upvotes: 2

elb98rm
elb98rm

Reputation: 800

The answer is : "this is not compatible with this feature in it's current state without extending it yourself".

I hope this helps anyone else using UUIDs as a primary key. You can't do it without editing the vendor items or extending... But, this is an easy fix.

I will be raising this with the package author and hopefully finding a more permanent solution. However, here is a workaround:

Background:

  • UUIDs are stored as binary(16) id columns in the database.
  • This is not human readable/text friendly.
  • To get around this, id_text fields exist as auto generated mysql fields
  • note - you should never search using these (it stresses the db badly)
  • These are text readable and thus.. can be easily used in forms etc
  • I created a UuidHelper to allow for easy translation

Problem in JWT:

  • The authentication function tries to decode an id_text as an id
  • obviously, this always fails

In file \vendor\tymon\jwt-auth\src\JWTAuth.php

/**
 * Authenticate a user via a token.
 *
 * @return \Tymon\JWTAuth\Contracts\JWTSubject|false
 */
public function authenticate()
{
    $id_text = $this->getPayload()->get('sub');

    $uuid_helper = new UuidHelper();
    $id = $uuid_helper->textIdToId($id_text);

    if (! $this->auth->byId($id)) {
        return false;
    }

    return $this->user();
}

The documentation isn't the clearest, so I assumed 'identifier' => 'email' would sidestep this problem... turns out it doesn't. I will feed this back to the authors

Modifying the core was just an exercise... I am pretty sure this class can be extended, and I will be attempting this shortly. Editing sources is of course sub-optimal most cases.

However - I hope this digging will help people understand the problem.

Upvotes: 0

Zulfiqar Tariq
Zulfiqar Tariq

Reputation: 394

You token is received but not parsed correctly you need to check for token parsing success.

if (! $auth = JWTAuth::parseToken();) {
    throw Exception('JWTAuth unable to parse token from request');
}
dd(\Auth::id());

i suggest you go to your kernal.php and add following lines in protected $routeMiddleware array

'jwt.auth' => 'Tymon\JWTAuth\Middleware\GetUserFromToken',
'jwt.refresh' => 'Tymon\JWTAuth\Middleware\RefreshToken',

Then go to your routes and use it like middleware to authenticate like this

 Route::group(['middleware' => 'jwt.auth'], function () {
  // Your routes here that you want to protect
        Route::get('foo', function () {
        return 'Hello World';
             });
     ]):

enter image description here

Upvotes: 0

Related Questions