<?php namespace RAP; use \Firebase\JWT\JWT; class TokenBuilder { private $locator; public function __construct(Locator $locator) { $this->locator = $locator; } public function getIdToken(AccessTokenData $tokenData, \Closure $jwtCustomizer = null): string { $keyPair = $this->locator->getJWKSDAO()->getNewestKeyPair(); $payload = $this->createIdTokenPayloadArray($tokenData, $jwtCustomizer); return JWT::encode($payload, $keyPair->privateKey, $keyPair->alg, $keyPair->keyId); } private function createIdTokenPayloadArray(AccessTokenData $tokenData, \Closure $jwtCustomizer = null) { $user = $this->locator->getUserDAO()->findUserById($tokenData->userId); $payloadArr = array( 'iss' => $this->locator->config->jwtIssuer, 'sub' => strval($user->id), 'iat' => intval($tokenData->creationTime), 'exp' => intval($tokenData->expirationTime), 'name' => $user->getCompleteName(), 'aud' => $tokenData->clientId ); if (in_array("email", $tokenData->scope)) { $payloadArr['email'] = $user->getPrimaryEmail(); } if (in_array("profile", $tokenData->scope)) { $payloadArr['given_name'] = $user->getName(); $payloadArr['family_name'] = $user->getSurname(); if ($user->getInstitution() !== null) { $payloadArr['org'] = $user->getInstitution(); } } if ($jwtCustomizer !== null) { // Add additional custom claims $jwtCustomizer($payloadArr); } return $payloadArr; } public function getAccessToken(AccessTokenData $tokenData, \Closure $jwtCustomizer = null): string { $keyPair = $this->locator->getJWKSDAO()->getNewestKeyPair(); $user = $this->locator->getUserDAO()->findUserById($tokenData->userId); if ($user === null) { // CLI client $sub = $tokenData->clientId; } else { $sub = $user->id; } $payload = array( 'iss' => $this->locator->config->jwtIssuer, 'sub' => strval($sub), 'iat' => intval($tokenData->creationTime), 'exp' => intval($tokenData->expirationTime), 'aud' => $this->getAudience($tokenData), 'scope' => implode(' ', $tokenData->scope) ); if ($jwtCustomizer !== null) { // Add additional custom claims $jwtCustomizer($payload); } return JWT::encode($payload, $keyPair->privateKey, $keyPair->alg, $keyPair->keyId); } private function getAudience(AccessTokenData $tokenData) { if ($tokenData->audience !== null) { return $this->getAudienceClaim($tokenData->audience); } $client = $this->locator->getBrowserBasedOAuth2ClientById($tokenData->clientId, true); if ($client === null) { // CLI client without audience return null; } $audiences = [$tokenData->clientId]; if ($client->scopeAudienceMap !== null) { foreach ($tokenData->scope as $scope) { if (array_key_exists($scope, $client->scopeAudienceMap)) { $audience = ((array) $client->scopeAudienceMap)[$scope]; if (!in_array($audience, $audiences)) { array_push($audiences, $audience); } } } } return $this->getAudienceClaim($audiences); } private function getAudienceClaim($audiences) { if (count($audiences) === 1) { // according to RFC 7519 audience can be a single value or an array return $audiences[0]; } return $audiences; } public function generateToken(array $claims) { $iat = time(); // basic payload $payload = array( 'iss' => $this->locator->config->jwtIssuer, 'iat' => $iat ); // copy claims passed as parameter foreach ($claims as $key => $value) { $payload[$key] = $value; } // set expiration claim if it doesn't exist if (!array_key_exists('exp', $payload)) { $payload['exp'] = $iat + 3600; } $keyPair = $this->locator->getJWKSDAO()->getNewestKeyPair(); return JWT::encode($payload, $keyPair->privateKey, $keyPair->alg, $keyPair->keyId); } /** * @param int $lifespan in hours * @param string $audience target service */ public function generateNewToken(string $subject, int $lifespan, string $audience) { $keyPair = $this->locator->getJWKSDAO()->getNewestKeyPair(); $iat = time(); $exp = $iat + $lifespan * 3600; $payload = array( 'iss' => $this->locator->config->jwtIssuer, 'sub' => strval($subject), 'iat' => $iat, 'exp' => $exp, 'aud' => $audience ); $conf = $this->getTokenIssuerConfig($audience); if (property_exists($conf, 'aud')) { $payload['aud'] = $conf->aud; } if (property_exists($conf, 'scope')) { $payload['scope'] = $conf->scope; } return JWT::encode($payload, $keyPair->privateKey, $keyPair->alg, $keyPair->keyId); } private function getTokenIssuerConfig($audience) { foreach ($this->locator->config->tokenIssuer->services as $service) { if ($service->id === $audience) { return $service; } } throw new \Exception("Unable to find configuration for " . $audience); } }