Azure Web PubSubのnegotiateをPHPで実装してみる
最近書いているとあるサービスでリアルタイム更新をしたいと思い、Azure SignalR ServiceとAzure Web PubSubを試してます。
クイックスタートを参考にすれば、Azure Functionsで割と簡単に動作を試せます。
今回のサービスのバックエンドAPIはPHPで書かれているため、 negotiate
の処理をPHPのAPIで実施したいと考えていたのですが、残念ながらAzure PubSubのPHP SDKは現時点で提供されていません。(多分この先も提供はされなそう... (;_; )
ということで、Azure Web PubSub service client library for JavaScript を参考に、negotiate
が何をしているか調べてみました。
調べた結果、negotiate
のレスポンスは以下のような内容になっていました。
{ baseUrl: 'wss://[PubSubName].webpubsub.azure.com/client/hubs/[hubname]', token: 'JWT token', url: 'wss://[PubSubName].webpubsub.azure.com/client/hubs/[hubname]?access_token=[JWT Token]' }
ふむふむ、JWTで認証しているよう...。生成されるJWTの中身は以下の様な内容でした。
{ "header": { "typ": "JWT", "alg": "HS256" }, "claims": { "iat": 1623618349, "exp": 1623621949, "aud": "https://[PubSubName].webpubsub.azure.com/client/hubs/[hubname]" }, "signature": "sigunature...", "raw": "eyJ0eXAiOiJ..." }
要は、接続文字列からこのJWTを生成できれば良さそうです。ということで、gree/joseを使ってざくっと書いてみたのが以下。
<?php declare(strict_types=1); class PubSubToken { protected $endpoint; protected $wssEndpoint; protected $accesskey; protected $version; protected $alg = 'HS256'; public function __construct($connectionString) { $params = explode(';', $connectionString); foreach ($params as $param) { list($k, $v) = explode('=', $param, 2); $this->{strtolower($k)} = $v; } $this->wssEndpoint = preg_replace('/(http)(s?:\/\/)/i', 'ws$2', $this->endpoint); if ($this->endpoint === null || $this->accesskey === null || $this->version === null || $this->wssEndpoint === null) { throw new \Exception('Parameter error'); } } public function getAuthenticationToken(string $hub, string $userId = null, int $ttl = 3600): array { $now = time(); $payload = [ 'iat' => $now, 'exp' => $now + $ttl, 'aud' => "{$this->endpoint}/client/hubs/{$hub}", ]; if ($userId !== null) { $payload['sub'] = $userId; } $jwt = new \JOSE_JWT($payload); $jwt->header['alg'] = $this->alg; $jwt->header['typ'] = 'JWT'; $jwt->sign($this->accesskey, $this->alg); $jws = new \JOSE_JWS($jwt); $jws = $jws->sign($this->accesskey, $this->alg); $token = $jws->toString(); return [ 'baseUrl' => "{$this->wssEndpoint}/client/hubs/{$hub}", 'token' => $token, 'url' => "{$this->wssEndpoint}/client/hubs/{$hub}?access_token={$token}", ]; } } $pubsub = new PubSubToken('Azure WebPubSubの接続文字列'); $token = $pubsub->getAuthenticationToken('test');
このtoken
を使って無事subscribeできました。
ということで、このtokenをAPIで返してあげれば、クライアント側でsubscribeできそうです。
コードは、gistにも上げておきました。