-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Removed support for spatie's permissions - Refactored class names - Added config options - Allows use without depending on database
- Loading branch information
Hergen Dillema
committed
Sep 15, 2020
1 parent
3e5b57c
commit 979fc2e
Showing
13 changed files
with
399 additions
and
96 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,34 @@ | ||
<?php | ||
|
||
return [ | ||
'jwkUri' => env('JWKS_URL', 'http://localhost:9011/.well-known/jwks.json'), | ||
'pemUri' => env('PEM_URL', 'http://localhost:9011/api/jwt/public-key'), | ||
|
||
// Configure to use PEM endpoint (default) or JWK | ||
'useJwk' => env('USE_JWK', true), | ||
// If enabled, stores every user in the database | ||
'useDB' => env('FA_USE_DB', false), | ||
|
||
// Column name in the users table where uuid should be stored. Defaults to id but can be another column like 'uuid' | ||
'userId' => env('FA_USR_ID', 'id'), | ||
|
||
'autoCreateUser' => env('FA_CREATE_USR', true), | ||
|
||
// This uses spatie's permissions package to give users the fusionauth roles. | ||
// To use this, installing the permissions package is required | ||
'usePermissions' => env('FA_USE_PERM', true), | ||
// Creates a role if not found in database | ||
'autoCreateRoles' => env('FA_CREATE_ROLES', true), | ||
// Only if useDB = true | ||
// Column name in the users table where uuid should be stored.' | ||
'userId' => env('FA_USR_ID', 'uuid'), | ||
// Only if useDB = true | ||
'autoCreateUser' => env('FA_CREATE_USR', false), | ||
|
||
'alg' => env('FA_ALG', 'RS256'), | ||
|
||
// Allows you to skip validation, this is potentially dangerous, | ||
// only use for testing or if the jwt has been validated by something like an api gateway | ||
'validateJwt' => env('FA_VALIDATE', true), | ||
|
||
// Only if validateJwt = true | ||
'cache' => [ | ||
'enabled' => env('FA_CACHE_ENABLED', false), | ||
'type' => env('FA_CACHE_TYPE', 'database'), | ||
], | ||
|
||
// Allows you to skip validation, this is potentially dangerous, | ||
// only use for testing or if the jwt has been validated by something like an api gateway | ||
'validateJwt' => env('FA_VALIDATE', false), | ||
// Only if validateJwt = true | ||
'jwkUri' => env('JWKS_URL', 'http://localhost:9011/.well-known/jwks.json'), | ||
// Only if validateJwt = true | ||
'pemUri' => env('PEM_URL', 'http://localhost:9011/api/jwt/public-key'), | ||
|
||
// Only if validateJwt = true | ||
// Configure to use PEM endpoint (default) or JWK | ||
'useJwk' => env('USE_JWK', false), | ||
|
||
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
<?php | ||
|
||
return [ | ||
// If enabled, stores every user in the database | ||
'useDB' => env('FA_USE_DB', false), | ||
|
||
// Only if useDB = true | ||
// Column name in the users table where uuid should be stored.' | ||
'userId' => env('FA_USR_ID', 'uuid'), | ||
// Only if useDB = true | ||
'autoCreateUser' => env('FA_CREATE_USR', false), | ||
|
||
'alg' => env('FA_ALG', 'RS256'), | ||
|
||
// Allows you to skip validation, this is potentially dangerous, | ||
// only use for testing or if the jwt has been validated by something like an api gateway | ||
'validateJwt' => env('FA_VALIDATE', true), | ||
|
||
// Only if validateJwt = true | ||
'cache' => [ | ||
'enabled' => env('FA_CACHE_ENABLED', false), | ||
'type' => env('FA_CACHE_TYPE', 'database'), | ||
], | ||
|
||
// Only if validateJwt = true | ||
'jwkUri' => env('JWKS_URL', 'http://localhost:9011/.well-known/jwks.json'), | ||
// Only if validateJwt = true | ||
'pemUri' => env('PEM_URL', 'http://localhost:9011/api/jwt/public-key'), | ||
|
||
// Only if validateJwt = true | ||
// Configure to use PEM endpoint (default) or JWK | ||
'useJwk' => env('USE_JWK', false), | ||
|
||
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<?php | ||
|
||
namespace werk365\jwtauthroles\Exceptions; | ||
|
||
use Symfony\Component\HttpKernel\Exception\HttpException; | ||
|
||
class AuthException extends HttpException | ||
{ | ||
public static function auth(int $status, string $message): self | ||
{ | ||
$exception = new static($status, $message, null, []); | ||
|
||
return $exception; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
<?php | ||
|
||
namespace werk365\JwtAuthRoles; | ||
|
||
use Firebase\JWT\JWT; | ||
use Illuminate\Support\Facades\Http; | ||
use Illuminate\Support\Str; | ||
use phpseclib\Crypt\RSA; | ||
use phpseclib\Math\BigInteger; | ||
use werk365\jwtauthroles\Exceptions\AuthException; | ||
use werk365\jwtauthroles\Models\JwtKey; | ||
use werk365\jwtauthroles\Models\JwtUser; | ||
|
||
class JwtAuthRoles | ||
{ | ||
private static function getKid(string $jwt): ?string | ||
{ | ||
if (Str::is('*.*.*', $jwt)) { | ||
$header = JWT::jsonDecode(JWT::urlsafeB64Decode(Str::before($jwt, '.'))); | ||
if (isset($header->alg) && $header->alg !== config('jwtauthroles.alg')) { | ||
throw AuthException::auth(422, 'Invalid algorithm'); | ||
} | ||
|
||
return $header->kid ?? null; | ||
} else { | ||
throw AuthException::auth(422, 'Malformed JWT'); | ||
} | ||
} | ||
|
||
private static function getClaims(string $jwt): ?object | ||
{ | ||
if (Str::is('*.*.*', $jwt)) { | ||
$claims = explode('.', $jwt); | ||
$claims = JWT::jsonDecode(JWT::urlsafeB64Decode($claims[1])); | ||
|
||
return $claims ?? null; | ||
} else { | ||
throw AuthException::auth(422, 'Malformed JWT'); | ||
} | ||
} | ||
|
||
/** | ||
* @param object $jwk | ||
* @return bool|string|null | ||
*/ | ||
private static function jwkToPem(object $jwk) | ||
{ | ||
if (isset($jwk->e) && isset($jwk->n)) { | ||
$rsa = new RSA(); | ||
$rsa->loadKey([ | ||
'e' => new BigInteger(JWT::urlsafeB64Decode($jwk->e), 256), | ||
'n' => new BigInteger(JWT::urlsafeB64Decode($jwk->n), 256), | ||
]); | ||
|
||
return $rsa->getPublicKey(); | ||
} | ||
throw AuthException::auth(500, 'Malformed jwk'); | ||
} | ||
|
||
/** | ||
* @param string $kid | ||
* @param string $uri | ||
* @return bool|string|null | ||
*/ | ||
private static function getJwk(string $kid, string $uri) | ||
{ | ||
$response = Http::get($uri); | ||
$json = $response->getBody(); | ||
if ($json) { | ||
$jwks = json_decode($json, false); | ||
if ($jwks && isset($jwks->keys) && is_array($jwks->keys)) { | ||
foreach ($jwks->keys as $jwk) { | ||
if ($jwk->kid === $kid) { | ||
return self::jwkToPem($jwk); | ||
} | ||
} | ||
} | ||
} | ||
throw AuthException::auth(404, 'jwks endpoint not found'); | ||
} | ||
|
||
private static function getPem(string $kid, string $uri): ?string | ||
{ | ||
$response = Http::get($uri); | ||
$json = $response->getBody(); | ||
if ($json) { | ||
$pems = json_decode($json, false); | ||
if ($pems && isset($pems->publicKeys) && is_object($pems->publicKeys)) { | ||
foreach ($pems->publicKeys as $key=>$pem) { | ||
if ($key === $kid) { | ||
return $pem; | ||
} | ||
} | ||
} | ||
} | ||
throw AuthException::auth(404, 'pem endpoint not found'); | ||
} | ||
|
||
private static function verifyToken(string $jwt, string $uri, bool $jwk = false): object | ||
{ | ||
$kid = self::getKid($jwt); | ||
if (! $kid) { | ||
throw AuthException::auth(422, 'Malformed JWT'); | ||
} | ||
if (config('jwtauthroles.cache.enabled')) { | ||
if (config('jwtauthroles.cache.type') === 'database') { | ||
$row = JwtKey::where('kid', $kid) | ||
->orderBy('created_at', 'desc') | ||
->first('key'); | ||
} | ||
} | ||
|
||
$publicKey = $row->key | ||
?? $jwk | ||
? self::getJwk($kid, $uri) | ||
: self::getPem($kid, $uri); | ||
|
||
if (! isset($publicKey) || ! $publicKey) { | ||
throw AuthException::auth(500, 'Unable to validate JWT'); | ||
} | ||
|
||
if (config('jwtauthroles.cache.enabled')) { | ||
if (config('jwtauthroles.cache.type') === 'database') { | ||
$row = $row ?? JwtKey::create(['kid' => $kid, 'key' => $publicKey]); | ||
} | ||
} | ||
|
||
return JWT::decode($jwt, $publicKey, [config('jwtauthroles.alg')]); | ||
} | ||
|
||
/** @return mixed */ | ||
public static function authUser(object $request) | ||
{ | ||
$jwt = $request->bearerToken(); | ||
|
||
$uri = config('jwtauthroles.useJwk') | ||
? config('jwtauthroles.jwkUri') | ||
: config('jwtauthroles.pemUri'); | ||
|
||
if (! config('jwtauthroles.validateJwt')) { | ||
$claims = self::getClaims($jwt); | ||
} else { | ||
$claims = self::verifyToken($jwt, $uri, config('jwtauthroles.useJwk')); | ||
} | ||
|
||
if(config('jwtauthroles.useDB')) { | ||
if (config('jwtauthroles.autoCreateUser')) { | ||
$user = JwtUser::firstOrNew([config('jwtauthroles.userId') => $claims->sub]); | ||
$user[config('jwtauthroles.userId')] = $claims->sub; | ||
$user->roles = json_encode($claims->roles); | ||
$user->claims = json_encode($claims); | ||
$user->save(); | ||
} else { | ||
$user = JwtUser::where(config('jwtauthroles.userId'), '=', $claims->sub)->firstOrFail(); | ||
$user->roles = json_encode($claims->roles); | ||
$user->claims = json_encode($claims); | ||
$user->save(); | ||
} | ||
} else { | ||
$user = new JwtUser(); | ||
$user->uuid = $claims->sub; | ||
$user->roles = $claims->roles; | ||
$user->claims = $claims; | ||
} | ||
|
||
return $user; | ||
} | ||
} |
Oops, something went wrong.