I using lumen(5.2) framework and jwt(1.0).
I get a token but i can't refresh it ,because the system tell me "The token has been blacklisted". I don't know how solved it. could you please help me.
Forgive me, My English isn't very good, There may be some differences in expression.
Route
$app->group(['prefix' => 'auth', 'namespace' => '\App\Http\Controllers'], function () use ($app) {
$app->post('/signin', 'AuthController#signin');
$app->put('/refresh', ['middleware' => ['before' => 'jwt.auth', 'after' => 'jwt.refresh'], 'uses' => 'AuthController#refresh']);
});
Sign in
public function signin(Request $request)
{
$this->validate($request, [
'email' => 'required|email|max:255',
'password' => 'required'
]);
try {
if ($token = $this->jwt->attempt($request->only(['email', 'password']))) {
return $this->json([
'token' => $token
]);
}
return $this->json([], 403, $this->_lang['signin_incorrect']);
} catch (JWTException $e) {
return $this->json([], 500, $e->getMessage());
}
}
Refresh
public function refresh()
{
try {
$this->jwt->setToken($this->jwt->getToken());
if($this->jwt->invalidate()) {
return $this->json([
'token' => $this->jwt->refresh()
]);
}
return $this->json([], 403, $this->_lang['token_incorrect']);
} catch (JWTException $e) {
return $this->json([], 500, $e->getMessage());
}
}
Auth Service Provider
public function boot()
{
// Here you may define how you wish users to be authenticated for your Lumen
// application. The callback which receives the incoming request instance
// should return either a User instance or null. You're free to obtain
// the User instance via an API token or any other method necessary.
$this->app['auth']->viaRequest('api', function ($request)
{
return \App\Models\User::where('email', $request->input('email'))->first();
});
}
I have solved this problem.
first I removed jwt.refresh middleware. then I using JWT MANAGER to refresh my token.
this is now code
$app->group(['prefix' => 'auth', 'namespace' => '\App\Http\Controllers'], function () use ($app) {
$app->post('/signin', 'AuthController#signin');
$app->put('/refresh', 'AuthController#refresh');
});
Controller
return $this->json([
'token' => $this->manager->refresh($this->jwt->getToken())->get()
]);
add JWT_BLACKLIST_ENABLED=false in .env file
Related
I have Codeigniter 4 web app that run REST API with firebase/php-jwt on Laragon 5.0.0210523 environment that run Apache-2.4.47, PHP-8.1.7, and MongoDB-4.0.28. I followed a tutorial and it works fine both server REST API and it REST client. After day work, i stop laragon server. In the next day i try run REST API server then tried then run the client but it failed and gave 301 moved permanently error, but i still can access it from postman.
REST API server side
composer.json
***
"require": {
"php": "^7.4 || ^8.0",
"codeigniter4/framework": "^4.0",
"mongodb/mongodb": "^1.12",
"firebase/php-jwt": "^6.3"
},
***
.env file
***
JWT_SECRET_KEY = SomeThing$089
JWT_TIME_TO_LIVE = 3600
app.baseURL = 'http://ci4-api.localhost'
***
Route.php
***
$routes->get('/', 'Home::index');
$routes->resource('api/users');
$routes->post('api/auth', [\App\Controllers\Api\Auth::class, 'index']);
***
JWT_Helper.php
use App\Models\ModelUsers;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
/**
* #throws Exception
*/
function getJWT($authHeader)
{
if (is_null($authHeader)){
throw new Exception("Authentication JWT failed");
}
return explode(" ", $authHeader)[1];
}
function validateJWT($encodedToken)
{
$key = getenv('JWT_SECRET_KEY');
$decodedToken = JWT::decode($encodedToken, new Key($key, 'HS256'));
$modelUsers = new ModelUsers();
$modelUsers->get_email($decodedToken->email);
}
function createJWT($email): string
{
$timeRequest = time();
$timeToken = getenv('JWT_TIME_TO_LIVE');
$timeExpired = $timeRequest + $timeToken;
$payload = [
'email' => $email,
'iat' => $timeRequest,
'exp' => $timeExpired,
];
return JWT::encode($payload, getenv('JWT_SECRET_KEY'), 'HS256');
}
FilterJWT.php
namespace App\Filters;
use CodeIgniter\API\ResponseTrait;
use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Config\Services;
use Exception;
class FilterJWT implements FilterInterface
{
use ResponseTrait;
public function before(RequestInterface $request, $arguments = null)
{
$header = $request->getServer('HTTP_AUTHORIZATION');
try {
helper('jwt');
$encodedToken = getJWT($header);
validateJWT($encodedToken);
return $request;
} catch (Exception $ex) {
return Services::response()->setJSON(
[
'error' => $ex->getMessage(),
]
)->setStatusCode(ResponseInterface::HTTP_UNAUTHORIZED);
}
}
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
{
// TODO: Implement after() method.
}
}
Filters.php
***
public $aliases = [
'csrf' => CSRF::class,
'toolbar' => DebugToolbar::class,
'honeypot' => Honeypot::class,
'invalidchars' => InvalidChars::class,
'secureheaders' => SecureHeaders::class,
'auth' => FilterJWT::class,
];
public $filters = [
'auth' => [
'before' => [
'api/users/*',
'api/users'
]
]
];
***
ModelUsers.php
namespace App\Models;
use App\Libraries\MongoDb;
class ModelUsers
{
private $database = 'ci4_api';
private $collection = 'user';
private $conn;
function __construct()
{
$mongodb = new MongoDb();
$this->conn = $mongodb->getConn();
}
function get_user_list() {
try {
$filter = [];
$query = new \MongoDB\Driver\Query($filter);
$result = $this->conn->executeQuery($this->database. '.' . $this->collection, $query);
return $result->toArray();
} catch (\MongoDB\Driver\Exception\RuntimeException $ex) {
show_error('Error while fetching users: ' . $ex->getMessage(), 500);
}
}
***
Auth.php
namespace App\Controllers\Api;
use App\Controllers\BaseController;
use App\Models\ModelUsers;
use CodeIgniter\API\ResponseTrait;
use CodeIgniter\Validation\Validation;
use Config\Services;
class Auth extends BaseController
{
use ResponseTrait;
private ModelUsers $model;
private Validation $validation;
function __construct()
{
$this->model = new ModelUsers();
$this->validation = Services::validation();
}
public function index()
{
$email = $this->request->getVar('email');
$password = $this->request->getVar('password');
$password_hash = password_hash($password, PASSWORD_DEFAULT);
$data1 = [
'email' => $email,
'password' => $password
];
if (!$this->validation->run($data1, 'login')) {
$errors = $this->validation->getErrors();
$response = [
'status' => 201,
'error' => null,
'messages' => [
'errors' => [
$errors
]
],
];
return $this->respond($response);
}
$data1 = $this->model->get_email($email);
//return $this->respond($data1, 200);
if (!$data1) {
$response = [
'status' => 201,
'error' => null,
'messages' => [
'error' => 'Data user atau password tidak ada1'
],
];
return $this->respond($response, 200);
}
$password_user = $data1->password;
if (password_verify($password_hash, $password_user) != 0){
$response = [
'status' => 201,
'error' => null,
'messages' => [
'error' => 'Data user atau password tidak ada2'
],
];
return $this->respond($response, 200);
}
helper('jwt');
$response = [
'message' => 'Auth berhasil dilakukan',
'data' => $data1,
'access_token' => createJWT($email)
];
return $this->respond($response, 200);
}
***
users.php
namespace App\Controllers\Api;
use App\Controllers\BaseController;
use App\Models\ModelUsers;
use CodeIgniter\API\ResponseTrait;
use CodeIgniter\HTTP\Response;
use CodeIgniter\Validation\Validation;
use Config\Services;
class Users extends BaseController
{
use ResponseTrait;
private ModelUsers $model;
private Validation $validation;
function __construct()
{
$this->model = new ModelUsers();
$this->validation = Services::validation();
}
public function index(): Response
{
$data = $this->model->get_user_list();
$count = count($data);
if ($count <= 0) {
$data = [
'status' => 201,
'error' => null,
'message' => [
'success' => 'Tidak ada data daftar pegawai'
],
];
}
return $this->respond($data, 200);
}
***
REST Client
.env file
***
app.baseURL = 'http://ci4-test.localhost'
***
Routes.php
***
$routes->get('/rest', [\App\Controllers\Rest\RestClient::class, 'index']);
***
RestClient.php
namespace App\Controllers\Rest;
use App\Controllers\BaseController;
use Config\Services;
class RestClient extends BaseController
{
public function index()
{
$client = Services::curlrequest();
$token = "someToken";
$url = "http://ci4-api.localhost/api/users/";
$headers = [
'Authorization' => 'Bearer ' . $token,
];
$response = $client->request('GET', $url, ['headers' => $headers, 'http_errors' => false]);
return $response->getBody();
}
}
Postman
api auth
api all user list
I have already tried some simple solution, like reload all laragon service like apache server and mongodb, restart the windows and tried find online, but it only suggest that the url is incorectly used like in this one []https://stackoverflow.com/questions/56700991/codeigniter-301-moved-permanently[3]
Is there anyone have same issue or solution, thanks in advance.
After trying some few more time, i found the problem. It still around about url similiar like in case of Codeigniter 301 Moved Permanently, but my problem i added "/" on my url.
eg
RestClient.php
//Read all users
$url = "http://ci4-api.localhost/api/users/";
Maybe i added it after copy paste process
so the correct url is
RestClient.php
//Read all users
$url = "http://ci4-api.localhost/api/users";
hopefully help some people facing same problem
I am accessing a route by the postman in an api made in Yii2, but the code that I insert in the action that corresponds to that route is not working. Follow the request print:
postman-request-print
The request return was not meant to be the one in the image, because the code I put in the 'create' action was this:
<?php
namespace app\modules\api\controllers;
use Yii;
use app\modules\pesquisa_mercado\models\PontoDaPesquisa;
class PesquisaPontoController extends \yii\rest\ActiveController
{
public $modelClass = 'app\modules\pesquisa_mercado\models\PesquisaPonto';
public function behaviors()
{
$behaviors = parent::behaviors();
return $behaviors + [
[
'class' => \yii\filters\auth\HttpBearerAuth::className(),
'except' => ['options'],
],
'verbs' => [
'class' => \app\modules\api\behaviors\Verbcheck::className(),
'actions' => [
'index' => ['GET'],
'create' => ['POST'],
'update' => ['PUT'],
'view' => ['GET'],
'delete' => ['DELETE'],
'finalizar-pesquisa' => ['POST'],
],
],
];
}
public function actions()
{
$actions = parent::actions();
unset($actions['update']);
return $actions;
}
public function actionCreate()
{
die("Test"); // test inserted here
}
}
That is, the return was to be 'Test'. For some reason I don't know, the route is being redirected to another place.
I also discovered that the request goes through the getLinks () method present in the PesquisaPonto model:
<?php
namespace app\modules\pesquisa_mercado\models;
class PesquisaPonto extends \yii\db\ActiveRecord implements \yii\web\Linkable
{
/**
* #inheritdoc
*/
public static function tableName()
{
return '{{%pesquisa_ponto}}';
}
/**
* #inheritdoc
*/
public function getLinks() // the requisition also passes through here!
{
return [
Link::REL_SELF => Url::to(['pesquisa-ponto/view', 'id' => $this->id], true),
'index' => Url::to(['pesquisa-ponto'], true)
];
}
}
Also follow the configuration of urlManager:
'urlManager' => [
'class' => 'yii\web\UrlManager',
'enablePrettyUrl' => true,
'showScriptName' => true,
'rules' => [
// Pontos de Pesquisa
// api/pesquisa-ponto
[
'class' => 'yii\rest\UrlRule',
'controller' => [
'api/pesquisa-ponto'
],
'pluralize' => false,
],
],
]
I still haven't found the reason why Yii2 is redirecting the route and not allowing the postman to access the 'create' action...
The actions() method in yii\rest\ActiveController looks like this
public function actions()
{
return [
// ...
'create' => [
'class' => 'yii\rest\CreateAction',
'modelClass' => $this->modelClass,
'checkAccess' => [$this, 'checkAccess'],
'scenario' => $this->createScenario,
],
// ...
];
}
In your implementation of actions() method you are only removing the configuration for update action but the configuration for create action is left untouched. That means that the action is run from the yii\rest\CreateAction not the actionCreate() method of the controller.
To run the action from the PesquisaPontoController::actionCreate() you have to unset the configuration for the create action as well.
public function actions()
{
$actions = parent::actions();
unset($actions['update'], $actions['create']);
return $actions;
}
I need Basic authentication in Yii2 rest API:
Api controller:
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['authenticator']['class'] = HttpBasicAuth::className();
$behaviors['authenticator']['auth'] = function ($username, $password) {
return \app\models\User::findOne([
'username' => $username,
'password' => $password,
]);
};
}
My Requwest:
login:password#api/users
How fix it?
Error:
Invalid argument supplied for foreach()
if ($this->_behaviors === null) {
$this->_behaviors = [];
foreach ($this->behaviors() as $name => $behavior) {
$this->attachBehaviorInternal($name, $behavior);
}
}
Line with "foreach".
In controller:
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['authenticator'] = [
'class' => HttpBasicAuth::className(),
];
return $behaviors;
}
Url for Get: username:password#api/users
Here is my Controller
class ClientController extends ActiveController
{
public $modelClass = 'common\models\Client';
public $serializer = [
'class' => 'yii\rest\Serializer',
'collectionEnvelope' => 'items',
];
public function behaviors()
{
return ArrayHelper::meenter code hererge(parent::behaviors(),[
[
'class' => \yii\filters\Cors::className(),
],
[
'class' => CompositeAuth::className(),
'except' => ['options'],
'authMethods' => [
HttpBearerAuth::className(),
QueryParamAuth::className(),
],
],
[
'class' => TimestampBehavior::className(),
],
[
'class' => 'yii\filters\ContentNegotiator',
'only' => ['view', 'index'], // in a controller
// if in a module, use the following IDs for user actions
// 'only' => ['user/view', 'user/index']
'formats' => [
'application/json' => Response::FORMAT_JSON,
],
],
[
'class' => AccessControl::className(),
// We will override the default rule config with the new AccessRule class
'ruleConfig' => [
'class' => AccessRule::className(),
],
'only' => ['create', 'delete'],
'rules' => [[
'actions' => ['create'],
'allow' => true,
// Allow users, moderators and admins to create
'roles' => [
User::ROLE_ADMIN
],
],
[
'actions' => ['delete'],
'allow' => true,
// Allow admins to delete
'roles' => [
User::ROLE_ADMIN
],
],
],
],
]);
}
public function actions(){
$actions = parent::actions();
unset( $actions['create']);
return $actions;
}
public function actionCreate(){
$model = new \common\models\Client();
$transaction = Yii::$app->db->beginTransaction();
try
{
$model->load(Yii::$app->getRequest()->getBodyParams(), '');
$user_create = \common\models\User::user_create($model);
if($user_create){
$model->user_id = $user_create->id;
if($model->save()){
$transaction->commit();
return $model;
}
}
}
catch (Exception $e)
{
$transaction->rollBack();
return null;
}
}
Here is my User Model
class User extends ActiveRecord implements IdentityInterface
{
public static function findIdentity($id)
{
return static::findOne(['id' => $id, 'status' => self::STATUS_ACTIVE]);
}
public function generateAccountActivationToken()
{
$this->account_activation_token = Yii::$app->security->generateRandomString() . '_' . time();
}
/**
* #inheritdoc
*/
// public static function findIdentityByAccessToken($token, $type = null)
// {
// throw new NotSupportedException('"findIdentityByAccessToken" is not implemented.');
// }
public static function findIdentityByAccessToken($token, $type = null)
{
return static::findOne(['auth_key' => $token]);
}
/**
* Finds user by username
*
* #param string $username
* #return static|null
*/
public static function findByUsername($username)
{
return static::findOne(['username' => $username, 'status' => self::STATUS_ACTIVE]);
}
/**
* Finds user by password reset token
*
* #param string $token password reset token
* #return static|null
*/
public static function findByPasswordResetToken($token)
{
$expire = Yii::$app->params['user.passwordResetTokenExpire'];
$parts = explode('_', $token);
$timestamp = (int) end($parts);
if ($timestamp + $expire < time()) {
// token expired
return null;
}
/**
* #inheritdoc
*/
public function getId()
{
return $this->getPrimaryKey();
}
/**
* #inheritdoc
*/
public function getAuthKey()
{
return $this->auth_key;
// return null;
}
/**
* #inheritdoc
*/
public function validateAuthKey($authKey)
{
return $this->getAuthKey() === $authKey;
}
/**
* Generates "remember me" authentication key
*/
public function generateAuthKey()
{
$this->auth_key = Yii::$app->security->generateRandomKey();
}
public function beforeSave($insert)
{
if (parent::beforeSave($insert)) {
if ($this->isNewRecord) {
$this->auth_key = \Yii::$app->security->generateRandomString();
}
return true;
}
return false;
}
}
The issue here is when i send post request it return 401 error.I know
it is athentication error it even didnt hit the function
public static function findIdentityByAccessToken($token, $type = null)
{
return static::findOne(['auth_key' => $token]);
}
I kown the issue is here at HttpBearerAuth::className(). How i can fix this error here is the image
About the statement
It does not even land on the findIdentityByAccessToken()
From the DOCS
After authentication, for every API
request, the requested controller will try to authenticate the user in
its beforeAction() step.
If authentication succeeds, the controller will perform other checks
(such as rate limiting, authorization) and then run the action. The
authenticated user identity information can be retrieved via
Yii::$app->user->identity.
If authentication fails, a response with HTTP status 401 will be sent
back together with other appropriate headers (such as a
WWW-Authenticate header for HTTP Basic Auth).
HttpBearerAuth extends HttpHeaderAuth which is an action filter that supports HTTP authentication through HTTP Headers, look into the source code for the HttpHeaderAuth function authenticate($user, $request, $response) you will see it gets the auth headers in the very first line
$authHeader = $request->getHeaders()->get($this->header);
and returns $identity only if the authHeaders are not null, otherwise it returns null from the authenticate($user, $request, $response) method and you receive a 401 error without even landing on the findIdentityByAccesToken().
What you should do is
Open postman and click on the Authorization tab
Select the Type from the drop-down BearerToken
on the right side add the auth_key from the user table for the user that you are sending the request against
Click on the Preview Request Button and you will see the message request headers were updated
now if you go to the Headers tab that is very next to the Authorization tab you will see the key=>value pair of the authorization headers
Now click on send Button and see your request go I will recommend you to comment out everything from the action currently, and just add a echo "hello"; statement to know that it landed there.
You can send the headers via curl for authentication in the following way
curl -d "param1=value1¶m2=value2"
-H "Content-Type: application/x-www-form-urlencoded"
-H "Authorization: Bearer YOUR_TOKEN_"
-X POST http://localhost:3000/data
I have a website which using twitter, facebook or google to login to system.
I use oAuth and here is my code.
config
'authClientCollection' => [
'class' => 'yii\authclient\Collection',
'clients' => [
'facebook' => [
'class' => 'yii\authclient\clients\Facebook',
'clientId' => 'asdsad',
'clientSecret' => 'xzxas',
],
'twitter' => [
'class' => 'yii\authclient\clients\Twitter',
'consumerKey' => 'sadsd',
'consumerSecret' => 'dasdasd',
],
],
],
controller
public function actions()
{
return [
'error' => [
'class' => 'yii\web\ErrorAction',
],
'captcha' => [
'class' => 'yii\captcha\CaptchaAction',
'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null,
],
'auth' => [
'class' => 'yii\authclient\AuthAction',
'successCallback' => [$this, 'oAuthSuccess'],
],
];
}
public function oAuthSuccess($client) {
// get user data from client
$userAttributes = $client->getUserAttributes();
echo '<pre>';
print_r($userAttributes);
die;
The question is how do i know that which one of social media the user use to log to system?
To differentiate your oauth client you can put some instance condition as:--
public function oAuthSuccess($client) {
$reponse = $client->getUserAttributes();
$session = Yii::$app->session;
$token = $client->accessToken->params['access_token'];
$session->set('token' ,$token);
$id = ArrayHelper::getValue($reponse , 'id');
$session->set('auth_id', $id);
//Facebook Oauth
if($client instanceof \yii\authclient\clients\Facebook){
//Do Facebook Login
}
//Google Oauth
elseif($client instanceof \yii\authclient\clients\GoogleOAuth){
//Do Google Login Condition
}
}
public function oAuthSuccess($client) {
// get user data from client
$userAttributes = $client->getUserAttributes();
if($client->getName() == 'twitter'){
........
}else if($client->getName() == 'facebook'){
.........
}