Skip to content
This repository has been archived by the owner on Mar 22, 2020. It is now read-only.

Commit

Permalink
Merge pull request #1 from nrocco/feature/jwt-tokens
Browse files Browse the repository at this point in the history
Replace sessions with (stateless) jwt tokens
  • Loading branch information
nrocco committed Oct 19, 2015
2 parents 2412f30 + 0263f93 commit 6720494
Show file tree
Hide file tree
Showing 6 changed files with 189 additions and 63 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"require": {
"doctrine/dbal": "^2.5",
"silex/silex": "^1.3",
"jdesrosiers/silex-cors-provider": "~0.1"
"jdesrosiers/silex-cors-provider": "~0.1",
"firebase/php-jwt": "^3.0"
},
"authors": [
{
Expand Down
47 changes: 45 additions & 2 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion config.php.dist
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ return [
'cors.allowCredentials' => true,
'cors.allowOrigin' => 'http://localhost:8000 http://0.0.0.0:8000',

'session.storage.options' => [
'auth.options' => [
'token_secret_key' => 'set-this-to-something-long-and-secret',
'token_issuer' => 'localhost',
'token_algorithms' => ['HS256'],

'cookie_lifetime' => 60*60*24*30,
'cookie_path' => '/',
'cookie_domain' => '',
Expand Down
115 changes: 115 additions & 0 deletions src/SilexRestApi/Providers/AuthenticationProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<?php

namespace SilexRestApi\Providers;

use Firebase\JWT\JWT;
use Silex\Application;
use Silex\ServiceProviderInterface;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\Response;

class AuthenticationProvider implements ServiceProviderInterface
{
public function register(Application $app)
{
$app['auth.jwt_validator'] = $app->protect(function ($token) use ($app) {
try {
$decodedToken = JWT::decode(
$token,
$app['auth.options']['token_secret_key'],
$app['auth.options']['token_algorithms']
);

if (isset($decodedToken->iss) and $decodedToken->iss === $app['auth.options']['token_issuer']) {
$app['api']->setUser($decodedToken->user);

return true;
}
} catch (\Exception $e) {
// TODO: handle this properly
}

return false;
});

$app['auth'] = $app->protect(function () use ($app) {
if (true === $app['request']->cookies->has('TOKEN')) {
$token = $app['request']->cookies->get('TOKEN');

if (true === $app['auth.jwt_validator']($token)) {
return;
}
} elseif (true === $app['request']->server->has('PHP_AUTH_USER')) {
$username = $app['request']->server->get('PHP_AUTH_USER');
$password = $app['request']->server->get('PHP_AUTH_PW');

if (true === password_verify($password, $app['restapi']['users'][$username])) {
$app['api']->setUser($username);

return;
}
} elseif (true === $app['request']->headers->has('authorization')) {
$header = $app['request']->headers->get('authorization');

if (0 === strpos($header, 'Bearer')) {
$token = str_replace('Bearer ', '', $header);

if (true === $app['auth.jwt_validator']($token)) {
return;
}
}
}

if ('/login' === $app['request']->getRequestUri() and 'POST' === $app['request']->getMethod()) {
return;
}

return $app->json(['message' => 'Unauthorized'], 401);
});

$app->post('/login', function () use ($app) {
$username = $app['request']->request->get('username');
$password = $app['request']->request->get('password');

if (isset($username) and password_verify($password, $app['restapi']['users'][$username])) {
$payload = [
'iss' => $app['auth.options']['token_issuer'],
'iat' => mktime(),
'user' => $username
];
$token = JWT::encode(
$payload,
$app['auth.options']['token_secret_key']
);
$cookie = new Cookie(
'TOKEN',
$token,
mktime() + $app['auth.options']['cookie_lifetime'],
$app['auth.options']['cookie_path'],
$app['auth.options']['cookie_domain'],
$app['auth.options']['cookie_secure'],
$app['auth.options']['cookie_httponly']
);

$response = new Response();
$response->headers->setCookie($cookie);
$response->headers->set('Content-Type', 'application/json');

if (true === $app['request']->request->has('redirect')) {
$response->headers->set('Location', $app['request']->request->get('redirect'));
$response->setStatusCode(302);
} else {
$response->setContent(json_encode(['token' => $token]));
}

return $response;
}

return $app->json(['message' => 'Unauthorized'], 401);
});
}

public function boot(Application $app)
{
}
}
12 changes: 12 additions & 0 deletions src/SilexRestApi/Providers/RestApiProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace SilexRestApi\Providers;

use Symfony\Component\HttpFoundation\Request;
use Silex\Application;
use Silex\ServiceProviderInterface;
use RestApi\RestApi;
Expand All @@ -11,6 +12,17 @@ class RestApiProvider implements ServiceProviderInterface
{
public function register(Application $app)
{
$app->before(function (Request $request) {
if (0 === strpos($request->headers->get('Content-Type'), 'application/json')) {
$data = json_decode($request->getContent(), true);
$request->request->replace(is_array($data) ? $data : array());
}
});

$app->view(function (array $response) use ($app) {
return $app->json($response['body'], $response['code'], $response['headers']);
});

$storage = new HashedStorage($app['restapi']['storage_path']);

$app['api'] = new RestApi($app['db']);
Expand Down
69 changes: 10 additions & 59 deletions web/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,95 +11,46 @@
$app->register(new Silex\Provider\DoctrineServiceProvider(), [
'db.options' => $app['db.options']
]);
$app->register(new Silex\Provider\SessionServiceProvider(), [
'session.storage.options' => $app['session.storage.options']
]);
$app->register(new SilexRestApi\Providers\AuthenticationProvider());
$app->register(new SilexRestApi\Providers\RestApiProvider());

// add cors
$app->before($app["auth"]);
$app->after($app["cors"]);

$mustBeLogged = function () use ($app) {
if (true === $app['request']->cookies->has('PHPSESSID')) {
// TODO: check if the session is still valid and contains valid data
$app['api']->setUser($app['session']->get('user')['username']);
return;
}

if (false === $app['request']->server->has('PHP_AUTH_USER')) {
return $app->json(["message" => "Unauthorized"], 401);
}

$username = $app['request']->server->get('PHP_AUTH_USER');
$password = $app['request']->server->get('PHP_AUTH_PW');

if (false === password_verify($password, $app['restapi']['users'][$username])) {
return $app->json(["message" => "Unauthorized"], 401);
}

$app['api']->setUser($username);
};

$app->before(function (\Symfony\Component\HttpFoundation\Request $request) {
if (0 === strpos($request->headers->get('Content-Type'), 'application/json')) {
$data = json_decode($request->getContent(), true);
$request->request->replace(is_array($data) ? $data : array());
}
});

$app->view(function (array $response) use ($app) {
$response['headers']['X-Has-Session'] = (session_status() === PHP_SESSION_ACTIVE) ? 'yes' : 'no';
return $app->json($response['body'], $response['code'], $response['headers']);
});

$app->post('/login', function(Silex\Application $app) {
$username = $app['request']->request->get('username', false);
$password = $app['request']->request->get('password');
$redirect = $app['request']->request->get('redirect', '/');

if (!isset($username) || false === password_verify($password, $app['restapi']['users'][$username])) {
return $app->json(["message" => "Unauthorized"], 401);
}

$app['session']->set('user', array('username' => $username));

return $app->redirect($redirect);
$app->get('/', function() use ($app) {
return $app['api']->listResources();
});

$app->get('/files/{hash}', function($hash) use ($app) {
return $app->sendFile($app['api']->fetchFile($hash));
})->before($mustBeLogged);

$app->get('/', function() use ($app) {
return $app['api']->listResources();
})->before($mustBeLogged);
});

$app->get('/{table}', function($table) use ($app) {
return $app['api']->readCollection($table, $app['request']->query->all());
})->before($mustBeLogged);
});

$app->post('/{table}', function($table) use ($app) {
$params = array_merge(
$app['request']->request->all(),
$app['request']->files->all()
);
return $app['api']->createResource($table, $params);
})->before($mustBeLogged);
});

$app->get('/{table}/{pk}', function($table, $pk) use ($app) {
return $app['api']->readResource($table, $pk, $app['request']->query->all());
})->before($mustBeLogged);
});

$app->match('/{table}/{pk}', function($table, $pk) use ($app) {
$params = array_merge(
$app['request']->request->all(),
$app['request']->files->all()
);
return $app['api']->updateResource($table, $pk, $params);
})->method('POST|PUT|PATCH')->before($mustBeLogged);
})->method('POST|PUT|PATCH');

$app->delete('/{table}/{pk}', function($table, $pk) use ($app) {
return $app['api']->deleteResource($table, $pk);
})->before($mustBeLogged);
});

$app->run();

0 comments on commit 6720494

Please sign in to comment.