8000 Import code. · jarnovanleeuwen/jwtapi@f79ca8c · GitHub
[go: up one dir, main page]

Skip to content

Commit f79ca8c

Browse files
Import code.
1 parent 6434f83 commit f79ca8c

18 files changed

+719
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/vendor
2+
composer.lock

composer.json

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"name": "jarnovanleeuwen/jwtapi",
3+
"type": "library",
4+
"description": "Efficient and secure machine-to-machine API using JSON Web Tokens and asymmetric request signing.",
5+
"keywords": [
6+
"jwt",
7+
"api",
8+
"json",
9+
"asymmetric",
10+
"boilerplate"
11+
],
12+
"homepage": "https://github.com/jarnovanleeuwen/jwtapi",
13+
"license": "MIT",
14+
"authors": [
15+
{
16+
"name": "Jarno van Leeuwen",
17+
"email": "vanleeuwen.jarno@gmail.com"
18+
}
19+
],
20+
"autoload": {
21+
"psr-4": {
22+
"JwtApi\\": "src/"
23+
}
24+
},
25+
"autoload-dev": {
26+
"psr-4": {
27+
"Examples\\": "examples/"
28+
}
29+
},
30+
"require": {
31+
"php": "^7.1",
32+
"firebase/php-jwt": "^5.0",
33+
"guzzlehttp/guzzle": "^6.3",
34+
"symfony/http-foundation": "^3.4"
35+
},
36+
"config": {
37+
"preferred-install": "dist",
38+
"sort-packages": true,
39+
"optimize-autoloader": true
40+
}
41+
}

examples/.htaccess

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<IfModule mod_rewrite.c>
2+
RewriteEngine On
3+
4+
# Handle Authorization Header
5+
RewriteCond %{HTTP:Authorization} .
6+
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
7+
</IfModule>

examples/ClientServer/Request.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
namespace Examples\ClientServer;
3+
4+
use JwtApi\Client\Request as BaseRequest;
5+
6+
class Request extends BaseRequest
7+
{
8+
public function __construct(array $parameters = [])
9+
{
10+
$this->parameters = $parameters;
11+
}
12+
13+
public function getMethod(): string
14+
{
15+
return 'GET';
16+
}
17+
18+
public function getUri(): string
19+
{
20+
return 'server.php';
21+
}
22+
23+
public function getRequestOptions(): ?array
24+
{
25+
return ['debug' => true];
26+
}
27+
}

examples/ClientServer/client.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
namespace Examples\ClientServer;
3+
4+
require_once "../../vendor/autoload.php";
5+
6+
use JwtApi\Client\Client;
7+
8+
$apiKey = "MR4CkRb4pm7yFpVOVL6jfJj5JClx81p3";
9+
10+
$client = new Client(
11+
"http://localhost/jwtapi/examples/ClientServer/",
12+
$apiKey,
13+
[
14+
'extra-claim' => 'IAmUntampered'
15+
]
16+
);
17+
18+
$client->loadPrivateKey("../private_rsa.pem");
19+
20+
$request = new Request(['random' => random_int(0, PHP_INT_MAX)]);
21+
22+
print '<pre>';
23+
$response = $client->send($request);
24+
print_r($response->getData());
25+
print '</pre>';

