https://odan.github.io/2019/12/02/slim4-oauth2-jwt.html
If you like the article, please click on the ⭐ button.
https://odan.github.io/2019/12/02/slim4-oauth2-jwt.html
If you like the article, please click on the ⭐ button.
Hi @outro99 This error happens when you create a private key with password. Try to create a private and public key without password.
Hi @outro99 This error happens when you create a private key with password. Try to create a private and public key without password.
@odan thanks for the reply. It seems it always validated it false because of some sort spaces that Visual Studio Code introduced. Others, please beware of copy-pasting and get the code inline.
May I ask what changes should I introduce if I am implementing this into your slim4 skeleton app?
What changes should I do inside skeleton app to make jwt work?
What should I use as a replacement for Slim\Http\Response and request inside TokenCreateAction.php?
{ "message": "Argument 1 passed to App\Action\TokenCreateAction::__invoke() must be an instance of Slim\Http\ServerRequest, instance of Slim\Psr7\Request given, called in /var/www/mydomain.com/cloud/vendor/slim/slim/Slim/Handlers/Strategies/RequestResponse.php on line 42", "exception": [ { "type": "TypeError", "code": 0, "message": "Argument 1 passed to App\Action\TokenCreateAction::__invoke() must be an instance of Slim\Http\ServerRequest, instance of Slim\Psr7\Request given, called in /var/www/mydomain.com.com/cloud/vendor/slim/slim/Slim/Handlers/Strategies/RequestResponse.php on line 42", "file": "/var/www/mydomain.com/cloud/src/Action/TokenCreateAction.php", "line": 20 } ] }
@outro99 Both tutorials are based on the slim/http package. If you use slim/psr7 then you can replace the method signature with the specific psr7 implementation (\Slim\Psr7\Request) or the more generic Psr7 interface.
@outro99 Both tutorials are based on the slim/http package. If you use slim/psr7 then you can replace the method signature with the specific psr7 implementation (\Slim\Psr7\Request) or the more generic Psr7 interface.
@odan thank you for everything! I am actively following your updates for skeleton app as well, loving it.
May I ask, is there a possibility that with the release of 1.0.0. skeleton app we might see also an opt-in pre-included docker installation sequence with nginx & mysql (and/or mariaDB?) and other resources that are needed for it.
Actually I would love also skeleton app to include much more detailed Authorization examples that better show how to initialize user and create different views (which I know they are in construction as stated) and would love if the skeleton would come already with slim4-oauth2 pre-included as a feature that can be enabled, with examples how to set cookies for it also inside your given application and correctly authorize and/or register user within db/manage roles without giving away stateless principle.
I know this is much. Just sharing my opinions for this skeleton to be the ultimate power. I will appreciate your answer as well!
Take care!
Great tutorials for Slim 4. So far I haven't even looked at the official docs, this blog has guided me through everything I've needed!
Small thing that I noticed, in the container.php
snippet the namespace use
at the top is missing use Slim\Factory\AppFactory;
.
For my scenario I wanted to verify Firebase Auth users were valid requests to my api coming from an Angular dashboard.
I used this tutorial as a base, and did the following:
Used this library: https://github.com/kreait/firebase-tokens-php
composer require kreait/firebase-tokens
// Firebase Settings
$settings['firebase'] = [
// The project ID
'project_id' => 'some-id-here',
];
<?php
namespace App\Auth;
use Kreait\Firebase\JWT\Error\IdTokenVerificationFailed;
use Kreait\Firebase\JWT\IdTokenVerifier;
use InvalidArgumentException;
use Kreait\Firebase\JWT\Token;
final class FirebaseAuth
{
/**
* @var string The firebase project id
*/
private $projectId;
/**
* The constructor.
*
* @param string $projectId The firebase project id
*/
public function __construct(
string $projectId
) {
$this->projectId = $projectId;
}
/**
* Parse token.
*
* @param string $token The JWT
*
* @throws InvalidArgumentException
*
* @return Token The parsed token
*/
public function createParsedToken(string $token): Token
{
$verifier = IdTokenVerifier::createWithProjectId($this->projectId);
return $verifier->verifyIdToken($token);
}
/**
* Validate the access token.
*
* @param string $accessToken The JWT
*
* @return bool The status
*/
public function validateToken(string $accessToken): bool
{
$token = null;
// create + validate
try {
$token = $this->createParsedToken($accessToken);
} catch (IdTokenVerificationFailed $e) {
// signature is not valid
return false;
}
// user must have verified email to proceed
if($token->payload()["email_verified"] === false) {
return false;
}
return true;
}
}
Add this in like the tutorial does:
ResponseFactoryInterface::class => function (ContainerInterface $container) {
return $container->get(App::class)->getResponseFactory();
},
FirebaseAuth::class => function (ContainerInterface $container) {
$config = $container->get(Configuration::class);
$projectId = $config->getString('firebase.project_id');
return new FirebaseAuth($projectId);
},
skipped this part as I only wanted to verify existing user tokens passed in from my dashboard
<?php
namespace App\Middleware;
use App\Auth\FirebaseAuth;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
/**
* Firebase middleware.
*/
final class FirebaseMiddleware implements MiddlewareInterface
{
/**
* @var FirebaseAuth
*/
private $firebaseAuth;
/**
* @var ResponseFactoryInterface
*/
private $responseFactory;
public function __construct(FirebaseAuth $firebaseAuth, ResponseFactoryInterface $responseFactory)
{
$this->firebaseAuth = $firebaseAuth;
$this->responseFactory = $responseFactory;
}
/**
* Invoke middleware.
*
* @param ServerRequestInterface $request The request
* @param RequestHandlerInterface $handler The handler
*
* @return ResponseInterface The response
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$authorization = explode(' ', (string)$request->getHeaderLine('Authorization'));
$token = $authorization[1] ?? '';
if (!$token || !$this->firebaseAuth->validateToken($token)) {
return $this->responseFactory->createResponse()
->withHeader('Content-Type', 'application/json')
->withStatus(401, 'Unauthorized');
}
// Append valid token
$parsedToken = $this->firebaseAuth->createParsedToken($token);
$request = $request->withAttribute('token', $parsedToken);
// Sample response for $parsedToken->payload();
// {
// "iss": "https://securetoken.google.com/{{PROJECTID}}",
// "aud": "{{PROJECTID}}",
// "auth_time": 1578755578,
// "user_id": "{{STRING OF LETTERS AND NUMBERS}}",
// "sub": "{{WAS SAME AS USER_ID}}",
// "iat": 1580038499,
// "exp": 1580042099,
// "email": "[email protected]",
// "email_verified": true,
// "firebase": {
// "identities": {
// "email": [
// "[email protected]"
// ]
// },
// "sign_in_provider": "password"
// }
// }
// Append the user id as request attribute
$request = $request->withAttribute('email', $parsedToken->payload()["email"]);
return $handler->handle($request);
}
}
Protect them just like the examples in the tutorial but replace it with the middleware class name:
$app->post('/users', \App\Action\UserCreateAction::class)
->add(\App\Middleware\FirebaseMiddleware::class);
One important note that is missed in the tutorial is that the OPTIONS preflight check for CORS support should not be authorised.
Inside an action you can then use an invoke method something like this:
public function __invoke(ServerRequest $request, Response $response): Response
{
// Collect input from the HTTP request
$data = (array) $request->getParsedBody();
// Get user email
$userEmail = $request->getAttribute("email");
// Invoke the Domain with inputs and retain the result
$devices = $this->deviceListReader->getDeviceList($userEmail);
// Transform the result into the JSON representation
foreach($devices as $device) {
$result[] = [
'id' => $device->id,
'client' => $device->client,
// etc
];
}
// Build the HTTP response
return $response->withJson($result)->withStatus(200);
}
Hopefully this will save a few hours for anyone wanting to implement it themselves.
@odan if you want to use any parts of this for a blog post please go ahead, thank you again for everything you have posted on here about Slim its been invaluable with my project.
Hi @rtpHarry Thanks for your feedback and your example about Firebase.
One important note that is missed in the tutorial is that the OPTIONS preflight check for CORS support should not be authorised.
You find more information about CORS in the Slim 4 documentation and here: https://odan.github.io/2019/11/24/slim4-cors.html
@odan, thank you, that is the tutorial I used to get CORS up and running for my system, it was great! My comment was something that came out of starting to merge your tutorials together. It doesn't mention, I don't think, that if the route is protected you shouldn't also protect the OPTIONS preflight check. It's basically the only piece of information I've had to research for myself with this project :)
@odan Do you have idea to create new tutorial with tuupola/slim-basic-auth and tuupola/slim-jwt-auth ?
@mkraha I think the documentation of tuupola/slim-basic-auth and tuupola/slim-jwt-auth is good enough.
Hi @odan
I followed your tutorial and I can generate tokens but I get the following error when trying to validate tokens on routes:
Message: Entry "App\Middleware\JwtMiddleware" cannot be resolved: Entry "Psr\Http\Message\ResponseFactoryInterface" cannot be resolved: the class is not instantiable Full definition: Object ( class = #NOT INSTANTIABLE# Psr\Http\Message\ResponseFactoryInterface lazy = false ) Full definition: Object ( class = App\Middleware\JwtMiddleware lazy = false __construct( $jwtAuth = get(App\Auth\JwtAuth) $responseFactory = get(Psr\Http\Message\ResponseFactoryInterface) ) )
My route looks like this:
$commentRoute->post('', \App\Action\BakeOnline\CommentCreateAction::class)->add(\App\Middleware\JwtMiddleware::class);
Do you have any suggestions on how to solve this problem?
Thank you in advance.
It looks like the container definition for ResponseFactoryInterface::class
is missing. You can find all the details in the article.
use Psr\Http\Message\ResponseFactoryInterface;
// ...
ResponseFactoryInterface::class => function (ContainerInterface $container) {
return $container->get(App::class)->getResponseFactory();
},
@odan, thank you so much. It worked for me. And thank you for the article! It has been a really great help!
Hello @odan
Thank you so much for this article. I have 2 questions the first isn't so important. I already decided that I will implement the way it's in your doc.
What are the pros / cons of using a library like tuupola/slim-jwt-auth versus an approach like in this article and what do you recommend in the end?
Do you know any projects where these JWT-Functions are being tested? Or do you have examples?
Hi @samuelgfeller My approach is more Middleware and Routing based while the tuupola/slim-jwt-auth
approach uses an array to configure the different routes. For me the array based protection is not so good to maintain in the long run, for example when you add or change route paths you may miss some routes and suddenly it's unprotected. I prefer to explicitly add the JwtAuthMiddleware
to specific routes or route groups in routes.php
. You can open the routes.php
file see what is protected. My approach also makes it easier to fetch users from the database (see TokenCreateAction) instead of loading it from a fixed array. I think you have to decide what's better for your specific use case.
@odan that's very pertinent! I think easily worth mentioning in the article. Below where you link to tuupola/slim-jwt-auth
or somewhere near.
@samuelgfeller Yes, thanks. I will add it to the article.
@odan, I see that this is the real place for JWT conversation. Here is my other post that I posted on slim page.
https://gist.github.com/odan/c8bee474b0054a06776481a6c8de1d8f#gistcomment-3123429
And if I generated by your example in which openSSL does not ask to provide key for private.pem, then i get this:
{ "message": "It was not possible to parse your key, reason: error:0908F070:PEM routines:get_header_and_data:short header", "exception": [ { "type": "InvalidArgumentException", "code": 0, "message": "It was not possible to parse your key, reason: error:0908F070:PEM routines:get_header_and_data:short header", "file": "/var/www/mydomain/cloud/vendor/lcobucci/jwt/src/Signer/OpenSSL.php", "line": 90 } ] }