randomizer = $randomizer ?? new Randomizer(); $this->clock = $clock !== null ? Closure::fromCallable($clock) : static fn (): DateTimeImmutable => new DateTimeImmutable(); } public function issue(string $did, string $accessScope, ?string $appPasswordName = null): AuthTokenPair { $now = ($this->clock)(); $accessExp = $now->modify('+' . $this->accessTtlSeconds . ' seconds'); $refreshExp = $now->modify('+' . $this->refreshTtlSeconds . ' seconds'); $jti = $this->generateJti(); $accessJwt = JWT::encode( [ 'scope' => $accessScope, 'sub' => $did, 'iss' => $this->issuer, 'aud' => 'did:web:' . $this->issuer, 'iat' => $now->getTimestamp(), 'exp' => $accessExp->getTimestamp(), ], $this->secret, self::ALG ); $refreshClaims = [ 'scope' => self::SCOPE_REFRESH, 'sub' => $did, 'iss' => $this->issuer, 'aud' => 'did:web:' . $this->issuer, 'jti' => $jti, 'iat' => $now->getTimestamp(), 'exp' => $refreshExp->getTimestamp(), ]; if ($appPasswordName !== null) { $refreshClaims['app_password_name'] = $appPasswordName; } $refreshJwt = JWT::encode($refreshClaims, $this->secret, self::ALG); return new AuthTokenPair($accessJwt, $refreshJwt, $jti, $refreshExp); } private function generateJti(): string { return rtrim(strtr(base64_encode($this->randomizer->getBytes(16)), '+/', '-_'), '='); } }