examples/ClientServer/server.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
namespace Examples\ClientServer;
3+
4+
require_once "../../vendor/autoload.php";
5+
6+
use JwtApi\Server\RequestParser;
7+
use JwtApi\Server\Exceptions\RequestException;
8+
use Symfony\Component\HttpFoundation\Request;
9+
10+
$requestParser = new RequestParser(function (string $apiKey): ?string {
11+
// Lookup and return public key associated with $apiKey
12+
if ($apiKey == "MR4CkRb4pm7yFpVOVL6jfJj5JClx81p3") {
13+
return file_get_contents("../public_rsa.pem");
14+
}
15+
return null;
16+
});
17+
18+
$result = [];
19+
20+
try {
21+
$requestParser->setRequest($request = Request::createFromGlobals());
22+
$requestParser->verify();
23+
24+
// The request is verified. The request:
25+
// - contains a valid API key
26+
// - is signed with the corresponding private key
27+
// - was signed less than 60 seconds ago
28+
$result = [
29+
'status' => 'success',
30+
'data' => [
31+
'claims' => $requestParser->getClaims(),
32+
'random' => $request->get('random'),
33+
'method' => $request->getMethod()
34+
]
35+
];
36+
} catch (RequestException $exception) {
37+
$result = [
38+
'status' => 'error',
39+
'code' => $exception->getCode(),
40+
'message' => $exception->getMessage()
41+
];
42+
}
43+
44+
header('Content-Type: application/json');
45+
print json_encode($result);

examples/private_rsa.pem

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
-----BEGIN RSA PRIVATE KEY-----
2+
MIIEogIBAAKCAQEA4X72IjJ09dH1tP/gMIX3gw0d0JdamNC4Kd/hLPn78ozlyVLN
3+
laXAjF/m4F9VQXjwuSpBeITTjQiI9GaJRU8VfHRTJ3TZxfx3s2rlMHw7yYrO4bdH
4+
OdDJLkxFl71PcPWz6SxmJ15uLILIGJaTIBxzRaR1I5ze3yS/wfIzhucSojnHGsUn
5+
CR4CtBDESr8eF2iiXOcx8kVvkIavSRyiJscTTTOfMecTHt4ZXynbBYg3HWh8dRDM
6+
qpVbBwQsCzR8izaA1vV6U19+23sEZbzEYXBxWg9pIJwmDeAe+5KOgL0+c/c1afH6
7+
KnMoXF7FecqExSpy4U/nHCFirMiO9DuFnRCnjQIDAQABAoIBAD4vPf82POzhZsnw
8+
WknnV9drEZLJenFz9aWOmNFHawR7eI7pZ8ph+FR9yfSMNb2ldXUKwx1SO+jDtsj3
9+
UdmsMnesuJXD8p5XvkbbLrMNP4uX3O+AFINMIX7EB9kNms/C4kdmOBGZR/a8ZiPp
10+
uk77qYpGDQuyIgAVaVA5053bMVdM5OxFHj/o5WOAbpYbjQMoB3bvD/A1Vy5EuEvn
11+
Fgi4L8gtxqs+8qf1jMRuUoJXsF5obXkym5syBkci+8Eoizi/vg5uhYUGlmLoOnCa
12+
8nIYuNoU8rwhv0xXSNsTXT1J+0PLjoTsC6UNI6WwzTdrISmdpxFOdhp2McW5v/Av
13+
FiRCQf0CgYEA/vqqvz4jcG89DjPYzkQzm/HEZ7HwcSiWZNGgBulTQBd8DAH+Tmh8
14+
28Gp4btPEPn5zA+9ozJzR/T+oB88pWAYFv2bDO3Io//Uj9UmrrPvhT7fq0EXJWx7
15+
GHD7qSgwn5j+SD7uuF7QtIvIIqPuJMA2YUV3TsyKG8i/B46RVqy1Ve8CgYEA4mYT
16+
mVdDwaw33oIRgz9P5qleU4+Q2T5Ay6QD8ATcbMA2XbNdIiik6OG+mNxjAo4cNZBt
17+
V12ttENleNNcFEkJCqUl2S7a5Yuf66Lhta60ihpW5U6FpeAfRoEfzuKuWmoRRW+l
18+
sM2tZ/J21mReJP+SyiJGxuZiicOJLCYJR/LxdkMCgYAi8Qz3GjfXD5dpW9eJJLWB
19+
2FbW8v6FM0+wzz1D1TOwY8d/CcLk0dLSdq6mHXoPVIJT4ZBWTfKYWM2P+dfgvdNv
20+
Sx+1XNyTBNsHPumHJWdcdipGmKvoV/5ichE6tCQ9qsSl9+HU6EFnjIRHovleleyd
21+
1dwss0D138O18GagXxiWhwKBgHrcJDSpy6ZVfDgu46wS9pxZO0wjc9rA0s7wIgbl
22+
zGzj3lz5EkiQP/X1U8aGiuB/GEXlK8EmoDZcALwSDz0e6V4ygxiaOcYqAE4SpWQ/
23+
+8+aPZ/Q/ewndTGAry9Jio6cUuUYkpUg+MKZLO5pp0FyxEkXUstriPvz9gPUXGME
24+
Lw8bAoGAa+8iAAUrX9cUVAbqKAN0C33C7lPHLfP8ZFRxQI50/nf1srh0BUZukMd2
25+
gPZ2IiJfMEzQlcVmVc1MZ3rTDy0j4KWD5LAol831E7TuJ6As/8YBUS82XeiquaM3
26+
oAyQR0u/KXvb3IuRQ237t1ZoMxbY/fCMQmx0KnK6168aPm1qy7s=
27+
-----END RSA PRIVATE KEY-----

