Skip to content

Slim 3 Example

Andy Dyer edited this page Feb 20, 2020 · 8 revisions

Slim 3 Example

composer require andrewdyer/jwt-auth firebase/php-jwt illuminate/database nesbot/carbon slim/slim:3.*

composer.json

Update composer.json and add autoload information.

{
    // ...
+    "autoload": {
+        "psr-4": {
+            "App\\": "app"
+        }
+    }
}

app/Models/User.php

Create a user model which extends Illuminate\Database\Eloquent\Model and implements the Anddye\JwtAuth\Contracts\JwtSubject interface.

<?php

namespace App\Models;

use Anddye\JwtAuth\Contracts\JwtSubject;
use Illuminate\Database\Eloquent\Model;

/**
 * Class User.
 */
final class User extends Model implements JwtSubject
{
    /**
     * @var array
     */
    protected $hidden = ['password'];

    /**
     * @return int
     */
    public function getJwtIdentifier(): int
    {
        return $this->id;
    }
}

app/Auth/AuthProvider.php

Create an auth provider which implements the Anddye\JwtAuth\Providers\AuthProviderInterface interface. This will handle the user validation.

<?php

namespace App\Auth;

use Anddye\JwtAuth\Contracts\JwtSubject;
use Anddye\JwtAuth\Providers\AuthProviderInterface;
use App\Models\User;

/**
 * Class AuthProvider.
 */
final class AuthProvider implements AuthProviderInterface
{
    /**
     * @param string $username
     * @param string $password
     *
     * @return JwtSubject|null
     */
    public function byCredentials(string $username, string $password): ?JwtSubject
    {
        if (!$user = User::where('username', $username)->first()) {
            return null;
        }

        if (!password_verify($password, $user->password)) {
            return null;
        }

        return $user;
    }

    /**
     * @param int $id
     *
     * @return JwtSubject|null
     */
    public function byId(int $id): ?JwtSubject
    {
        return User::find($id);
    }
}

app/Auth/JwtProvider.php

Create an jwt provider which implements the Anddye\JwtAuth\Providers\JwtProviderInterface interface. This will handle encoding / decoding of the jwt token. The slim container is injected to get config properties off the settings array.

<?php

namespace App\Auth;

use Anddye\JwtAuth\Providers\JwtProviderInterface;
use Firebase\JWT\JWT;

/**
 * Class JwtProvider.
 */
final class JwtProvider implements JwtProviderInterface
{
    /**
     * @var string
     */
    protected $algorithm;

    /**
     * @var string
     */
    protected $secret;

    /**
     * JwtProvider constructor.
     *
     * @param string $algorithm
     * @param string $secret
     */
    public function __construct(string $algorithm, string $secret)
    {
        $this->algorithm = $algorithm;
        $this->secret = $secret;
    }

    /**
     * @param string $token
     *
     * @return object
     */
    public function decode(string $token): object
    {
        return JWT::decode($token, $this->secret, [$this->algorithm]);
    }

    /**
     * @param array $claims
     *
     * @return string
     */
    public function encode(array $claims): string
    {
        return JWT::encode($claims, $this->secret, $this->algorithm);
    }
}

app/Controllers/LoginController.php

<?php

namespace App\Controllers;

use Anddye\JwtAuth\JwtAuth;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;

/**
 * Class LoginController.
 */
final class LoginController
{
    /**
     * @var JwtAuth
     */
    protected $jwtAuth;

    /**
     * LoginController constructor.
     *
     * @param JwtAuth $jwtAuth
     */
    public function __construct(JwtAuth $jwtAuth)
    {
        $this->jwtAuth = $jwtAuth;
    }

    /**
     * @param Request  $request
     * @param Response $response
     *
     * @return Response
     */
    public function postAction(Request $request, Response $response): Response
    {
        $username = $request->getParam('username', '');
        $password = $request->getParam('password', '');

        if (!$token = $this->jwtAuth->attempt($username, $password)) {
            return $response->withJson(['error' => '']);
        }

        return $response->withJson(['token' => $token]);
    }
}

app/Controllers/ActorController.php

<?php

namespace App\Controllers;

use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;

/**
 * Class ActorController.
 */
final class ActorController
{
    /**
     * @param Request  $request
     * @param Response $response
     *
     * @return Response
     */
    public function getAction(Request $request, Response $response): Response
    {
        $actor = $request->getAttribute('actor');

        return $response->withJson($actor);
    }
}

app/Middleware/AuthenticateMiddleware.php

<?php

namespace App\Middleware;

use Anddye\JwtAuth\JwtAuth;
use Firebase\JWT\BeforeValidException;
use Firebase\JWT\ExpiredException;
use Firebase\JWT\SignatureInvalidException;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;

/**
 * Class AuthenticateMiddleware.
 */
final class AuthenticateMiddleware
{
    /**
     * @var JwtAuth
     */
    protected $jwtAuth;

