diff --git a/src/ClaimsAwareInterface.php b/src/ClaimsAwareInterface.php new file mode 100644 index 0000000..47d0503 --- /dev/null +++ b/src/ClaimsAwareInterface.php @@ -0,0 +1,11 @@ +all() + [ 'sub' => $webToken->getSubject(), 'iat' => $webToken->getIssuedAt()->getTimestamp(), 'exp' => $webToken->getExpireAt()->getTimestamp(), @@ -33,15 +33,19 @@ public function encode(WebToken $webToken) */ public function decode($encoded) { - $payload = (array) JWT::decode($encoded, $this->secret, [$this->algoritm]) + [ + $defaults = [ 'sub' => null, 'iat' => null, 'exp' => null, ]; + $payload = (array) JWT::decode($encoded, $this->secret, [$this->algoritm]) + $defaults; + + $claims = array_diff_key($payload, $defaults); + $expireAt = new DateTimeImmutable('@'.$payload['exp']); $issuedAt = new DateTimeImmutable('@'.$payload['iat']); - return new WebToken($payload['sub'], $issuedAt, $expireAt); + return new WebToken($payload['sub'], $issuedAt, $expireAt, $claims); } } diff --git a/src/Security/UsernamePasswordAuthenticator.php b/src/Security/UsernamePasswordAuthenticator.php index 7d83ead..b52569c 100644 --- a/src/Security/UsernamePasswordAuthenticator.php +++ b/src/Security/UsernamePasswordAuthenticator.php @@ -4,6 +4,7 @@ use Antenna\Coder; use Antenna\WebToken; +use Antenna\ClaimsAwareInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\JsonResponse; @@ -37,9 +38,9 @@ class UsernamePasswordAuthenticator implements private $coder; /** - * @param UserCheckerInterface $userChecker + * @param UserCheckerInterface $userChecker * @param UserPasswordEncoderInterface $encoder - * @param Coder $coder + * @param Coder $coder */ public function __construct( UserCheckerInterface $userChecker, @@ -105,10 +106,19 @@ public function onAuthenticationFailure(Request $request, AuthenticationExceptio public function onAuthenticationSuccess(Request $request, TokenInterface $token) { - $webToken = new WebToken($token->getUsername(), date_create_immutable(), date_create_immutable('+7 days')); + $claims = []; + $user = $token->getUser(); + + if ($user instanceof ClaimsAwareInterface) { + $claims = $user->getClaims(); + } + + $webToken = new WebToken( + $token->getUsername(), date_create_immutable(), date_create_immutable('+7 days'), $claims + ); return new JsonResponse([ - 'token' => $this->coder->encode($webToken) + 'token' => $this->coder->encode($webToken), ]); } } diff --git a/src/WebToken.php b/src/WebToken.php index 9d864ff..4a5c822 100644 --- a/src/WebToken.php +++ b/src/WebToken.php @@ -4,17 +4,33 @@ use DateTimeInterface; +/** + * Represents part of a Json Web Token. + * + * Its main relation is "claims" and as such, the methods for interacting + * with these follow the Symfony Convention http://symfony.com/doc/current/contributing/code/conventions.html#method-names + * + * Not this is an immutable class. + */ class WebToken { private $subject; private $issuedAt; private $expireAt; + private $claims = []; - public function __construct($subject, DateTimeInterface $issuedAt, DateTimeInterface $expireAt) + /** + * @param string $subject The sub (subject) claim identifies the principal that is the subject of the JWT. + * @param DateTimeInterface $issuedAt The iat (issued at) claim identifies the time at which the JWT was issued. + * @param DateTimeInterface $expireAt The exp (expiration time) claim identifies the expiration time. + * @param [string]mixed Additional claims that is not required. + */ + public function __construct($subject, DateTimeInterface $issuedAt, DateTimeInterface $expireAt, $claims = []) { $this->subject = $subject; $this->issuedAt = $issuedAt; $this->expireAt = $expireAt; + $this->claims = $claims; } public function getSubject() @@ -31,4 +47,24 @@ public function getExpireAt() { return $this->expireAt; } + + public function all() + { + return $this->claims; + } + + public function keys() + { + return array_keys($this->claims); + } + + public function has($claim) + { + return array_key_exists($claim, $this->claims); + } + + public function get($claim, $default = null) + { + return $this->has($claim) ? $this->claims[$claim] : $default; + } } diff --git a/tests/CoderTest.php b/tests/CoderTest.php index 9b818f9..843e2de 100644 --- a/tests/CoderTest.php +++ b/tests/CoderTest.php @@ -7,20 +7,41 @@ class CoderTest extends \PHPUnit_Framework_TestCase { - public function testCoding() + public function setUp() { - $coder = new Coder('shared_secret'); + $this->coder = new Coder('shared_secret'); + } + public function testCoding() + { $utc = new \DateTimeZone('UTC'); $issuedAt = date_create_immutable('now'); $expireAt = date_create_immutable('+1 year'); - $webToken = $coder->decode($coder->encode( - new WebToken('my_subject', $issuedAt, $expireAt) - )); + $webToken = $this->coder->decode( + $this->coder->encode( + new WebToken('my_subject', $issuedAt, $expireAt) + ) + ); $this->assertEquals('my_subject', $webToken->getSubject()); $this->assertEquals($issuedAt->getTimestamp(), $webToken->getIssuedAt()->getTimestamp()); $this->assertEquals($expireAt->getTimestamp(), $webToken->getExpireAt()->getTimestamp()); } + + public function testClaims() + { + $claims = [ + 'administrator' => 1, + 'roles' => ['ROLE_USER', 'ROLE_SUPER_ADMIN'], + ]; + + $webToken = $this->coder->decode( + $this->coder->encode( + new WebToken('my_subject', date_create(), date_create('+1 year'), $claims) + ) + ); + + $this->assertEquals($claims, $webToken->all()); + } }