I'm trying to learn Laravel and my goal is to be able to build a RESTful API (no use of views or blade, only JSON results. Later, an AngularJS web app and a Cordova hybrid mobile app will consume this api.
After some research, I'm inclining to choose JWT-Auth library for completely stateless benefit. My problem is: I have 2 main types of users: customers and moderators. Customers are not required to have a password. I need to be able to generate a token for access with the provided email only. If that email exists in the database and it belongs to a customer, it will generate and return the token.
If it exists and belongs to a moderator, it will return false so the interface can request a password. If the email doesn't exist, it throws an invalid parameter error.
I read the docs here and it says it's possible to use Custom Claims. But the docs doesn't explain what are claims and what it means the array being passed as custom claims. I'd like some input on how to go about achieving what I explain above.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use JWTAuth;
use Tymon\JWTAuth\Exceptions\JWTException;
class AuthenticateController extends Controller
{
public function authenticate(Request $request)
{
$credentials = $request->only('email', 'password');
try {
// verify the credentials and create a token for the user
if (! $token = JWTAuth::attempt($credentials)) {
return response()->json(['error' => 'invalid_credentials'], 401);
}
} catch (JWTException $e) {
// something went wrong
return response()->json(['error' => 'could_not_create_token'], 500);
}
// if no errors are encountered we can return a JWT
return response()->json(compact('token'));
}
}
Thanks you.
Update
Bounty's code
public function authenticate(Request $request) {
$email = $request->input('email');
$user = User::where('email', '=', $email)->first();
try {
// verify the credentials and create a token for the user
if (! $token = JWTAuth::fromUser($user)) {
return response()->json(['error' => 'invalid_credentials'], 401);
}
} catch (JWTException $e) {
// something went wrong
return response()->json(['error' => 'could_not_create_token'], 500);
}
// if no errors are encountered we can return a JWT
return response()->json(compact('token'));
}
try with this:
$user=User::where('email','=','user2#gmail.com')->first();
if (!$userToken=JWTAuth::fromUser($user)) {
return response()->json(['error' => 'invalid_credentials'], 401);
}
return response()->json(compact('userToken'));
it works for me, hope can help
Generating token for the customers (without password) can be achieved through
$user = \App\Modules\User\Models\UserModel::whereEmail('xyz#gmail.com')->first();
$userToken=JWTAuth::fromUser($user);
Here $userToken
will stores the token after existence check of email in the table configured in UserModel file.
I have assumed that you stores both customer and moderators in the same table, there must be some flag to discriminate among them. Assume the flag is user_type
$token = null;
$user = \App\Modules\User\Models\UserModel::whereEmail('xyz#gmail.com')->first();
if($user['user_type'] == 'customer'){
$credentials = $request->only('email');
$token =JWTAuth::fromUser($user);
}else if($user['user_type'] == 'moderator'){
$credentials = $request->only('email','password');
$token = JWTAuth::attempt($credentials);
}else{
//No such user exists
}
return $token;
As far as custom claims are concerned these are custom defined payloads which can be attached to token string.
For example, JWTAuth::attempt($credentials,['role'=>1]); Will attempt to add role object to token payload.
Once you decode the token string through JWT Facade JWTAuth::parseToken()->getPayload(); you in turn get all payloads defined in required_claims under config/jwt.php with additional role payload.
Refer https://github.com/tymondesigns/jwt-auth/wiki/Creating-Tokens#creating-a-token-based-on-anything-you-like
Let me know in case you requires anything else.
Rather than making a different login strategy for customers and moderators, you can add token authentication to both user type. this will makes your life easier and prepare for scalability.
In your api, you can just restrict moderator users to not have access to the api by sending
<?php
Response::json('error'=>'method not allowed')
Apart from this suggestion, I believe #Alimnjan code should work.
If you don't already have an App\User object, get it with something like
$user = App\User::find(1);
Generate the token using the fromUser() method of JWTAuth
$token = \JWTAuth::fromUser($user)
The above doesn't authenticate the user, it only generates a JWT token. If you need to authenticate the user, then you have to add something like this
\JWTAuth::setToken($token)->toUser();
Related
So, I have integrated keycloak API endpoints in my Symfony project..
Regarding https://ultimatesecurity.pro/post/password-policy/ we have added 'not username' policy to test on creating new user within the app.
Idea is to delcare specific method with defined endpoint which will do this.
I was checking the documentation and could not find any endpoint that can check for password policy rules --> documentation
Idea for it:
$options = [
'headers' => $this->getAuthJsonHeaders()
];
try {
$endpoint = sprintf('auth/admin/realms/%s/', $this->realm);
$response = $this->request('GET', $endpoint, $options);
return $response;
} catch (\Exception $e) {
$this->exception('Can`t reset user password on Keycloak. ' . $e->getMessage());
}
This is what I get:
when dumping results
To get the list of the password policies being used by the Realm, you should call the following endpoint:
GET <KEYCLOAK_HOST>/auth/admin/realms/<YOUR_REALM>
from the JSON response extract the field:
passwordPolicy
which for instance if you have set Minimum length to 12 and Hashing Iterations to 27500 the passwordPolicy would be "length(12) and hashIterations(27500)"
I'm using VueJS in my front-end, so I have this API call somewhere in the front-end code:
let products = axios.get('api/products');
And then in the routes/api.php:-
Routes::get('products', 'ProductsController#index');
in the ProductsController's index method:
public function index () {
if ( Auth::check() ) {
$user = Auth::user();
return $user->products;
}
// return 'You don't have any products right now!';
}
The index method will always return null even if the user is logged-in!
So how to authenticate the users in this case while using the API calls?
Are you sending auth token because when we write the API we need to deal with auth token and not with Auth::check().
You need to send auth token with user id at the time of api call and verify that details to proceed.
You can check user Auth of API calls by this:
At first add this use code in your Class:
use Auth;
Then add this code in your function:
if (Auth::guard('api')->check())
{
logger(Auth::guard('api')->user()); // to get user
}else{
logger("User not authorized");
}
Return the data via json and decode them in your frontend again. Maybe this helps?
Make sure you're sending the CSRF token, somewhere in your bootstrap.js file, you should have something like this. Also make sure you have the csrf token somewhere in the meta tag
let token = document.head.querySelector('meta[name="csrf-token"]');
if (token) {
window.axios.defaults.headers.common = {
'X-CSRF-TOKEN': Laravel.csrfToken,
'X-Requested-With': 'XMLHttpRequest'
};
} else {
console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
}
So, I'm using Laravel+Passport and so far is working fine.
But, I would like to made a small change to the passport code(well, not in the vendor folder, I hope), once that I would request the User to change it's password in case that he is doing the first login.
So, what I would need is two things (I believe):
1 - How can I add one more info to the oauth/token response? Together with the access_token, I would like to add one column from the DB that is needsNewPassword=true/false.
2 - In case that needsNewPassword is true, then, the app will redirect to another screen, where the user will set a new password. I would set the new password, remove the flag for needsNewPassword and send back a new access_token to the user. The user then, would use only that access_token. How can I regenerate a new access_token?
Thanks for you help! João
Right,
I answering my own question, in case someone needs to do the same. Maybe is not the best way, but is working.What I did is:
Create a new route, like /api/login that points to a method (be sure that is Outside of your middleware "auth", once that it's not sending the token in thi call). E.g: Route::post('/login', 'Auth\LoginController#apiLogin');
in the method, you do a request to the oauth/token and, with the result, you add the fields that you want.
test
function apiLogin(Request $request) {
$tokenRequest = $request->create('/oauth/token', 'POST', $request->all());
$request->request->add([
"client_id" => 'your_client_id',
"client_secret" => 'your_client_secret',
"grant_type" => 'password',
"code" => '*',
]);
$response = Route::dispatch($tokenRequest);
$json = (array) json_decode($response->getContent());
$json['new_value'] = '123456';
$response->setContent(json_encode($json));
return $response
}
This is working for me. In my case, I also have just one app so, my client_id, client_secret, grant_type and code is added in the server side. The client only need to pass username(or email, depends of what you are using) and password and then it will get the access_token and the other info that I want to send as well.
Hope that this helps someone else too.
Cheers,
joao
#joao.sauer
Your own answer is working like a charm, but if you wan't a bit more freedom, you could extend Passport's own AccessTokenController.
A simple example:
use App\Models\User;
use Exception;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use League\OAuth2\Server\Exception\OAuthServerException;
use Psr\Http\Message\ServerRequestInterface;
use Response;
class AccessTokenController extends \Laravel\Passport\Http\Controllers\AccessTokenController
{
public function issueToken(ServerRequestInterface $request)
{
try {
//get username (default is :email)
$username = $request->getParsedBody()['username'];
//get user
$user = User::where('email', '=', $username)->firstOrFail();
//issuetoken
$tokenResponse = parent::issueToken($request);
//convert response to json string
$content = $tokenResponse->getBody()->__toString();
//convert json to array
$data = json_decode($content, true);
if(isset($data["error"]))
throw new OAuthServerException('The user credentials were incorrect.', 6, 'invalid_credentials', 401);
//add access token to user
$user = collect($user);
$user->put('access_token', $data['access_token']);
return Response::json(array($user));
}
catch (ModelNotFoundException $e) { // email notfound
//return error message
}
catch (OAuthServerException $e) { //password not correct..token not granted
//return error message
}
catch (Exception $e) {
////return error message
}
}
}
credits to Messi89:
Laravel Passport - Customize The Token Response
I found a simple solution without need new request, controller or extends, just add parameters to request and call issueToken via app, it can useful for starter:
// in routes/api.php
Route::post('/token',function(Request $request){
$request->request->add([
'grant_type' => 'password',
'client_id' => '2',
'client_secret' => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
]);
return app()->call('\Laravel\Passport\Http\Controllers\AccessTokenController#issueToken');
});
Also can add try...catch block to handle exceptions or add parameters to response before send to client
Route::post('/token',function(Request $request){
$request->request->add([
'grant_type' => 'password',
'client_id' => '2',
'client_secret' => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
]);
try {
$response = app()->call('\Laravel\Passport\Http\Controllers\AccessTokenController#issueToken');
$newResponse = json_decode($response->content());
// Add parameters to response here
$newResponse->user = ['user'=>'user','pass'=>'pass'];
return Response()->json($newResponse);
}catch (Laravel\Passport\Exceptions\OAuthServerException $e) {
if ($e->statusCode() == 400) {
return response()->json(['message' => 'Invalid request. Please enter username and password.'], $e->statusCode());
} else if ($e->statusCode() == 401) {
return response()->json(['message' => 'Your credentials are incorrect. Please try again.'], $e->statusCode());
}
return response()->json('Something went wrong on the server. Please try later.', $e->statusCode());
}
});
I am a newbie to JWT Token System in laravel 5 and using tymon JWT Auth
I managed to create my custom JWT token and my code as follows
use App\Http\Requests;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Tymon\JWTAuth\JWTManager as JWT;
use JWTAuth;
use JWTFactory;
use Tymon\JWTAuth\Exceptions\JWTException;
public function login(Request $request)
{
$customClaims = ['foo' => 'bar', 'baz' => 'bob'];
$payload = JWTFactory::make($customClaims);
$token = JWTAuth::encode($payload);
// return response()->json(compact('token')); // This didnt work?Why?
return response()->json(compact($token))->header('Authorization','Bearer '.$token);
}
public function getUser(){
$token = JWTAuth::parseToken();
echo $token;
}
Here are my following clarifications required
// return response()->json(compact('token'));
Why this gave me an empty json object as {"token":{}}
Is it the right way, i could send my custom data in token and get it back the foo and baz values from the same token?
The output of my code while testing with postman is an empty array. as []. But my headers are added with Authorization →Bearer eyJ0eXAiOiJKV1QiLCJhbG...
Is this correct?
3a. Instead of a simple blank array, i need a success message as 'authorized':true. How can i achieve it?
How should i pass this token back to test. Where should this token be passed using postman. I passed it through Headers as shown in the image
How could i parse this token using laravel and get the custom data i.e foo and baz sent as a token. The method i called is getUser here.
I dont think the token creation is being built properly. Below is working code for login token creation. For this, make sure that the 'user' model under your config/jwt.php is the correct eloquent user model for your application.
$user = array(
'user' => $request->input('email'),
'password' => $request->input('pass')
);
$customClaims= ['usr' => $user['user']];
if(!$token = JWTAuth::attempt($user, $customClaims)){
abort(401);
}
else{
return response()->json(compact('token'));
}
Also included in the above code with the custom claims variable, you were on the right track with that just needs to be passed as a second parameter in the attempt function.
Only the client needs to send the authorization: Bearertoken header to prove that they are who they say they are (I am coming from an android client/server jwt background. So sorry if this doesnt apply to your application).
3a. For any subsequent pages that the user browses to, you simply add an if statement like this
if(!$user = JWTAuth::parseToken()->authenticate()){
abort(401);
}
else{
// Code allowing the user to see protected content
}
See answer to question 3. include an http header with authorization BearerToken
To extract the data from the JWT Payload, you will need to decode the base64 encoded text from the text after the first period in the token and send that to a string. Then run that string through the base64_decode($string) function. That should start to give you some of the payload data.
Hope this helps.
I had the same problem here and i got the following solution:
public function whatEver()
{
$token = JWTAuth::parseToken();
$response = $token->getPayload()->get('foo');
return $response
}
this should return bar.
you can use this method in your user model :
/**
* Return a key value array, containing any custom claims to be added to the JWT.
*
* #return array
*/
public function getJWTCustomClaims()
{
return [
'perms' => '
'
];
}
I'm using laravel 5 and JWTauth package. I'm wondering if it's possible to use a custom token to authenticate a user. Instead of encoding the user's full details, just encode id and email.
The reason why I want to do this is because every time the user updates his details, the app needs to generate new token and update the header bearer token. Otherwise the token is invalid. Is there other way/better way to do this?
I would appreciate your recommendation. Thanks!
You can obtain the token in this way.
$user = User::first();
$token = JWTAuth::fromUser($user);
or
try {
//attempt to verify the credentials and create a token for the user
if(!$userToken = JWTAuth::attempt($credentials)) {
return response()->json(['error' => 'invalid_credentials'], 401);
}
} catch(JWTException $e) {
return response()->json(['error' => 'could_not_create_token'], 500);
}