    /**
     * AuthenticateMiddleware constructor.
     *
     * @param JwtAuth $jwtAuth
     */
    public function __construct(JwtAuth $jwtAuth)
    {
        $this->jwtAuth = $jwtAuth;
    }

    /**
     * @param Request  $request
     * @param Response $response
     * @param callable $next
     *
     * @return Response
     */
    public function __invoke(Request $request, Response $response, callable $next): Response
    {
        try {
            if (!$header = $this->getAuthorizationHeader($request)) {
                return $response->withJson(['error' => ''], 401);
            }

            if (!$token = $this->extractToken($header)) {
                return $response->withJson(['error' => ''], 401);
            }

            if (!$actor = $this->jwtAuth->authenticate($token)->getActor()) {
                return $response->withJson(['error' => ''], 401);
            }

            $request = $request->withAttribute('actor', $actor);

            return $next($request, $response);
        } catch (BeforeValidException | ExpiredException | SignatureInvalidException $ex) {
            return $response->withJson(['error' => $ex->getMessage()], 401);
        }
    }

    /**
     * @param string $header
     *
     * @return string|null
     */
    protected function extractToken(string $header): ?string
    {
        if (preg_match('/Bearer\s(\S+)/', $header, $matches)) {
            return $matches[1];
        }

        return null;
    }

    /**
     * @param Request $request
     *
     * @return string|null
     */
    protected function getAuthorizationHeader(Request $request): ?string
    {
        if ($header = $request->getHeader('Authorization')) {
            list($header) = $header;

            return $header;
        }

        return null;
    }
}

bootstrap/app.php

<?php

use Slim\App;
use Slim\Handlers\Strategies\RequestResponseArgs;

require __DIR__ . '/../vendor/autoload.php';

$app = new App([
    'settings' => [
        'displayErrorDetails' => true,
        'database' => [
            'driver' => 'mysql',
            'host' => '127.0.0.1',
            'port' => '3306',
            'database' => 'pt',
            'username' => 'root',
            'password' => 'password',
            'charset' => 'utf8mb4',
            'collation' => 'utf8mb4_unicode_ci',
            'prefix' => '',
        ],
        'jwt' => [
            'algorithm' => 'HS256',
            'issuer' => 'https://github.com/andrewdyer/jwt-auth',
            'jwt_id' => '60ad17YE73',
            'secret' => 'supersecretkeynottobecommitedtogithub',
        ],
    ],
    'foundHandler' => function () {
        return new RequestResponseArgs();
    },
]);

require_once __DIR__ . '/container.php';
require_once __DIR__ . '/routes.php';

bootstrap/container.php

<?php

use Anddye\JwtAuth\ClaimsFactory;
use Anddye\JwtAuth\JwtAuth;
use App\Auth\AuthProvider;
use App\Auth\JwtProvider;
use App\Controllers\ActorController;
use App\Controllers\LoginController;
use App\Middleware\AuthenticateMiddleware;
use Carbon\Carbon;
use Illuminate\Database\Capsule\Manager as Capsule;

$container = $app->getContainer();

$container[Capsule::class] = function ($container) {
    $capsule = new Capsule();
    $capsule->addConnection($container->get('settings')['database']);
    $capsule->setAsGlobal();

    return $capsule;
};

$container[Capsule::class]->bootEloquent();

$container[JwtAuth::class] = function ($container) {
    $settings = $container->get('settings')['jwt'];

    $authProvider = new AuthProvider();
    $jwtProvider = new JwtProvider($settings['algorithm'], $settings['secret']);
    $claimsFactory = ClaimsFactory::build([
        'exp' => Carbon::now()->addMinute(5)->getTimestamp(),
        'iat' => Carbon::now()->getTimestamp(),
        'iss' => $settings['issuer'],
        'jti' => $settings['jwt_id'],
        'nbf' => Carbon::now()->getTimestamp(),
    ]);

    return new JwtAuth($authProvider, $jwtProvider, $claimsFactory);
};

$container[ActorController::class] = function () {
    return new ActorController();
};

$container[LoginController::class] = function ($container) {
    return new LoginController(
        $container->get(JwtAuth::class)
    );
};

$container[AuthenticateMiddleware::class] = function ($container) {
    return new AuthenticateMiddleware(
        $container->get(JwtAuth::class)
    );
};

bootstrap/routes.php

<?php

use App\Controllers\ActorController;
use App\Controllers\LoginController;
use App\Middleware\AuthenticateMiddleware;

$app->group('/api', function () use ($container) {
    $this->get('/auth/me', ActorController::class . ':getAction')->add($container[AuthenticateMiddleware::class]);
    $this->post('/auth/login', LoginController::class . ':postAction');
});

public/index.php

<?php

require_once __DIR__ . '/../bootstrap/app.php';

$app->run();
Clone this wiki locally