examples/public_rsa.pem

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
-----BEGIN PUBLIC KEY-----
2+
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4X72IjJ09dH1tP/gMIX3
3+
gw0d0JdamNC4Kd/hLPn78ozlyVLNlaXAjF/m4F9VQXjwuSpBeITTjQiI9GaJRU8V
4+
fHRTJ3TZxfx3s2rlMHw7yYrO4bdHOdDJLkxFl71PcPWz6SxmJ15uLILIGJaTIBxz
5+
RaR1I5ze3yS/wfIzhucSojnHGsUnCR4CtBDESr8eF2iiXOcx8kVvkIavSRyiJscT
6+
TTOfMecTHt4ZXynbBYg3HWh8dRDMqpVbBwQsCzR8izaA1vV6U19+23sEZbzEYXBx
7+
Wg9pIJwmDeAe+5KOgL0+c/c1afH6KnMoXF7FecqExSpy4U/nHCFirMiO9DuFnRCn
8+
jQIDAQAB
9+
-----END PUBLIC KEY-----

src/Client/Client.php

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
<?php
2+
namespace JwtApi\Client;
3+
4+
use Firebase\JWT\JWT;
5+
use GuzzleHttp\Client as HttpClient;
6+
use GuzzleHttp\Exception\ClientException;
7+
use GuzzleHttp\RequestOptions;
8+
use JwtApi\Client\Exceptions\AccessTokenException;
9+
use JwtApi\Client\Exceptions\RequestException;
10+
11+
class Client
12+
{
13+
const CLIENT = 'JwtApi/PHP';
14+
const VERSION = 0.1;
15+
const DEFAULT_HASH_ALGORITHM = 'RS256';
16+
const HEADER_API_KEY = 'API-Key';
17+
18+
/**
19+
* @var string
20+
*/
21+
private $apiKey;
22+
23+
/**
24+
* @var string
25+
*/
26+
private $apiUrl;
27+
28+
/**
29+
* @var array
30+
*/
31+
private $claims;
32+
33+
/**
34+
* @var string
35+
*/
36+
private $hashAlgorithm;
37+
38+
/**
39+
* @var HttpClient
40+
*/
41+
private $httpClient;
42+
43+
/**
44+
* @var string
45+
*/
46+
private $privateKey;
47+
48+
public function __construct(string $apiUrl, string $apiKey, array $claims = [])
49+
{
50+
$this->apiUrl = $apiUrl;
51+
$this->apiKey = $apiKey;
52+
$this->claims = $claims;
53+
54+
$this->setHttpClient(new HttpClient([
55+
'base_uri' => $apiUrl,
56+
'http_errors' => false,
57+
'headers' => ['User-Agent' => static::version()]
58+
]));
59+
}
60+
61+
protected function setHttpClient(HttpClient $httpClient): void
62+
{
63+
$this->httpClient = $httpClient;
64+
}
65+
66+
public function getClaims(): array
67+
{
68+
return [];
69+
}
70+
71+
public function setClaims(array $claims): void
72+
{
73+
$this->claims = $claims;
74+
}
75+
76+
public function loadPrivateKey(string $path, string $hashAlgorithm = self::DEFAULT_HASH_ALGORITHM): void
77+
{
78+
$this->setPrivateKey(file_get_contents($path), $hashAlgorithm);
79+
}
80+
81+
public function setPrivateKey(string $privateKey, string $hashAlgorithm = self::DEFAULT_HASH_ALGORITHM): void
82+
{
83+
$this->privateKey = $privateKey;
84+
$this->hashAlgorithm = $hashAlgorithm;
85+
}
86+
87+
protected function createAccessToken(): string
88+
{
89+
if ($this->privateKey === null) {
90+
throw new AccessTokenException("Cannot create Access Token because no Private Key has been set.");
91+
}
92+
93+
return JWT::encode(array_merge([
94+
'iat' => time(),
95+
'iss' => static::version()
96+
], $this->claims), $this->privateKey, $this->hashAlgorithm);
97+
}
98+
99+
protected function getRequestOptions(Request $request): array
100+
{
101+
$options = [
102+
RequestOptions::HEADERS => [
103+
static::HEADER_API_KEY => $this->apiKey,
104+
'Authorization' => "Bearer {$this->createAccessToken()}"
105+
]
106+
];
107+
108+
if ($parameters = $request->getParameters()) {
109+
$options[RequestOptions::QUERY] = $parameters;
110+
}
111+
112+
if ($payload = $request->getPayload()) {
113+
$options[RequestOptions::JSON] = $payload;
114+
}
115+
116+
return array_merge($options, $request->getRequestOptions());
117+
}
118+
119+
public function send(Request $request): Response
120+
{
121+
try {
122+
$response = $this->httpClient->request(
123+
$request->getMethod(),
124+
$request->getUri(),
125+
$this->getRequestOptions($request)
126+
);
127+
128+
if (($statusCode = $response->getStatusCode()) >= 200 && $statusCode < 300) {
129+
return new Response($response);
130+
}
131+
132+
throw new RequestException($response->getBody());
133+
} catch (ClientException $exception) {
134+
throw new RequestException($exception->getMessage());
135+
}
136+
}
137+
138+
public static function version(): string
139+
{
140+
return static::CLIENT.'/'.static::VERSION;
141+
}
142+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
namespace JwtApi\Client\Exceptions;
3+
4+
use Exception;
5+
6+
class AccessTokenException extends ClientException
7+
{
8+
public function __construct($message, $code = 0, Exception $previous = null)
9+
{
10+
parent::__construct($message, $code, $previous);
11+
}
12+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
namespace JwtApi\Client\Exceptions;
3+
4+
use Exception;
5+
use RuntimeException;
6+
7+
abstract class ClientException extends RuntimeException
8+
{
9+
/**
10+
* @var array
11+
*/
12+
private $errors = [];
13+
14+
public function __construct($message, $code = 0, Exception $previous = null)
15+
{
16+
// Try to decode into a JwtApi error response.
17+
$response = json_decode($message);
18+
19+
if ($response !== null) {
20+
$this->errors = $response->errors ?? [];
21+
22+
if (count($this->errors) > 0) {
23+
$error = $this->errors[0];
24+
25+
$message = "[{$error->code}] {$error->message}";
26+
}
27+
}
28+
29+
parent::__construct($message, $code, $previous);
30+
}
31+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
namespace JwtApi\Client\Exceptions;
3+
4+
use Exception;
5+
6+
class RequestException extends ClientException
7+
{
8+
public function __construct($message, $code = 0, Exception $previous = null)
9+
{
10+
parent::__construct($message, $code, $previous);
11+
}
12+
}

0 commit comments

Comments
 (0)
0