Laravel AT Protocol Client (alpha & unstable)
1<?php
2
3namespace SocialDept\AtpClient\Http;
4
5use Illuminate\Http\Client\PendingRequest;
6use Illuminate\Support\Facades\Http;
7use Psr\Http\Message\RequestInterface;
8use Psr\Http\Message\ResponseInterface;
9use SocialDept\AtpClient\Auth\DPoPKeyManager;
10use SocialDept\AtpClient\Auth\DPoPNonceManager;
11use SocialDept\AtpClient\Data\DPoPKey;
12
13class DPoPClient
14{
15 public function __construct(
16 protected DPoPKeyManager $dpopManager,
17 protected DPoPNonceManager $nonceManager,
18 ) {}
19
20 /**
21 * Build a DPoP-authenticated request with automatic nonce retry
22 */
23 public function request(
24 string $pdsEndpoint,
25 string $url,
26 string $method,
27 DPoPKey $dpopKey,
28 ?string $accessToken = null,
29 ): PendingRequest {
30 return Http::retry(times: 2, sleepMilliseconds: 0, throw: false)
31 ->withRequestMiddleware(
32 fn (RequestInterface $request) => $this->addDPoPProof(
33 $request,
34 $pdsEndpoint,
35 $url,
36 $method,
37 $dpopKey,
38 $accessToken,
39 )
40 )
41 ->withResponseMiddleware(
42 fn (ResponseInterface $response) => $this->captureNonce($response, $pdsEndpoint)
43 );
44 }
45
46 /**
47 * Add DPoP proof header to request
48 */
49 protected function addDPoPProof(
50 RequestInterface $request,
51 string $pdsEndpoint,
52 string $url,
53 string $method,
54 DPoPKey $dpopKey,
55 ?string $accessToken,
56 ): RequestInterface {
57 $nonce = $this->nonceManager->getNonce($pdsEndpoint);
58
59 $dpopProof = $this->dpopManager->createProof(
60 key: $dpopKey,
61 method: $method,
62 url: $url,
63 nonce: $nonce,
64 accessToken: $accessToken,
65 );
66
67 $request = $request->withHeader('DPoP', $dpopProof);
68
69 if ($accessToken) {
70 $request = $request->withHeader('Authorization', 'DPoP '.$accessToken);
71 }
72
73 return $request;
74 }
75
76 /**
77 * Capture DPoP nonce from response for future requests
78 */
79 protected function captureNonce(ResponseInterface $response, string $pdsEndpoint): ResponseInterface
80 {
81 $nonce = $response->getHeaderLine('DPoP-Nonce');
82
83 if ($nonce !== '') {
84 $this->nonceManager->storeNonce($pdsEndpoint, $nonce);
85 }
86
87 return $response;
88 }
89}