user = $user; $this->issued = $issued; } /** * Load the cookiesalt as secret * * @return string */ protected static function getSecret() { return auth_cookiesalt(false, true); } /** * Create a new instance from a token * * @param $token * @return self * @throws \Exception */ public static function validate($token) { [$header, $payload, $signature] = sexplode('.', $token, 3, ''); $signature = base64_decode($signature); if (!hash_equals($signature, hash_hmac('sha256', "$header.$payload", self::getSecret(), true))) { throw new \Exception('Invalid JWT signature'); } try { $header = json_decode(base64_decode($header), true, 512, JSON_THROW_ON_ERROR); $payload = json_decode(base64_decode($payload), true, 512, JSON_THROW_ON_ERROR); } catch (\Exception $e) { throw new \Exception('Invalid JWT', $e->getCode(), $e); } if (!$header || !$payload || !$signature) { throw new \Exception('Invalid JWT'); } if ($header['alg'] !== 'HS256') { throw new \Exception('Unsupported JWT algorithm'); } if ($header['typ'] !== 'JWT') { throw new \Exception('Unsupported JWT type'); } if ($payload['iss'] !== 'dokuwiki') { throw new \Exception('Unsupported JWT issuer'); } if (isset($payload['exp']) && $payload['exp'] < time()) { throw new \Exception('JWT expired'); } $user = $payload['sub']; $file = self::getStorageFile($user); if (!file_exists($file)) { throw new \Exception('JWT not found, maybe it expired?'); } if (file_get_contents($file) !== $token) { throw new \Exception('JWT invalid, maybe it expired?'); } return new self($user, $payload['iat']); } /** * Create a new instance from a user * * Loads an existing token if available * * @param $user * @return self */ public static function fromUser($user) { $file = self::getStorageFile($user); if (file_exists($file)) { try { return self::validate(io_readFile($file)); } catch (\Exception $ignored) { } } $token = new self($user, time()); $token->save(); return $token; } /** * Get the JWT token for this instance * * @return string */ public function getToken() { $header = [ 'alg' => 'HS256', 'typ' => 'JWT', ]; $header = base64_encode(json_encode($header)); $payload = [ 'iss' => 'dokuwiki', 'sub' => $this->user, 'iat' => $this->issued, ]; $payload = base64_encode(json_encode($payload, JSON_THROW_ON_ERROR)); $signature = hash_hmac('sha256', "$header.$payload", self::getSecret(), true); $signature = base64_encode($signature); return "$header.$payload.$signature"; } /** * Save the token for the user * * Resets the issued timestamp */ public function save() { $this->issued = time(); io_saveFile(self::getStorageFile($this->user), $this->getToken()); } /** * Get the user of this token * * @return string */ public function getUser() { return $this->user; } /** * Get the issued timestamp of this token * * @return int */ public function getIssued() { return $this->issued; } /** * Get the storage file for this token * * Tokens are stored to be able to invalidate them * * @param string $user The user the token is for * @return string */ public static function getStorageFile($user) { return getCacheName($user, '.token'); } }