Example micro app sinbadxiii/phalcon-auth-jwt-example
Additional JWT guard for the Phalcon authentication library sinbadxiii/phalcon-auth
Phalcon: ^5
PHP: ^7.4 || ^8.1
Run the following command to pull in the latest version::
composer require "sinbadxiii/phalcon-auth-jwt"
use Sinbadxiii\PhalconAuthJWT\Blacklist;
use Sinbadxiii\PhalconAuthJWT\Builder;
use Sinbadxiii\PhalconAuthJWT\Http\Parser\Chains\AuthHeaders;
use Sinbadxiii\PhalconAuthJWT\Http\Parser\Chains\InputSource;
use Sinbadxiii\PhalconAuthJWT\Http\Parser\Chains\QueryString;
use Sinbadxiii\PhalconAuthJWT\Http\Parser\Parser;
use Sinbadxiii\PhalconAuthJWT\JWT;
use Sinbadxiii\PhalconAuthJWT\Manager as JWTManager;
$di->setShared("jwt", function () {
$configJwt = $this->getConfig()->path('jwt');
$providerJwt = $configJwt->providers->jwt;
$builder = new Builder();
$builder->lockSubject($configJwt->lock_subject)
->setTTL($configJwt->ttl)
->setRequiredClaims($configJwt->required_claims->toArray())
->setLeeway($configJwt->leeway)
->setMaxRefreshPeriod($configJwt->max_refresh_period);
$parser = new Parser($this->getRequest(), [
new AuthHeaders,
new QueryString,
new InputSource,
]);
$providerStorage = $configJwt->providers->storage;
$blacklist = new Blacklist(new $providerStorage($this->getCache()));
$blacklist->setGracePeriod($configJwt->blacklist_grace_period);
$manager = new JWTManager(new $providerJwt(
$configJwt->secret,
$configJwt->algo,
$configJwt->keys->toArray()
), $blacklist, $builder);
$manager->setBlacklistEnabled((bool) $configJwt->blacklist_enabled);
return new JWT($builder, $manager, $parser);
});
Copy file from config/jwt.php
in your folder config and merge your config
Update the secret
value in config jwt.php or JWT_SECRET value in your .env file.
Generate a 32 character secret phrase like here https://passwordsgenerator.net/
Firstly you need to implement the Sinbadxiii\PhalconAuthJWT\JWTSubject contract on your User model, which requires that you implement the 2 methods getJWTIdentifier()
and getJWTCustomClaims()
.
The example below:
<?php
namespace App\Models;
use Phalcon\Mvc\Model;
use Sinbadxiii\PhalconAuthJWT\JWTSubject;
class User extends Model implements JWTSubject
{
//...
public function getJWTIdentifier()
{
return $this->id;
}
public function getJWTCustomClaims()
{
return [
"email" => $this->email,
"username" => $this->username
];
}
}
<?php
namespace App\Security\Access;
use Sinbadxiii\PhalconAuth\Access\AbstractAccess;
class Jwt extends AbstractAccess
{
/**
* @return bool
*/
public function allowedIf(): bool
{
return $this->auth->parseToken()->check();
}
}
$di->setShared("auth", function () {
$security = $this->getSecurity();
$adapter = new \Sinbadxiii\PhalconAuth\Adapter\Model($security);
$adapter->setModel(App\Models\User::class);
$guard = new \Sinbadxiii\PhalconAuthJWT\Guard\JWTGuard(
$adapter,
$this->getJwt(),
$this->getRequest(),
$this->getEventsManager(),
);
$manager = new Manager();
$manager->addGuard("jwt", $guard);
$manager->setDefaultGuard($guard);
$manager->setAccess(new \App\Security\Access\Jwt());
$manager->except("/auth/login");
return $manager;
});
Here we are telling the api
guard to use the jwt
driver, and we are setting the api guard as the default.
We can now use Phalcon Auth with JWT guard.
$application = new \Phalcon\Mvc\Micro($di);
$eventsManager = new Manager();
$application->post(
"/auth/logout",
function () {
$this->auth->logout();
return ['message' => 'Successfully logged out'];
}
);
$application->post(
"/auth/refresh",
function () {
$token = $this->auth->refresh();
return $token->toResponse();
}
);
$application->post(
'/auth/login',
function () {
$credentials = [
'email' => $this->request->getJsonRawBody()->email,
'password' => $this->request->getJsonRawBody()->password
];
$this->auth->claims(['aud' => [
$this->request->getURI()
]]);
if (! $token = $this->auth->attempt($credentials)) {
return ['error' => 'Unauthorized'];
}
return $token->toResponse();
}
);
$application->get(
'/',
function () {
return [
'message' => 'hello, my friend'
];
}
);
<?php
declare(strict_types=1);
namespace App\Controllers\Auth;
use Phalcon\Mvc\Controller;
class LoginController extends Controller
{
public function loginAction()
{
$credentials = [
'email' => $this->request->getJsonRawBody()->email,
'password' => $this->request->getJsonRawBody()->password
];
$this->auth->claims(['aud' => [
$this->request->getURI()
]]);
if (! $token = $this->auth->attempt($credentials)) {
$this->response->setJsonContent(['error' => 'Unauthorized'])->send();
}
return $this->respondWithToken($token);
}
public function meAction()
{
$this->response->setJsonContent($this->auth->user())->send();
}
public function logoutAction()
{
$this->auth->logout();
$this->response->setJsonContent(['message' => 'Successfully logged out'])->send();
}
public function refreshAction()
{
return $this->respondWithToken($this->auth->refresh());
}
protected function respondWithToken($token)
{
$this->response->setJsonContent($token->toResponse())->send();
}
}
Example code for middleware:
<?php
namespace App\Middlewares;
use Phalcon\Mvc\Micro\MiddlewareInterface;
use Phalcon\Mvc\Micro;
use function in_array;
class AuthMiddleware implements MiddlewareInterface
{
public function call(Micro $application)
{
$authService = $application->getDI()->get("auth");
if ($access = $authService->getAccess()) {
$excepts = $access->getExceptActions();
$uri = $application->getDI()->get("request")->getURI(true);
if (!in_array($uri, $excepts)) {
try {
$authService->parseToken()->checkOrFail();
} catch (\Throwable $t) {
$responseService = $application->getDI()->get("response");
$responseService->setStatusCode(401, 'Unauthorized');
$responseService->setJsonContent(
[
"error" => "Unauthorized: " . $t->getMessage(),
"code" => 401
]
);
if (!$responseService->isSent()) {
$responseService->send();
}
}
}
}
return true;
}
}
and attach:
$application = new \Phalcon\Mvc\Micro($di);
$eventsManager = new Manager();
$eventsManager->attach('micro', new AuthMiddleware());
$application->before(new AuthMiddleware());
You should now be able to POST to the login endpoint (e.g. http://0.0.0.0:8000/auth/login) with some valid credentials and see a response like:
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJhdWQiOlsiXC9hdXRoXC9sb2dpbiJdLCJleHAiOjE2MzE2OTkwOTMsImlzcyI6IjAuMC4wLjA6ODAwMCIsImlhdCI6MTYzMTY5NzI5MywianRpIjoiZFNhaGhyeUciLCJzdWIiOiIzIiwiZW1haWwiOiIxMjM0NUAxMjM0NS5ydSIsInVzZXJuYW1lIjoiMTIzNDUgdXNlcm5hbWUifQ.bTyngpVQt86IwtySdRUxPgZH_xk-44hYHTkmiA3BC_0s75TvkuLqTC9WN1jzBIR7Q_H4dWb_ErPR2MlTaw9VQA",
"token_type": "bearer",
"expires_in": 1800
}
There are a number of ways to send the token via http:
Authorization header:
Authorization Bearer eyJ0eXAiOiJKV1QiLC...
Query string param:
http://0.0.0.0:8000/me?token=eyJ0eXAiOiJKV1QiLC...
$credentials = ['email' => '[email protected]', 'password' => '1234'];
$token = $this->auth->guard('api')->attempt($credentials);
Attempt to authenticate a user via some credentials.
// Generate a token for the user if the credentials are valid
$token = $this->auth->attempt($credentials);
This will return either a jwt or null
Log a user in and return a jwt for them.
// Get some user from somewhere
$user = User::findFirst(1);
// Get the token
$token = $this->auth->login($user);
Get the currently authenticated user.
// Get the currently authenticated user
$user = $this->auth->user();
If the user is not then authenticated, then null will be returned.
Log the user out - which will invalidate the current token and unset the authenticated user.
$this->auth->logout();
Refresh a token, which invalidates the current one
$newToken = $this->auth->refresh();
Invalidate the token (add it to the blacklist)
$this->auth->invalidate();
Get a token based on a given user's id.
$token = $this->auth->tokenById(1);
Get the raw JWT payload
$payload = $this->auth->payload();
// then you can access the claims directly e.g.
$payload->get('sub'); // = 1
$payload['jti']; // = 'sFF32fsDfs'
$payload('exp') // = 1665544846
$payload->toArray(); // = ['sub' => 1, 'exp' => 1665544846, 'jti' => 'sFF32fsDfs'] etc
Validate a user's credentials
if ($this->auth->validate($credentials)) {
// credentials are valid
}
$token = $this->auth->claims(['username' => 'phalconist'])->attempt($credentials);
$user = $this->auth->setToken('eyJhb...')->user();
Checking the token for correctness
$this->auth->parseToken()->checkOrFail()
Will return true
if everything is ok or Exceptions:
- Sinbadxiii\PhalconAuthJWT\Exceptions\TokenExpiredException ('The token has expired')
- Sinbadxiii\PhalconAuthJWT\Exceptions\TokenBlacklistedException ('The token has been blacklisted')
- Sinbadxiii\PhalconAuthJWT\Exceptions\TokenInvalidException
The MIT License (MIT). Please see License File for more information.