Closed smarques84 closed 7 years ago
Which generates a token although this method doesn't return a string (Is it normal?)
Yes it is normal. JWTAuth::encode
method would return Tymon\JWTAuth\Token
, to make this as string you can implicitly cast it to string, like so:
$token = JWTAuth::encode($payload);
$tokenString = (string) $token;
For that case you need custom middleware, because you're about to validate payload with your custom claims. Are you ready for long story? If yes, here we go:
namespace App\Auth;
use App\User;
use Tymon\JWTAuth\JWTAuth;
use BadMethodCallException;
use Tymon\JWTAuth\Providers\Auth\AuthInterface;
class CustomAuthenticator implements AuthInterface
{
/**
* Current authenticated user.
*
* @var \App\User|null
*/
protected $user;
/**
* Check a user's credentials.
*
* @param array $credentials
* @return bool
*/
public function byCredentials(array $credentials = [])
{
throw new BadMethodCallException();
}
/**
* Authenticate a user via the id. Here you can modify how you validate
* your `credentials` from your payload inside the token.
*
* @param mixed $id
* @return bool
*/
public function byId($id)
{
// Because our `sub` is json_encoded type, we need to decode it first
$credentials = json_decode($id, true);
// Example to fetch user from it's email given by the payload
// TODO: NEED TO VALIDATE THE PASSWORD
$this->user = User::where('email', $credentials['email'])->first();
return $this->user != null;
}
/**
* Get the currently authenticated user.
*
* @return mixed
*/
public function user()
{
return $this->user;
}
}
I'll explain why we should write
byId
method below.
byId
method? Actually, JWTAuth
class need this method to authenticate the payload so we can get our current authenticated user, see here. From that code, we know that our payload should have sub
"key". So when you create your token, you may fill sub
"key":// This json_encoded value will be passed to our `CustomAuthenticator::byId`
$payload = JWTFactory::make($credentials + ['sub' => json_encode($credentials)]);
$token = (string) JWTAuth::encode($payload);
config/jwt.php
/*
|--------------------------------------------------------------------------
| Authentication Provider
|--------------------------------------------------------------------------
|
| Specify the provider that is used to authenticate users.
|
*/
'auth' => 'App\Auth\CustomAuthenticator'
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\JsonResponse;
use Tymon\JWTAuth\Facades\JWTAuth;
class CustomAuthMiddleware
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if (!JWTAuth::parseToken()->authenticate()) {
return new JsonResponse(['message' => 'invalid_token'], 401);
}
$response = $next($request);
return $response;
}
}
bootstrap/app.php
find and change this line$app->routeMiddleware([
'auth.custom' => App\Http\Middleware\CustomAuthMiddleware::class,
]);
$api->group(['middleware' => 'auth.custom'], function ($api) {
// Register your protected route here
});
Have a try. Let me know if you need something.
You don't need to add custom middleware, it just works by skipping step 4 - 6. Just leave your middleware
in route using api.auth
$api->group([
'middleware' => 'api.auth',
], function ($api) {
// rest of your protected routes
});
Ok Thank you so much i manage to successfully authenticate but i noticed that the username and password go on the sub key whats the recommended way to store that info inside the token? Just hash the password?
The expiration date is set inside the token correct?
Edit: For what i see i can't hash the password inside the token if i want to use password_verify
php function (password is hashed with bcrypt in the db). So is it supposed for the username & password to be in plain text inside the token? That way anyone can just grab the token with wireshark and simply decode the token and retrieve the user credentials from the sub key!! Or am i missing something here?
... whats the recommended way to store that info inside the token? Just hash the password?
~No, JWT has encrypted anything in it, just make sure you have generate your JWT_KEY
, from that you can decrypt the payload. It means, as long as eavesdropper cannot "guest" your JWT_KEY
, then THERE IS NO WAY THEY CAN SEE THE PLAIN PASSWORD.~ But I really recommend you to NOT sending the password inside the token. Although it seems secure (since it's encrypted), but, who knows?
The expiration date is set inside the token correct?
Yes it is.
So is it supposed for the username & password to be in plain text inside the token?
If you really want to decrypt the password. You may see this snippet. You can decrypt and encrypt it safely.
@krisanalfa Thanks for all the help but for what i read the password should never go inside the token. https://stormpath.com/blog/jwt-the-right-way read where it says "These tokens are usually signed to protect against manipulation (not encrypted) so the data in the claims can be easily decoded and read"
I just tried decoding my token with this website https://py-jwt-decoder.appspot.com/ and can see the sub in plain text! Just test with this token i generated: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImZpcnN0bGFzdEB3aGVyZS5jb20iLCJwYXNzd29yZCI6Im15X3Bhc3N3b3JkX2lzX2luX3BsYWluX3RleHQiLCJzdWIiOiJ7XCJlbWFpbFwiOlwiZmlyc3RsYXN0QHdoZXJlLmNvbVwiLFwicGFzc3dvcmRcIjpcIm15X3Bhc3N3b3JkX2lzX2luX3BsYWluX3RleHRcIn0iLCJpc3MiOiJodHRwOlwvXC8xMjcuMC4wLjE6ODg4OFwvbHVtZW4tand0XC9wdWJsaWNcL2FwaVwvYXV0aFwvbG9naW4iLCJpYXQiOjE0ODUyNjMxMjQsImV4cCI6MTQ4NTI2NjcyNCwibmJmIjoxNDg1MjYzMTI0LCJqdGkiOiJlZmNhYTg4NGE5MjllYWY1OWVjNTNkZmQ1ODFmNmNkMiJ9.xLA-gbxPFGDn0sWhn_3T9zxflgdG1zTtLYeqXczzNlQ
So its really easy to get the data, for what i understand it seems the real protection in JWT is protecting against tempering the token.
Reading on the first link it says: "Always verify the signature before you trust any information in the JWT."
The correct way to use this is just insert a value that identifies the user in the database (like the unique id) and then verify the token signature with the JWT_SECRET to make sure its a valid token. Now how do I verify the token signature inside the public function byId($id)
function?
My, bad. It's decode-able. You may change your authentication process like so:
POST /auth/login
request, validate their credentials. Get the User
corresponds to the credentials and create your payload like this:$user = $this->getUserByCredentials($credentials);
$payload = JWTFactory::make(['sub' => $user->getKey()]);
$token = (string) JWTAuth::encode($payload);
public function byId($id)
{
$this->user = User::where('id', $id)->first();
return $this->user != null;
}
Now how verify the token signature inside the byID function?
It is automatically verified by the authenticate
process. Just change your JWT_SECRET
value, your old token will be invalid with this error response message
:
Token Signature could not be verified.
Thank you so much :1st_place_medal: i successfully implemented the api the way i needed
I am modifying this project to authenticate with a custom user table from a Microsoft SQL Database with different column names and where the password is saved with a different hashing algorithm.
Because of this scenario i would like to check the credentials by my self while avoid using eloquent, to generate the token. So far i have this in AuthController.php
$payload = JWTFactory::make($credentials); $token = JWTAuth::encode($payload);
Which generates a token although this method doesn't return a string (Is it normal?)
Now i think i need to authenticate the token so i had a look in the api.php inside routes folder
$api->group([ 'middleware' => 'api.auth', ], function ($api) {
The middleware part is responsible to check if the token is valid right? But where does this middleware points to? I am a newbie at both JWT and Dingo so any help will be appreciated.