Refactored TokenStorageInterface for an improved flow

* Now passing an auth code data validation callback to the exchange method
* Removed Token, it’s no longer necessary
* Simplified interface where possible
* All tests passing
* Updated docblocks
This commit is contained in:
Barnaby Walters 2021-06-13 14:34:37 +02:00
parent 645ab833c5
commit 61aa7f55f9
15 changed files with 468 additions and 335 deletions

View File

@ -148,6 +148,8 @@
self::INVALID_STATE => ['statusCode' => 302, 'name' => 'Invalid state Parameter', 'error' => 'invalid_request'],
self::INVALID_CODE_CHALLENGE => ['statusCode' => 302, 'name' => 'Invalid code_challenge Parameter', 'error' => 'invalid_request'],
self::INVALID_SCOPE => ['statusCode' => 302, 'name' => 'Invalid scope Parameter', 'error' => 'invalid_request'],
self::INVALID_GRANT => ['statusCode' => 400, 'name' => 'The provided credentials were not valid.', 'error' => 'invalid_grant'],
self::INVALID_REQUEST => ['statusCode' => 400, 'name' => 'Invalid Request', 'error' => 'invalid_request'],
] </span>
</dt>
<dd></dd>
@ -192,6 +194,13 @@
<span>
&nbsp;= 10 </span>
</dt>
<dd></dd>
<dt class="phpdocumentor-table-of-contents__entry -constant -public">
<a href="classes/Taproot-IndieAuth-IndieAuthException.html#constant_INVALID_GRANT">INVALID_GRANT</a>
<span>
&nbsp;= 12 </span>
</dt>
<dd></dd>
<dt class="phpdocumentor-table-of-contents__entry -constant -public">
@ -199,6 +208,13 @@
<span>
&nbsp;= 7 </span>
</dt>
<dd></dd>
<dt class="phpdocumentor-table-of-contents__entry -constant -public">
<a href="classes/Taproot-IndieAuth-IndieAuthException.html#constant_INVALID_REQUEST">INVALID_REQUEST</a>
<span>
&nbsp;= 13 </span>
</dt>
<dd></dd>
<dt class="phpdocumentor-table-of-contents__entry -constant -public">
@ -358,7 +374,7 @@
<aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/IndieAuthException.php"><a href="files/src-indieauthexception.html"><abbr title="src/IndieAuthException.php">IndieAuthException.php</abbr></a></abbr>
:
<span class="phpdocumentor-element-found-in__line">23</span>
<span class="phpdocumentor-element-found-in__line">25</span>
</aside>
@ -380,6 +396,8 @@
self::INVALID_STATE =&gt; [&#039;statusCode&#039; =&gt; 302, &#039;name&#039; =&gt; &#039;Invalid state Parameter&#039;, &#039;error&#039; =&gt; &#039;invalid_request&#039;],
self::INVALID_CODE_CHALLENGE =&gt; [&#039;statusCode&#039; =&gt; 302, &#039;name&#039; =&gt; &#039;Invalid code_challenge Parameter&#039;, &#039;error&#039; =&gt; &#039;invalid_request&#039;],
self::INVALID_SCOPE =&gt; [&#039;statusCode&#039; =&gt; 302, &#039;name&#039; =&gt; &#039;Invalid scope Parameter&#039;, &#039;error&#039; =&gt; &#039;invalid_request&#039;],
self::INVALID_GRANT =&gt; [&#039;statusCode&#039; =&gt; 400, &#039;name&#039; =&gt; &#039;The provided credentials were not valid.&#039;, &#039;error&#039; =&gt; &#039;invalid_grant&#039;],
self::INVALID_REQUEST =&gt; [&#039;statusCode&#039; =&gt; 400, &#039;name&#039; =&gt; &#039;Invalid Request&#039;, &#039;error&#039; =&gt; &#039;invalid_request&#039;],
]</span>
</code>
@ -537,6 +555,31 @@
</article>
<article class="phpdocumentor-element -constant -public ">
<h4 class="phpdocumentor-element__name" id="constant_INVALID_GRANT">
INVALID_GRANT
<a href="classes/Taproot-IndieAuth-IndieAuthException.html#constant_INVALID_GRANT" class="headerlink"><i class="fas fa-link"></i></a>
</h4>
<aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/IndieAuthException.php"><a href="files/src-indieauthexception.html"><abbr title="src/IndieAuthException.php">IndieAuthException.php</abbr></a></abbr>
:
<span class="phpdocumentor-element-found-in__line">22</span>
</aside>
<code class="phpdocumentor-signature phpdocumentor-code ">
<span class="phpdocumentor-signature__visibility">public</span>
<span class="phpdocumentor-signature__type">mixed</span>
<span class="phpdocumentor-signature__name">INVALID_GRANT</span>
= <span class="phpdocumentor-signature__default-value">12</span>
</code>
</article>
<article class="phpdocumentor-element -constant -public ">
<h4 class="phpdocumentor-element__name" id="constant_INVALID_REDIRECT_URI">
@ -562,6 +605,31 @@
</article>
<article class="phpdocumentor-element -constant -public ">
<h4 class="phpdocumentor-element__name" id="constant_INVALID_REQUEST">
INVALID_REQUEST
<a href="classes/Taproot-IndieAuth-IndieAuthException.html#constant_INVALID_REQUEST" class="headerlink"><i class="fas fa-link"></i></a>
</h4>
<aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/IndieAuthException.php"><a href="files/src-indieauthexception.html"><abbr title="src/IndieAuthException.php">IndieAuthException.php</abbr></a></abbr>
:
<span class="phpdocumentor-element-found-in__line">23</span>
</aside>
<code class="phpdocumentor-signature phpdocumentor-code ">
<span class="phpdocumentor-signature__visibility">public</span>
<span class="phpdocumentor-signature__type">mixed</span>
<span class="phpdocumentor-signature__name">INVALID_REQUEST</span>
= <span class="phpdocumentor-signature__default-value">13</span>
</code>
</article>
<article class="phpdocumentor-element -constant -public ">
<h4 class="phpdocumentor-element__name" id="constant_INVALID_SCOPE">
@ -637,7 +705,7 @@
<aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/IndieAuthException.php"><a href="files/src-indieauthexception.html"><abbr title="src/IndieAuthException.php">IndieAuthException.php</abbr></a></abbr>
:
<span class="phpdocumentor-element-found-in__line">39</span>
<span class="phpdocumentor-element-found-in__line">43</span>
</aside>
@ -671,7 +739,7 @@
<aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/IndieAuthException.php"><a href="files/src-indieauthexception.html"><abbr title="src/IndieAuthException.php">IndieAuthException.php</abbr></a></abbr>
:
<span class="phpdocumentor-element-found-in__line">41</span>
<span class="phpdocumentor-element-found-in__line">45</span>
</aside>
@ -727,7 +795,7 @@
<aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/IndieAuthException.php"><a href="files/src-indieauthexception.html"><abbr title="src/IndieAuthException.php">IndieAuthException.php</abbr></a></abbr>
:
<span class="phpdocumentor-element-found-in__line">56</span>
<span class="phpdocumentor-element-found-in__line">60</span>
</aside>
@ -759,7 +827,7 @@
<aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/IndieAuthException.php"><a href="files/src-indieauthexception.html"><abbr title="src/IndieAuthException.php">IndieAuthException.php</abbr></a></abbr>
:
<span class="phpdocumentor-element-found-in__line">60</span>
<span class="phpdocumentor-element-found-in__line">64</span>
</aside>
@ -791,7 +859,7 @@
<aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/IndieAuthException.php"><a href="files/src-indieauthexception.html"><abbr title="src/IndieAuthException.php">IndieAuthException.php</abbr></a></abbr>
:
<span class="phpdocumentor-element-found-in__line">76</span>
<span class="phpdocumentor-element-found-in__line">80</span>
</aside>
@ -823,7 +891,7 @@
<aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/IndieAuthException.php"><a href="files/src-indieauthexception.html"><abbr title="src/IndieAuthException.php">IndieAuthException.php</abbr></a></abbr>
:
<span class="phpdocumentor-element-found-in__line">52</span>
<span class="phpdocumentor-element-found-in__line">56</span>
</aside>
@ -855,7 +923,7 @@
<aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/IndieAuthException.php"><a href="files/src-indieauthexception.html"><abbr title="src/IndieAuthException.php">IndieAuthException.php</abbr></a></abbr>
:
<span class="phpdocumentor-element-found-in__line">71</span>
<span class="phpdocumentor-element-found-in__line">75</span>
</aside>
<p class="phpdocumentor-summary">Trust Query Params</p>

View File

@ -1072,7 +1072,7 @@ error behaviour, one way to do so is to subclass <code class="prettyprint">Serve
<aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Server.php"><a href="files/src-server.html"><abbr title="src/Server.php">Server.php</abbr></a></abbr>
:
<span class="phpdocumentor-element-found-in__line">769</span>
<span class="phpdocumentor-element-found-in__line">771</span>
</aside>
<p class="phpdocumentor-summary">Handle Exception</p>

View File

@ -93,7 +93,7 @@
<aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
:
<span class="phpdocumentor-element-found-in__line">25</span>
<span class="phpdocumentor-element-found-in__line">26</span>
</aside>
<p class="phpdocumentor-summary">Filesystem JSON Token Storage</p>
@ -199,7 +199,7 @@ will likely be superceded by the SQLite version.</p>
<dt class="phpdocumentor-table-of-contents__entry -method -public">
<a href="classes/Taproot-IndieAuth-Storage-FilesystemJsonStorage.html#method_createAuthCode">createAuthCode()</a>
<span>
&nbsp;: <a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a>|null </span>
&nbsp;: string|null </span>
</dt>
<dd>Create Auth Code</dd>
@ -220,7 +220,7 @@ will likely be superceded by the SQLite version.</p>
<dt class="phpdocumentor-table-of-contents__entry -method -public">
<a href="classes/Taproot-IndieAuth-Storage-FilesystemJsonStorage.html#method_exchangeAuthCodeForAccessToken">exchangeAuthCodeForAccessToken()</a>
<span>
&nbsp;: <a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a>|null </span>
&nbsp;: array&lt;string|int, mixed&gt;|null </span>
</dt>
<dd>Exchange Authorization Code for Access Token</dd>
@ -234,7 +234,7 @@ will likely be superceded by the SQLite version.</p>
<dt class="phpdocumentor-table-of-contents__entry -method -public">
<a href="classes/Taproot-IndieAuth-Storage-FilesystemJsonStorage.html#method_getAccessToken">getAccessToken()</a>
<span>
&nbsp;: <a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a>|null </span>
&nbsp;: array&lt;string|int, mixed&gt;|null </span>
</dt>
<dd>Get Access Token</dd>
@ -299,7 +299,7 @@ will likely be superceded by the SQLite version.</p>
<aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
:
<span class="phpdocumentor-element-found-in__line">27</span>
<span class="phpdocumentor-element-found-in__line">28</span>
</aside>
@ -324,7 +324,7 @@ will likely be superceded by the SQLite version.</p>
<aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
:
<span class="phpdocumentor-element-found-in__line">26</span>
<span class="phpdocumentor-element-found-in__line">27</span>
</aside>
@ -349,7 +349,7 @@ will likely be superceded by the SQLite version.</p>
<aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
:
<span class="phpdocumentor-element-found-in__line">29</span>
<span class="phpdocumentor-element-found-in__line">30</span>
</aside>
@ -389,7 +389,7 @@ will likely be superceded by the SQLite version.</p>
<aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
:
<span class="phpdocumentor-element-found-in__line">33</span>
<span class="phpdocumentor-element-found-in__line">34</span>
</aside>
@ -419,7 +419,7 @@ will likely be superceded by the SQLite version.</p>
<aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
:
<span class="phpdocumentor-element-found-in__line">32</span>
<span class="phpdocumentor-element-found-in__line">33</span>
</aside>
@ -449,7 +449,7 @@ will likely be superceded by the SQLite version.</p>
<aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
:
<span class="phpdocumentor-element-found-in__line">36</span>
<span class="phpdocumentor-element-found-in__line">37</span>
</aside>
@ -479,7 +479,7 @@ will likely be superceded by the SQLite version.</p>
<aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
:
<span class="phpdocumentor-element-found-in__line">31</span>
<span class="phpdocumentor-element-found-in__line">32</span>
</aside>
@ -509,7 +509,7 @@ will likely be superceded by the SQLite version.</p>
<aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
:
<span class="phpdocumentor-element-found-in__line">34</span>
<span class="phpdocumentor-element-found-in__line">35</span>
</aside>
@ -543,7 +543,7 @@ will likely be superceded by the SQLite version.</p>
<aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
:
<span class="phpdocumentor-element-found-in__line">39</span>
<span class="phpdocumentor-element-found-in__line">40</span>
</aside>
@ -620,14 +620,14 @@ will likely be superceded by the SQLite version.</p>
<aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
:
<span class="phpdocumentor-element-found-in__line">67</span>
<span class="phpdocumentor-element-found-in__line">68</span>
</aside>
<p class="phpdocumentor-summary">Create Auth Code</p>
<code class="phpdocumentor-code phpdocumentor-signature ">
<span class="phpdocumentor-signature__visibility">public</span>
<span class="phpdocumentor-signature__name">createAuthCode</span><span>(</span><span class="phpdocumentor-signature__argument"><span class="phpdocumentor-signature__argument__return-type">array&lt;string|int, mixed&gt;&nbsp;</span><span class="phpdocumentor-signature__argument__name">$data</span></span><span>)</span><span> : </span><span class="phpdocumentor-signature__response_type"><a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a>|null</span></code>
<span class="phpdocumentor-signature__name">createAuthCode</span><span>(</span><span class="phpdocumentor-signature__argument"><span class="phpdocumentor-signature__argument__return-type">array&lt;string|int, mixed&gt;&nbsp;</span><span class="phpdocumentor-signature__argument__name">$data</span></span><span>)</span><span> : </span><span class="phpdocumentor-signature__response_type">string|null</span></code>
<section class="phpdocumentor-description"><p>This method is called on a valid authorization token request. The <code class="prettyprint">$data</code>
array is guaranteed to have the following keys:</p>
@ -659,9 +659,8 @@ key, which is a valid space-separated scope string listing the scopes granted by
the consent screen. Other implementations of <code class="prettyprint">AuthorizationFormInterface</code> may add additional
data, such as custom token-specific settings, or a custom token lifetime.</li>
</ul>
<p>This method should store the data passed to it, generate a corresponding authorization code,
and return an instance of <code class="prettyprint">Storage\Token</code> containing both. Implementations will usually add
an expiry time, usually under the <code class="prettyprint">valid_until</code> key.</p>
<p>This method should store the data passed to it, generate a corresponding authorization code
string, and return it.</p>
<p>The method call and data is structured such that implementations have a lot of flexibility
about how to store authorization code data. It could be a record in an auth code database
table, a record in a table which is used for both auth codes and access tokens, or even
@ -687,7 +686,7 @@ throw exceptions.</p>
<h5 class="phpdocumentor-return-value__heading">Return values</h5>
<span class="phpdocumentor-signature__response_type"><a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a>|null</span>
<span class="phpdocumentor-signature__response_type">string|null</span>
&mdash;
<section class="phpdocumentor-description"></section>
@ -706,13 +705,13 @@ throw exceptions.</p>
<aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
:
<span class="phpdocumentor-element-found-in__line">208</span>
<span class="phpdocumentor-element-found-in__line">228</span>
</aside>
<code class="phpdocumentor-code phpdocumentor-signature ">
<span class="phpdocumentor-signature__visibility">public</span>
<span class="phpdocumentor-signature__name">delete</span><span>(</span><span class="phpdocumentor-signature__argument"><span class="phpdocumentor-signature__argument__return-type">string&nbsp;</span><span class="phpdocumentor-signature__argument__name">$key</span></span><span>)</span><span> : </span><span class="phpdocumentor-signature__response_type">bool</span></code>
<span class="phpdocumentor-signature__name">delete</span><span>(</span><span class="phpdocumentor-signature__argument"><span class="phpdocumentor-signature__argument__return-type">string&nbsp;</span><span class="phpdocumentor-signature__argument__name">$key</span></span><span class="phpdocumentor-signature__argument"><span>[</span><span>, </span><span class="phpdocumentor-signature__argument__return-type">mixed&nbsp;</span><span class="phpdocumentor-signature__argument__name">$observeLock</span><span> = </span><span class="phpdocumentor-signature__argument__default-value">true</span><span> ]</span></span><span>)</span><span> : </span><span class="phpdocumentor-signature__response_type">bool</span></code>
<h5 class="phpdocumentor-argument-list__heading">Parameters</h5>
@ -723,6 +722,13 @@ throw exceptions.</p>
</dt>
<dd class="phpdocumentor-argument-list__definition">
</dd>
<dt class="phpdocumentor-argument-list__entry">
<span class="phpdocumentor-signature__argument__name">$observeLock</span>
: <span class="phpdocumentor-signature__argument__return-type">mixed</span>
= <span class="phpdocumentor-signature__argument__default-value">true</span> </dt>
<dd class="phpdocumentor-argument-list__definition">
</dd>
</dl>
@ -748,7 +754,7 @@ throw exceptions.</p>
<aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
:
<span class="phpdocumentor-element-found-in__line">149</span>
<span class="phpdocumentor-element-found-in__line">169</span>
</aside>
@ -780,29 +786,56 @@ throw exceptions.</p>
<aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
:
<span class="phpdocumentor-element-found-in__line">81</span>
<span class="phpdocumentor-element-found-in__line">82</span>
</aside>
<p class="phpdocumentor-summary">Exchange Authorization Code for Access Token</p>
<code class="phpdocumentor-code phpdocumentor-signature ">
<span class="phpdocumentor-signature__visibility">public</span>
<span class="phpdocumentor-signature__name">exchangeAuthCodeForAccessToken</span><span>(</span><span class="phpdocumentor-signature__argument"><span class="phpdocumentor-signature__argument__return-type">string&nbsp;</span><span class="phpdocumentor-signature__argument__name">$code</span></span><span>)</span><span> : </span><span class="phpdocumentor-signature__response_type"><a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a>|null</span></code>
<span class="phpdocumentor-signature__name">exchangeAuthCodeForAccessToken</span><span>(</span><span class="phpdocumentor-signature__argument"><span class="phpdocumentor-signature__argument__return-type">string&nbsp;</span><span class="phpdocumentor-signature__argument__name">$code</span></span><span class="phpdocumentor-signature__argument"><span>, </span><span class="phpdocumentor-signature__argument__return-type">callable&nbsp;</span><span class="phpdocumentor-signature__argument__name">$validateAuthCode</span></span><span>)</span><span> : </span><span class="phpdocumentor-signature__response_type">array&lt;string|int, mixed&gt;|null</span></code>
<section class="phpdocumentor-description"><p>Attempt to exchange an authorization code identified by <code class="prettyprint">$code</code> for
an access token, returning it in a <code class="prettyprint">Token</code> on success and null on error.</p>
an access token. Return an array of access token data to be passed onto
the client app on success, and null on error.</p>
<p>This method is called at the beginning of a code exchange request, before
further error checking or validation is applied. On an error, the created
access token is immediately revoked via <code class="prettyprint">revokeAccessToken()</code>.</p>
<p>For this reason, the token data in the returned Token object MUST include
the <code class="prettyprint">client_id</code> and <code class="prettyprint">redirect_uri</code> parameters associated with the
authorization code, as these are used by the IndieAuth Server for further
validation.</p>
<p>This method is responsible for ensuring that the matched auth code is
not expired. If it is, it should return null, presumably after deleting
the corresponding authorization code record.</p>
<p>If the exchanged access token should expire, this method should set its
expiry time, usually in a <code class="prettyprint">valid_until</code> key.</p>
further error checking or validation is applied. It should proceed as
follows.</p>
<ul>
<li>Attempt to fetch the authorization code data identified by $code. If
it does not exist or has expired, return null;</li>
<li>Pass the authorization code data array to $validateAuthCode for validation.
If there is a problem with the code, a <code class="prettyprint">Taproot\IndieAuth\IndieAuthException</code>
will be thrown. This method should catch it, invalidate the authorization
code data, then re-throw the exception for handling by Server.</li>
<li>If the authorization code data passed all checks, convert it into an access
token, invalidate the auth code to prevent re-use, and store the access token
data internally.</li>
<li>Return an array of access token data to be passed onto the client app. It MUST
contain the following keys:
<ul>
<li>
<code class="prettyprint">me</code>
</li>
<li>
<code class="prettyprint">access_token</code>
Additonally, it SHOULD contain the following keys:</li>
<li>
<code class="prettyprint">scope</code>, if the token grants any scope
And MAY contain additional keys, such as:</li>
<li>
<code class="prettyprint">profile</code>
</li>
<li>
<code class="prettyprint">expires_at</code>
</li>
</ul>
</li>
</ul>
<p>If the authorization code was redeemed at the authorization endpoint, Server will
only pass the <code class="prettyprint">me</code> and <code class="prettyprint">profile</code> keys onto the client. In both cases, it will filter
out <code class="prettyprint">code_challenge</code> keys to prevent that data from accidentally being leaked to
clients.</p>
</section>
<h5 class="phpdocumentor-argument-list__heading">Parameters</h5>
@ -812,16 +845,28 @@ expiry time, usually in a <code class="prettyprint">valid_until</code> key.</p>
: <span class="phpdocumentor-signature__argument__return-type">string</span>
</dt>
<dd class="phpdocumentor-argument-list__definition">
<section class="phpdocumentor-description"><p>The Authorization Code to attempt to exchange.</p>
</section>
</dd>
<dt class="phpdocumentor-argument-list__entry">
<span class="phpdocumentor-signature__argument__name">$validateAuthCode</span>
: <span class="phpdocumentor-signature__argument__return-type">callable</span>
</dt>
<dd class="phpdocumentor-argument-list__definition">
<section class="phpdocumentor-description"><p>A callable to perform additional validation if valid auth code data is found. Takes <code class="prettyprint">array $authCodeData</code>, raises <code class="prettyprint">Taproot\IndieAuth\IndieAuthException</code> on invalid data, which should be bubbled up to the caller after any clean-up. Returns void.</p>
</section>
</dd>
</dl>
<h5 class="phpdocumentor-return-value__heading">Return values</h5>
<span class="phpdocumentor-signature__response_type"><a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a>|null</span>
<span class="phpdocumentor-signature__response_type">array&lt;string|int, mixed&gt;|null</span>
&mdash;
<section class="phpdocumentor-description"></section>
<section class="phpdocumentor-description"><p>An array of access token data to return to the client on success, null on any error.</p>
</section>
</article>
@ -838,7 +883,7 @@ expiry time, usually in a <code class="prettyprint">valid_until</code> key.</p>
<aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
:
<span class="phpdocumentor-element-found-in__line">177</span>
<span class="phpdocumentor-element-found-in__line">197</span>
</aside>
@ -880,18 +925,17 @@ expiry time, usually in a <code class="prettyprint">valid_until</code> key.</p>
<aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
:
<span class="phpdocumentor-element-found-in__line">127</span>
<span class="phpdocumentor-element-found-in__line">147</span>
</aside>
<p class="phpdocumentor-summary">Get Access Token</p>
<code class="phpdocumentor-code phpdocumentor-signature ">
<span class="phpdocumentor-signature__visibility">public</span>
<span class="phpdocumentor-signature__name">getAccessToken</span><span>(</span><span class="phpdocumentor-signature__argument"><span class="phpdocumentor-signature__argument__return-type">string&nbsp;</span><span class="phpdocumentor-signature__argument__name">$token</span></span><span>)</span><span> : </span><span class="phpdocumentor-signature__response_type"><a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a>|null</span></code>
<span class="phpdocumentor-signature__name">getAccessToken</span><span>(</span><span class="phpdocumentor-signature__argument"><span class="phpdocumentor-signature__argument__return-type">string&nbsp;</span><span class="phpdocumentor-signature__argument__name">$token</span></span><span>)</span><span> : </span><span class="phpdocumentor-signature__response_type">array&lt;string|int, mixed&gt;|null</span></code>
<section class="phpdocumentor-description"><p>Fetch access token data identified by the token <code class="prettyprint">$token</code>, returning
null if it is expired or invalid. The data should be structured in
exactly the same way it was stored by <code class="prettyprint">exchangeAuthCodeForAccessToken</code>.</p>
null if it is expired or invalid.</p>
</section>
<h5 class="phpdocumentor-argument-list__heading">Parameters</h5>
@ -908,7 +952,7 @@ exactly the same way it was stored by <code class="prettyprint">exchangeAuthCode
<h5 class="phpdocumentor-return-value__heading">Return values</h5>
<span class="phpdocumentor-signature__response_type"><a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a>|null</span>
<span class="phpdocumentor-signature__response_type">array&lt;string|int, mixed&gt;|null</span>
&mdash;
<section class="phpdocumentor-description"></section>
@ -927,7 +971,7 @@ exactly the same way it was stored by <code class="prettyprint">exchangeAuthCode
<aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
:
<span class="phpdocumentor-element-found-in__line">218</span>
<span class="phpdocumentor-element-found-in__line">242</span>
</aside>
@ -969,7 +1013,7 @@ exactly the same way it was stored by <code class="prettyprint">exchangeAuthCode
<aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
:
<span class="phpdocumentor-element-found-in__line">199</span>
<span class="phpdocumentor-element-found-in__line">219</span>
</aside>
@ -1018,7 +1062,7 @@ exactly the same way it was stored by <code class="prettyprint">exchangeAuthCode
<aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
:
<span class="phpdocumentor-element-found-in__line">143</span>
<span class="phpdocumentor-element-found-in__line">163</span>
</aside>
<p class="phpdocumentor-summary">Revoke Access Token</p>
@ -1064,7 +1108,7 @@ or false on error, including if the token did not exist.</p>
<aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
:
<span class="phpdocumentor-element-found-in__line">61</span>
<span class="phpdocumentor-element-found-in__line">62</span>
</aside>
@ -1106,7 +1150,7 @@ or false on error, including if the token did not exist.</p>
<aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
:
<span class="phpdocumentor-element-found-in__line">247</span>
<span class="phpdocumentor-element-found-in__line">271</span>
</aside>
@ -1148,7 +1192,7 @@ or false on error, including if the token did not exist.</p>
<aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
:
<span class="phpdocumentor-element-found-in__line">223</span>
<span class="phpdocumentor-element-found-in__line">247</span>
</aside>

View File

@ -133,21 +133,21 @@ reason, is indicated by returning either <code class="prettyprint">null</code> o
<dt class="phpdocumentor-table-of-contents__entry -method -public">
<a href="classes/Taproot-IndieAuth-Storage-TokenStorageInterface.html#method_createAuthCode">createAuthCode()</a>
<span>
&nbsp;: <a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a>|null </span>
&nbsp;: string|null </span>
</dt>
<dd>Create Auth Code</dd>
<dt class="phpdocumentor-table-of-contents__entry -method -public">
<a href="classes/Taproot-IndieAuth-Storage-TokenStorageInterface.html#method_exchangeAuthCodeForAccessToken">exchangeAuthCodeForAccessToken()</a>
<span>
&nbsp;: <a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a>|null </span>
&nbsp;: array&lt;string|int, mixed&gt;|null </span>
</dt>
<dd>Exchange Authorization Code for Access Token</dd>
<dt class="phpdocumentor-table-of-contents__entry -method -public">
<a href="classes/Taproot-IndieAuth-Storage-TokenStorageInterface.html#method_getAccessToken">getAccessToken()</a>
<span>
&nbsp;: <a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a>|null </span>
&nbsp;: array&lt;string|int, mixed&gt;|null </span>
</dt>
<dd>Get Access Token</dd>
@ -182,14 +182,14 @@ reason, is indicated by returning either <code class="prettyprint">null</code> o
<aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/TokenStorageInterface.php"><a href="files/src-storage-tokenstorageinterface.html"><abbr title="src/Storage/TokenStorageInterface.php">TokenStorageInterface.php</abbr></a></abbr>
:
<span class="phpdocumentor-element-found-in__line">79</span>
<span class="phpdocumentor-element-found-in__line">78</span>
</aside>
<p class="phpdocumentor-summary">Create Auth Code</p>
<code class="phpdocumentor-code phpdocumentor-signature ">
<span class="phpdocumentor-signature__visibility">public</span>
<span class="phpdocumentor-signature__name">createAuthCode</span><span>(</span><span class="phpdocumentor-signature__argument"><span class="phpdocumentor-signature__argument__return-type">array&lt;string|int, mixed&gt;&nbsp;</span><span class="phpdocumentor-signature__argument__name">$data</span></span><span>)</span><span> : </span><span class="phpdocumentor-signature__response_type"><a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a>|null</span></code>
<span class="phpdocumentor-signature__name">createAuthCode</span><span>(</span><span class="phpdocumentor-signature__argument"><span class="phpdocumentor-signature__argument__return-type">array&lt;string|int, mixed&gt;&nbsp;</span><span class="phpdocumentor-signature__argument__name">$data</span></span><span>)</span><span> : </span><span class="phpdocumentor-signature__response_type">string|null</span></code>
<section class="phpdocumentor-description"><p>This method is called on a valid authorization token request. The <code class="prettyprint">$data</code>
array is guaranteed to have the following keys:</p>
@ -221,9 +221,8 @@ key, which is a valid space-separated scope string listing the scopes granted by
the consent screen. Other implementations of <code class="prettyprint">AuthorizationFormInterface</code> may add additional
data, such as custom token-specific settings, or a custom token lifetime.</li>
</ul>
<p>This method should store the data passed to it, generate a corresponding authorization code,
and return an instance of <code class="prettyprint">Storage\Token</code> containing both. Implementations will usually add
an expiry time, usually under the <code class="prettyprint">valid_until</code> key.</p>
<p>This method should store the data passed to it, generate a corresponding authorization code
string, and return it.</p>
<p>The method call and data is structured such that implementations have a lot of flexibility
about how to store authorization code data. It could be a record in an auth code database
table, a record in a table which is used for both auth codes and access tokens, or even
@ -249,7 +248,7 @@ throw exceptions.</p>
<h5 class="phpdocumentor-return-value__heading">Return values</h5>
<span class="phpdocumentor-signature__response_type"><a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a>|null</span>
<span class="phpdocumentor-signature__response_type">string|null</span>
&mdash;
<section class="phpdocumentor-description"></section>
@ -268,29 +267,56 @@ throw exceptions.</p>
<aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/TokenStorageInterface.php"><a href="files/src-storage-tokenstorageinterface.html"><abbr title="src/Storage/TokenStorageInterface.php">TokenStorageInterface.php</abbr></a></abbr>
:
<span class="phpdocumentor-element-found-in__line">103</span>
<span class="phpdocumentor-element-found-in__line">119</span>
</aside>
<p class="phpdocumentor-summary">Exchange Authorization Code for Access Token</p>
<code class="phpdocumentor-code phpdocumentor-signature ">
<span class="phpdocumentor-signature__visibility">public</span>
<span class="phpdocumentor-signature__name">exchangeAuthCodeForAccessToken</span><span>(</span><span class="phpdocumentor-signature__argument"><span class="phpdocumentor-signature__argument__return-type">string&nbsp;</span><span class="phpdocumentor-signature__argument__name">$code</span></span><span>)</span><span> : </span><span class="phpdocumentor-signature__response_type"><a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a>|null</span></code>
<span class="phpdocumentor-signature__name">exchangeAuthCodeForAccessToken</span><span>(</span><span class="phpdocumentor-signature__argument"><span class="phpdocumentor-signature__argument__return-type">string&nbsp;</span><span class="phpdocumentor-signature__argument__name">$code</span></span><span class="phpdocumentor-signature__argument"><span>, </span><span class="phpdocumentor-signature__argument__return-type">callable&nbsp;</span><span class="phpdocumentor-signature__argument__name">$validateAuthCode</span></span><span>)</span><span> : </span><span class="phpdocumentor-signature__response_type">array&lt;string|int, mixed&gt;|null</span></code>
<section class="phpdocumentor-description"><p>Attempt to exchange an authorization code identified by <code class="prettyprint">$code</code> for
an access token, returning it in a <code class="prettyprint">Token</code> on success and null on error.</p>
an access token. Return an array of access token data to be passed onto
the client app on success, and null on error.</p>
<p>This method is called at the beginning of a code exchange request, before
further error checking or validation is applied. On an error, the created
access token is immediately revoked via <code class="prettyprint">revokeAccessToken()</code>.</p>
<p>For this reason, the token data in the returned Token object MUST include
the <code class="prettyprint">client_id</code> and <code class="prettyprint">redirect_uri</code> parameters associated with the
authorization code, as these are used by the IndieAuth Server for further
validation.</p>
<p>This method is responsible for ensuring that the matched auth code is
not expired. If it is, it should return null, presumably after deleting
the corresponding authorization code record.</p>
<p>If the exchanged access token should expire, this method should set its
expiry time, usually in a <code class="prettyprint">valid_until</code> key.</p>
further error checking or validation is applied. It should proceed as
follows.</p>
<ul>
<li>Attempt to fetch the authorization code data identified by $code. If
it does not exist or has expired, return null;</li>
<li>Pass the authorization code data array to $validateAuthCode for validation.
If there is a problem with the code, a <code class="prettyprint">Taproot\IndieAuth\IndieAuthException</code>
will be thrown. This method should catch it, invalidate the authorization
code data, then re-throw the exception for handling by Server.</li>
<li>If the authorization code data passed all checks, convert it into an access
token, invalidate the auth code to prevent re-use, and store the access token
data internally.</li>
<li>Return an array of access token data to be passed onto the client app. It MUST
contain the following keys:
<ul>
<li>
<code class="prettyprint">me</code>
</li>
<li>
<code class="prettyprint">access_token</code>
Additonally, it SHOULD contain the following keys:</li>
<li>
<code class="prettyprint">scope</code>, if the token grants any scope
And MAY contain additional keys, such as:</li>
<li>
<code class="prettyprint">profile</code>
</li>
<li>
<code class="prettyprint">expires_at</code>
</li>
</ul>
</li>
</ul>
<p>If the authorization code was redeemed at the authorization endpoint, Server will
only pass the <code class="prettyprint">me</code> and <code class="prettyprint">profile</code> keys onto the client. In both cases, it will filter
out <code class="prettyprint">code_challenge</code> keys to prevent that data from accidentally being leaked to
clients.</p>
</section>
<h5 class="phpdocumentor-argument-list__heading">Parameters</h5>
@ -300,16 +326,28 @@ expiry time, usually in a <code class="prettyprint">valid_until</code> key.</p>
: <span class="phpdocumentor-signature__argument__return-type">string</span>
</dt>
<dd class="phpdocumentor-argument-list__definition">
<section class="phpdocumentor-description"><p>The Authorization Code to attempt to exchange.</p>
</section>
</dd>
<dt class="phpdocumentor-argument-list__entry">
<span class="phpdocumentor-signature__argument__name">$validateAuthCode</span>
: <span class="phpdocumentor-signature__argument__return-type">callable</span>
</dt>
<dd class="phpdocumentor-argument-list__definition">
<section class="phpdocumentor-description"><p>A callable to perform additional validation if valid auth code data is found. Takes <code class="prettyprint">array $authCodeData</code>, raises <code class="prettyprint">Taproot\IndieAuth\IndieAuthException</code> on invalid data, which should be bubbled up to the caller after any clean-up. Returns void.</p>
</section>
</dd>
</dl>
<h5 class="phpdocumentor-return-value__heading">Return values</h5>
<span class="phpdocumentor-signature__response_type"><a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a>|null</span>
<span class="phpdocumentor-signature__response_type">array&lt;string|int, mixed&gt;|null</span>
&mdash;
<section class="phpdocumentor-description"></section>
<section class="phpdocumentor-description"><p>An array of access token data to return to the client on success, null on any error.</p>
</section>
</article>
@ -326,18 +364,17 @@ expiry time, usually in a <code class="prettyprint">valid_until</code> key.</p>
<aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/TokenStorageInterface.php"><a href="files/src-storage-tokenstorageinterface.html"><abbr title="src/Storage/TokenStorageInterface.php">TokenStorageInterface.php</abbr></a></abbr>
:
<span class="phpdocumentor-element-found-in__line">112</span>
<span class="phpdocumentor-element-found-in__line">127</span>
</aside>
<p class="phpdocumentor-summary">Get Access Token</p>
<code class="phpdocumentor-code phpdocumentor-signature ">
<span class="phpdocumentor-signature__visibility">public</span>
<span class="phpdocumentor-signature__name">getAccessToken</span><span>(</span><span class="phpdocumentor-signature__argument"><span class="phpdocumentor-signature__argument__return-type">string&nbsp;</span><span class="phpdocumentor-signature__argument__name">$token</span></span><span>)</span><span> : </span><span class="phpdocumentor-signature__response_type"><a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a>|null</span></code>
<span class="phpdocumentor-signature__name">getAccessToken</span><span>(</span><span class="phpdocumentor-signature__argument"><span class="phpdocumentor-signature__argument__return-type">string&nbsp;</span><span class="phpdocumentor-signature__argument__name">$token</span></span><span>)</span><span> : </span><span class="phpdocumentor-signature__response_type">array&lt;string|int, mixed&gt;|null</span></code>
<section class="phpdocumentor-description"><p>Fetch access token data identified by the token <code class="prettyprint">$token</code>, returning
null if it is expired or invalid. The data should be structured in
exactly the same way it was stored by <code class="prettyprint">exchangeAuthCodeForAccessToken</code>.</p>
null if it is expired or invalid.</p>
</section>
<h5 class="phpdocumentor-argument-list__heading">Parameters</h5>
@ -354,7 +391,7 @@ exactly the same way it was stored by <code class="prettyprint">exchangeAuthCode
<h5 class="phpdocumentor-return-value__heading">Return values</h5>
<span class="phpdocumentor-signature__response_type"><a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a>|null</span>
<span class="phpdocumentor-signature__response_type">array&lt;string|int, mixed&gt;|null</span>
&mdash;
<section class="phpdocumentor-description"></section>
@ -373,7 +410,7 @@ exactly the same way it was stored by <code class="prettyprint">exchangeAuthCode
<aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/TokenStorageInterface.php"><a href="files/src-storage-tokenstorageinterface.html"><abbr title="src/Storage/TokenStorageInterface.php">TokenStorageInterface.php</abbr></a></abbr>
:
<span class="phpdocumentor-element-found-in__line">120</span>
<span class="phpdocumentor-element-found-in__line">135</span>
</aside>
<p class="phpdocumentor-summary">Revoke Access Token</p>

View File

@ -112,7 +112,6 @@
</ul>
<h3>T</h3>
<ul class="phpdocumentor-list">
<li><a href="files/src-storage-token.html"><abbr title="src/Storage/Token.php">Token.php</abbr></a></li>
<li><a href="files/src-storage-tokenstorageinterface.html"><abbr title="src/Storage/TokenStorageInterface.php">TokenStorageInterface.php</abbr></a></li>
</ul>
<section data-search-results class="phpdocumentor-search-results phpdocumentor-search-results--hidden">

View File

@ -280,6 +280,16 @@ Search.appendIndex(
"name": "INVALID_SCOPE",
"summary": "",
"url": "classes/Taproot-IndieAuth-IndieAuthException.html#constant_INVALID_SCOPE"
}, {
"fqsen": "\\Taproot\\IndieAuth\\IndieAuthException\u003A\u003AINVALID_GRANT",
"name": "INVALID_GRANT",
"summary": "",
"url": "classes/Taproot-IndieAuth-IndieAuthException.html#constant_INVALID_GRANT"
}, {
"fqsen": "\\Taproot\\IndieAuth\\IndieAuthException\u003A\u003AINVALID_REQUEST",
"name": "INVALID_REQUEST",
"summary": "",
"url": "classes/Taproot-IndieAuth-IndieAuthException.html#constant_INVALID_REQUEST"
}, {
"fqsen": "\\Taproot\\IndieAuth\\IndieAuthException\u003A\u003AEXC_INFO",
"name": "EXC_INFO",
@ -640,46 +650,6 @@ Search.appendIndex(
"name": "Sqlite3Storage",
"summary": "",
"url": "classes/Taproot-IndieAuth-Storage-Sqlite3Storage.html"
}, {
"fqsen": "\\Taproot\\IndieAuth\\Storage\\Token",
"name": "Token",
"summary": "",
"url": "classes/Taproot-IndieAuth-Storage-Token.html"
}, {
"fqsen": "\\Taproot\\IndieAuth\\Storage\\Token\u003A\u003A__construct\u0028\u0029",
"name": "__construct",
"summary": "",
"url": "classes/Taproot-IndieAuth-Storage-Token.html#method___construct"
}, {
"fqsen": "\\Taproot\\IndieAuth\\Storage\\Token\u003A\u003AgetData\u0028\u0029",
"name": "getData",
"summary": "",
"url": "classes/Taproot-IndieAuth-Storage-Token.html#method_getData"
}, {
"fqsen": "\\Taproot\\IndieAuth\\Storage\\Token\u003A\u003AgetKey\u0028\u0029",
"name": "getKey",
"summary": "",
"url": "classes/Taproot-IndieAuth-Storage-Token.html#method_getKey"
}, {
"fqsen": "\\Taproot\\IndieAuth\\Storage\\Token\u003A\u003A__toString\u0028\u0029",
"name": "__toString",
"summary": "",
"url": "classes/Taproot-IndieAuth-Storage-Token.html#method___toString"
}, {
"fqsen": "\\Taproot\\IndieAuth\\Storage\\Token\u003A\u003AjsonSerialize\u0028\u0029",
"name": "jsonSerialize",
"summary": "",
"url": "classes/Taproot-IndieAuth-Storage-Token.html#method_jsonSerialize"
}, {
"fqsen": "\\Taproot\\IndieAuth\\Storage\\Token\u003A\u003A\u0024key",
"name": "key",
"summary": "",
"url": "classes/Taproot-IndieAuth-Storage-Token.html#property_key"
}, {
"fqsen": "\\Taproot\\IndieAuth\\Storage\\Token\u003A\u003A\u0024data",
"name": "data",
"summary": "",
"url": "classes/Taproot-IndieAuth-Storage-Token.html#property_data"
}, {
"fqsen": "\\Taproot\\IndieAuth\\Storage\\TokenStorageInterface",
"name": "TokenStorageInterface",

View File

@ -95,8 +95,6 @@
<dd>Filesystem JSON Token Storage</dd>
<dt class="phpdocumentor-table-of-contents__entry -class"><a href="classes/Taproot-IndieAuth-Storage-Sqlite3Storage.html"><abbr title="\Taproot\IndieAuth\Storage\Sqlite3Storage">Sqlite3Storage</abbr></a></dt>
<dd></dd>
<dt class="phpdocumentor-table-of-contents__entry -class"><a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a></dt>
<dd></dd>
</dl>

View File

@ -111,8 +111,6 @@
<dd>Filesystem JSON Token Storage</dd>
<dt class="phpdocumentor-table-of-contents__entry -class"><a href="classes/Taproot-IndieAuth-Storage-Sqlite3Storage.html"><abbr title="\Taproot\IndieAuth\Storage\Sqlite3Storage">Sqlite3Storage</abbr></a></dt>
<dd></dd>
<dt class="phpdocumentor-table-of-contents__entry -class"><a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a></dt>
<dd></dd>
</dl>

View File

@ -19,6 +19,8 @@ class IndieAuthException extends Exception {
const INVALID_STATE = 9;
const INVALID_CODE_CHALLENGE = 10;
const INVALID_SCOPE = 11;
const INVALID_GRANT = 12;
const INVALID_REQUEST = 13;
const EXC_INFO = [
self::INTERNAL_ERROR => ['statusCode' => 500, 'name' => 'Internal Server Error', 'explanation' => 'An internal server error occurred.'],
@ -34,6 +36,8 @@ class IndieAuthException extends Exception {
self::INVALID_STATE => ['statusCode' => 302, 'name' => 'Invalid state Parameter', 'error' => 'invalid_request'],
self::INVALID_CODE_CHALLENGE => ['statusCode' => 302, 'name' => 'Invalid code_challenge Parameter', 'error' => 'invalid_request'],
self::INVALID_SCOPE => ['statusCode' => 302, 'name' => 'Invalid scope Parameter', 'error' => 'invalid_request'],
self::INVALID_GRANT => ['statusCode' => 400, 'name' => 'The provided credentials were not valid.', 'error' => 'invalid_grant'],
self::INVALID_REQUEST => ['statusCode' => 400, 'name' => 'Invalid Request', 'error' => 'invalid_request'],
];
protected ServerRequestInterface $request;

View File

@ -324,65 +324,63 @@ class Server {
$bodyParams = $request->getParsedBody();
if (!isset($bodyParams['code'])) {
$this->logger->warning('The exchange request was missing the code parameter. Returning an error response.');
return new Response(400, ['content-type' => 'application/json'], json_encode([
'error' => 'invalid_request',
'error_description' => 'The code parameter was missing.'
]));
}
// Attempt to internally exchange the provided auth code for an access token.
// We do this before anything else so that the auth code is invalidated as soon as the request starts,
// and the resulting access token is revoked if we encounter an error. This ends up providing a simpler
// and more flexible interface for TokenStorage implementors.
if (array_key_exists('code', $bodyParams)) {
$token = $this->tokenStorage->exchangeAuthCodeForAccessToken($bodyParams['code']);
try {
// Call the token exchange method, passing in a callback which performs additional validation
// on the auth code before it gets exchanged.
$tokenData = $this->tokenStorage->exchangeAuthCodeForAccessToken($bodyParams['code'], function (array $authCode) use ($request, $bodyParams) {
// Verify that all required parameters are included.
$requiredParameters = ['client_id', 'redirect_uri', 'code_verifier'];
$missingRequiredParameters = array_filter($requiredParameters, function ($p) use ($bodyParams) {
return !array_key_exists($p, $bodyParams) || empty($bodyParams[$p]);
});
if (!empty($missingRequiredParameters)) {
$this->logger->warning('The exchange request was missing required parameters. Returning an error response.', ['missing' => $missingRequiredParameters]);
throw IndieAuthException::create(IndieAuthException::INVALID_REQUEST, $request);
}
if (is_null($token)) {
$this->logger->error('Attempting to exchange an auth code for a token resulted in null.', $bodyParams);
return new Response(400, ['content-type' => 'application/json'], json_encode([
'error' => 'invalid_grant',
'error_description' => 'The provided credentials were not valid.'
]));
}
} // The else case is handled by the code below.
// Verify that it was issued for the same client_id and redirect_uri
if ($authCode['client_id'] !== $bodyParams['client_id']
|| $authCode['redirect_uri'] !== $bodyParams['redirect_uri']) {
$this->logger->error("The provided client_id and/or redirect_uri did not match those stored in the token.");
throw IndieAuthException::create(IndieAuthException::INVALID_GRANT, $request);
}
// Verify that all required parameters are included.
$requiredParameters = ['client_id', 'redirect_uri', 'code', 'code_verifier'];
$missingRequiredParameters = array_filter($requiredParameters, function ($p) use ($bodyParams) {
return !array_key_exists($p, $bodyParams) || empty($bodyParams[$p]);
});
if (!empty($missingRequiredParameters)) {
if (isset($token)) {
$this->tokenStorage->revokeAccessToken($token->getKey());
}
$this->logger->warning('The exchange request was missing required parameters. Returning an error response.', ['missing' => $missingRequiredParameters]);
// Check that the supplied code_verifier hashes to the stored code_challenge
// TODO: support method = plain as well as S256.
if (!hash_equals($authCode['code_challenge'], generatePKCECodeChallenge($bodyParams['code_verifier']))) {
$this->logger->error("The provided code_verifier did not hash to the stored code_challenge");
throw IndieAuthException::create(IndieAuthException::INVALID_GRANT, $request);
}
// Check that this token either grants at most the profile scope.
$requestedScopes = explode(' ', $authCode['scope'] ?? '');
if (!empty($requestedScopes) && $requestedScopes != ['profile']) {
$this->logger->error("An exchange request for a token granting scopes other than “profile” was sent to the authorization endpoint.");
throw IndieAuthException::create(IndieAuthException::INVALID_GRANT, $request);
}
});
} catch (IndieAuthException $e) {
// If an exception was thrown, return a corresponding error response.
return new Response(400, ['content-type' => 'application/json'], json_encode([
'error' => 'invalid_request',
'error_description' => 'The following required parameters were missing or empty: ' . join(', ', $missingRequiredParameters)
'error' => $e->getInfo()['error'],
'error_description' => $e->getMessage()
]));
}
// Verify that it was issued for the same client_id and redirect_uri
if ($token->getData()['client_id'] !== $bodyParams['client_id']
|| $token->getData()['redirect_uri'] !== $bodyParams['redirect_uri']) {
$this->tokenStorage->revokeAccessToken($token->getKey());
$this->logger->error("The provided client_id and/or redirect_uri did not match those stored in the token.");
return new Response(400, ['content-type' => 'application/json'], json_encode([
'error' => 'invalid_grant',
'error_description' => 'The provided credentials were not valid.'
]));
}
// Check that the supplied code_verifier hashes to the stored code_challenge
// TODO: support method = plain as well as S256.
if (!hash_equals($token->getData()['code_challenge'], generatePKCECodeChallenge($bodyParams['code_verifier']))) {
$this->tokenStorage->revokeAccessToken($token->getKey());
$this->logger->error("The provided code_verifier did not hash to the stored code_challenge");
return new Response(400, ['content-type' => 'application/json'], json_encode([
'error' => 'invalid_grant',
'error_description' => 'The provided credentials were not valid.'
]));
}
// Check that this token either grants at most the profile scope.
$requestedScopes = explode(' ', $token->getData()['scope'] ?? '');
if (!empty($requestedScopes) && $requestedScopes != ['profile']) {
$this->tokenStorage->revokeAccessToken($token->getKey());
$this->logger->error("An exchange request for a token granting scopes other than “profile” was sent to the authorization endpoint.");
if (is_null($tokenData)) {
$this->logger->error('Attempting to exchange an auth code for a token resulted in null.', $bodyParams);
return new Response(400, ['content-type' => 'application/json'], json_encode([
'error' => 'invalid_grant',
'error_description' => 'The provided credentials were not valid.'
@ -395,7 +393,9 @@ class Server {
return new Response(200, [
'content-type' => 'application/json',
'cache-control' => 'no-store',
], json_encode(array_filter($token->getData(), function ($k) {
], json_encode(array_filter($tokenData, function ($k) {
// Prevent codes exchanged at the authorization endpoint from returning any information other than
// me and profile.
return in_array($k, ['me', 'profile']);
}, ARRAY_FILTER_USE_KEY)));
}
@ -586,7 +586,7 @@ class Server {
// Return a redirect to the client app.
return new Response(302, [
'Location' => appendQueryParams($queryParams['redirect_uri'], [
'code' => $authCode->getKey(),
'code' => $authCode,
'state' => $code['state']
]),
'Cache-control' => 'no-cache'
@ -679,79 +679,81 @@ class Server {
$bodyParams = $request->getParsedBody();
if (!isset($bodyParams['code'])) {
$this->logger->warning('The exchange request was missing the code parameter. Returning an error response.');
return new Response(400, ['content-type' => 'application/json'], json_encode([
'error' => 'invalid_request',
'error_description' => 'The code parameter was missing.'
]));
}
// Attempt to internally exchange the provided auth code for an access token.
// We do this before anything else so that the auth code is invalidated as soon as the request starts,
// and the resulting access token is revoked if we encounter an error. This ends up providing a simpler
// and more flexible interface for TokenStorage implementors.
if (array_key_exists('code', $bodyParams)) {
$token = $this->tokenStorage->exchangeAuthCodeForAccessToken($bodyParams['code']);
try {
// Call the token exchange method, passing in a callback which performs additional validation
// on the auth code before it gets exchanged.
$tokenData = $this->tokenStorage->exchangeAuthCodeForAccessToken($bodyParams['code'], function (array $authCode) use ($request, $bodyParams) {
// Verify that all required parameters are included.
$requiredParameters = ['client_id', 'redirect_uri', 'code_verifier'];
$missingRequiredParameters = array_filter($requiredParameters, function ($p) use ($bodyParams) {
return !array_key_exists($p, $bodyParams) || empty($bodyParams[$p]);
});
if (!empty($missingRequiredParameters)) {
$this->logger->warning('The exchange request was missing required parameters. Returning an error response.', ['missing' => $missingRequiredParameters]);
throw IndieAuthException::create(IndieAuthException::INVALID_REQUEST, $request);
}
if (is_null($token)) {
$this->logger->error('Attempting to exchange an auth code for a token resulted in null.', $bodyParams);
return new Response(400, ['content-type' => 'application/json'], json_encode([
'error' => 'invalid_grant',
'error_description' => 'The provided credentials were not valid.'
]));
}
}
// Verify that it was issued for the same client_id and redirect_uri
if ($authCode['client_id'] !== $bodyParams['client_id']
|| $authCode['redirect_uri'] !== $bodyParams['redirect_uri']) {
$this->logger->error("The provided client_id and/or redirect_uri did not match those stored in the token.");
throw IndieAuthException::create(IndieAuthException::INVALID_GRANT, $request);
}
// Verify that all required parameters are included.
$requiredParameters = ['client_id', 'redirect_uri', 'code', 'code_verifier'];
$missingRequiredParameters = array_filter($requiredParameters, function ($p) use ($bodyParams) {
return !array_key_exists($p, $bodyParams) || empty($bodyParams[$p]);
});
if (!empty($missingRequiredParameters)) {
if (isset($token)) {
$this->tokenStorage->revokeAccessToken($token->getKey());
}
$this->logger->warning('The exchange request was missing required parameters. Returning an error response.', ['missing' => $missingRequiredParameters]);
// Check that the supplied code_verifier hashes to the stored code_challenge
// TODO: support method = plain as well as S256.
if (!hash_equals($authCode['code_challenge'], generatePKCECodeChallenge($bodyParams['code_verifier']))) {
$this->logger->error("The provided code_verifier did not hash to the stored code_challenge");
throw IndieAuthException::create(IndieAuthException::INVALID_GRANT, $request);
}
// Check that scope is not empty.
if (empty($authCode['scope'])) {
$this->logger->error("An exchange request for a token with an empty scope was sent to the token endpoint.");
throw IndieAuthException::create(IndieAuthException::INVALID_GRANT, $request);
}
});
} catch (IndieAuthException $e) {
// If an exception was thrown, return a corresponding error response.
return new Response(400, ['content-type' => 'application/json'], json_encode([
'error' => 'invalid_request',
'error_description' => 'The following required parameters were missing or empty: ' . join(', ', $missingRequiredParameters)
'error' => $e->getInfo()['error'],
'error_description' => $e->getMessage()
]));
}
// Verify that it was issued for the same client_id and redirect_uri
if ($token->getData()['client_id'] !== $bodyParams['client_id']
|| $token->getData()['redirect_uri'] !== $bodyParams['redirect_uri']) {
$this->tokenStorage->revokeAccessToken($token->getKey());
$this->logger->error("The provided client_id and/or redirect_uri did not match those stored in the token.");
if (is_null($tokenData)) {
$this->logger->error('Attempting to exchange an auth code for a token resulted in null.', $bodyParams);
return new Response(400, ['content-type' => 'application/json'], json_encode([
'error' => 'invalid_grant',
'error_description' => 'The provided credentials were not valid.'
]));
}
// Check that the supplied code_verifier hashes to the stored code_challenge
// TODO: support method = plain as well as S256.
if (!hash_equals($token->getData()['code_challenge'], generatePKCECodeChallenge($bodyParams['code_verifier']))) {
$this->tokenStorage->revokeAccessToken($token->getKey());
$this->logger->error("The provided code_verifier did not hash to the stored code_challenge");
return new Response(400, ['content-type' => 'application/json'], json_encode([
'error' => 'invalid_grant',
'error_description' => 'The provided credentials were not valid.'
]));
}
// TODO: return an error if the token doesnt contain a me key.
// If the auth code was issued with no scope, return an error.
if (empty($token->getData()['scope'])) {
$this->tokenStorage->revokeAccessToken($token->getKey());
$this->logger->error("Cannot issue an access token with no scopes.");
return new Response(400, ['content-type' => 'application/json'], json_encode([
'error' => 'invalid_grant',
'error_description' => 'The provided credentials were not valid.'
]));
}
// If everything checks out, generate an access token and return it.
// If everything checked out, return {"me": "https://example.com"} response
return new Response(200, [
'content-type' => 'application/json',
'cache-control' => 'no-store'
'cache-control' => 'no-store',
], json_encode(array_merge([
'access_token' => $token->getKey(),
// Ensure that the token_type key is present, if tokenStorage doesnt include it.
'token_type' => 'Bearer'
], array_filter($token->getData(), function ($k) {
return in_array($k, ['me', 'profile', 'scope']);
], array_filter($tokenData, function ($k) {
// We should be able to trust the return data from tokenStorage, but theres no harm in
// preventing code_challenges from leaking, per OAuth2.
return !in_array($k, ['code_challenge', 'code_challenge_method']);
}, ARRAY_FILTER_USE_KEY))));
}

View File

@ -7,6 +7,7 @@ use Exception;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Taproot\IndieAuth\IndieAuthException;
use function Taproot\IndieAuth\generateRandomString;
@ -64,7 +65,7 @@ class FilesystemJsonStorage implements TokenStorageInterface, LoggerAwareInterfa
// TokenStorageInterface Methods.
public function createAuthCode(array $data): ?Token {
public function createAuthCode(array $data): ?string {
$authCode = generateRandomString(self::TOKEN_LENGTH);
$accessToken = $this->hash($authCode);
@ -75,17 +76,17 @@ class FilesystemJsonStorage implements TokenStorageInterface, LoggerAwareInterfa
if (!$this->put($accessToken, $data)) {
return null;
}
return new Token($authCode, $data);
return $authCode;
}
public function exchangeAuthCodeForAccessToken(string $code): ?Token {
public function exchangeAuthCodeForAccessToken(string $code, callable $validateAuthCode): ?array {
// Hash the auth code to get the theoretical matching access token filename.
$accessToken = $this->hash($code);
// Prevent the token file from being read, modified or deleted while were working with it.
// r+ to allow reading and writing, but to make sure we dont create the file if it doesnt
// already exist.
return $this->withLock($this->getPath($accessToken), 'r+', function ($fp) use ($accessToken) {
return $this->withLock($this->getPath($accessToken), 'r+', function ($fp) use ($accessToken, $validateAuthCode) {
// Read the file contents.
$fileContents = '';
while ($d = fread($fp, 1024)) { $fileContents .= $d; }
@ -100,6 +101,18 @@ class FilesystemJsonStorage implements TokenStorageInterface, LoggerAwareInterfa
// Make sure the auth code isnt expired.
if (($data['valid_until'] ?? 0) < time()) { return null; }
// The auth code is valid as far as we know, pass it to the validation callback passed from the
// Server.
try {
$validateAuthCode($data);
} catch (IndieAuthException $e) {
// If there was an issue with the auth code, delete it before bubbling the exception
// up to the Server for handling. We currently have a lock on the file path, so pass
// false to $observeLock to prevent a deadlock.
$this->delete($accessToken, false);
throw $e;
}
// If the access token is valid, mark it as redeemed and set a new expiry time.
$data['exchanged_at'] = time();
@ -120,11 +133,18 @@ class FilesystemJsonStorage implements TokenStorageInterface, LoggerAwareInterfa
if (fwrite($fp, $jsonData) === false) { return null; }
if (ftruncate($fp, strlen($jsonData)) === false) { return null; }
return new Token($accessToken, $data);
// Return the OAuth2-compatible access token data to the Server for passing onto
// the client app. Passed via array_filter to remove the scope key if scope is null.
return array_filter([
'access_token' => $accessToken,
'scope' => $data['scope'] ?? null,
'me' => $data['me'],
'profile' => $data['profile'] ?? null
]);
});
}
public function getAccessToken(string $token): ?Token {
public function getAccessToken(string $token): ?array {
$data = $this->get($token);
if (!is_array($data)) { return null; }
@ -137,7 +157,7 @@ class FilesystemJsonStorage implements TokenStorageInterface, LoggerAwareInterfa
if (is_int($data['valid_until']) && $data['valid_until'] < time()) { return null; }
// The token is valid!
return new Token($token, $data);
return $data;
}
public function revokeAccessToken(string $token): bool {
@ -205,12 +225,16 @@ class FilesystemJsonStorage implements TokenStorageInterface, LoggerAwareInterfa
});
}
public function delete(string $key): bool {
public function delete(string $key, $observeLock=true): bool {
$path = $this->getPath($key);
if (file_exists($path)) {
return $this->withLock($path, 'r', function ($fp) use ($path) {
if ($observeLock) {
return $this->withLock($path, 'r', function ($fp) use ($path) {
return unlink($path);
});
} else {
return unlink($path);
});
}
}
return false;
}

View File

@ -1,31 +0,0 @@
<?php declare(strict_types=1);
namespace Taproot\IndieAuth\Storage;
use JsonSerializable;
class Token implements JsonSerializable {
protected string $key;
protected array $data;
public function __construct(string $key, array $data) {
$this->key = $key;
$this->data = $data;
}
public function getData(): array {
return $this->data;
}
public function getKey(): string {
return $this->key;
}
public function __toString() {
return $this->key;
}
public function jsonSerialize() {
return $this->data;
}
}

View File

@ -61,9 +61,8 @@ interface TokenStorageInterface {
* the consent screen. Other implementations of `AuthorizationFormInterface` may add additional
* data, such as custom token-specific settings, or a custom token lifetime.
*
* This method should store the data passed to it, generate a corresponding authorization code,
* and return an instance of `Storage\Token` containing both. Implementations will usually add
* an expiry time, usually under the `valid_until` key.
* This method should store the data passed to it, generate a corresponding authorization code
* string, and return it.
*
* The method call and data is structured such that implementations have a lot of flexibility
* about how to store authorization code data. It could be a record in an auth code database
@ -76,40 +75,56 @@ interface TokenStorageInterface {
* recommended to log it internally for reference. For the same reason, this method should not
* throw exceptions.
*/
public function createAuthCode(array $data): ?Token;
public function createAuthCode(array $data): ?string;
/**
* Exchange Authorization Code for Access Token
*
* Attempt to exchange an authorization code identified by `$code` for
* an access token, returning it in a `Token` on success and null on error.
* an access token. Return an array of access token data to be passed onto
* the client app on success, and null on error.
*
* This method is called at the beginning of a code exchange request, before
* further error checking or validation is applied. On an error, the created
* access token is immediately revoked via `revokeAccessToken()`.
* further error checking or validation is applied. It should proceed as
* follows.
*
* For this reason, the token data in the returned Token object MUST include
* the `client_id` and `redirect_uri` parameters associated with the
* authorization code, as these are used by the IndieAuth Server for further
* validation.
* * Attempt to fetch the authorization code data identified by $code. If
* it does not exist or has expired, return null;
* * Pass the authorization code data array to $validateAuthCode for validation.
* If there is a problem with the code, a `Taproot\IndieAuth\IndieAuthException`
* will be thrown. This method should catch it, invalidate the authorization
* code data, then re-throw the exception for handling by Server.
* * If the authorization code data passed all checks, convert it into an access
* token, invalidate the auth code to prevent re-use, and store the access token
* data internally.
* * Return an array of access token data to be passed onto the client app. It MUST
* contain the following keys:
* * `me`
* * `access_token`
* Additonally, it SHOULD contain the following keys:
* * `scope`, if the token grants any scope
* And MAY contain additional keys, such as:
* * `profile`
* * `expires_at`
*
* This method is responsible for ensuring that the matched auth code is
* not expired. If it is, it should return null, presumably after deleting
* the corresponding authorization code record.
* If the authorization code was redeemed at the authorization endpoint, Server will
* only pass the `me` and `profile` keys onto the client. In both cases, it will filter
* out `code_challenge` keys to prevent that data from accidentally being leaked to
* clients.
*
* If the exchanged access token should expire, this method should set its
* expiry time, usually in a `valid_until` key.
* @param string $code The Authorization Code to attempt to exchange.
* @param callable $validateAuthCode A callable to perform additional validation if valid auth code data is found. Takes `array $authCodeData`, raises `Taproot\IndieAuth\IndieAuthException` on invalid data, which should be bubbled up to the caller after any clean-up. Returns void.
* @return array|null An array of access token data to return to the client on success, null on any error.
*/
public function exchangeAuthCodeForAccessToken(string $code): ?Token;
public function exchangeAuthCodeForAccessToken(string $code, callable $validateAuthCode): ?array;
/**
* Get Access Token
*
* Fetch access token data identified by the token `$token`, returning
* null if it is expired or invalid. The data should be structured in
* exactly the same way it was stored by `exchangeAuthCodeForAccessToken`.
* null if it is expired or invalid.
*/
public function getAccessToken(string $token): ?Token;
public function getAccessToken(string $token): ?array;
/**
* Revoke Access Token

View File

@ -7,7 +7,6 @@ use PHPUnit\Framework\TestCase;
use Taproot\IndieAuth\Storage\FilesystemJsonStorage;
const SECRET = '1111111111111111111111111111111111111111111111111111111111111111';
const TMP_DIR = __DIR__ . '/tmp';
class FilesystemJsonStorageTest extends TestCase {
protected function setUp(): void {

View File

@ -613,20 +613,21 @@ EOT
] as $endpointHandler) {
// Create an auth code.
$codeVerifier = generateRandomString(32);
$authCode = $storage->createAuthCode([
$authCodeData = [
'client_id' => 'https://client.example.com/',
'redirect_uri' => 'https://client.example.com/auth',
'code_challenge' => generatePKCECodeChallenge($codeVerifier),
'state' => '12345',
'code_challenge_method' => 'S256'
]);
];
$authCode = $storage->createAuthCode($authCodeData);
// Create what would by default be a successful request, then merge specific error-inducing params.
$req = (new ServerRequest('POST', 'https://example.com'))->withParsedBody(array_merge([
'grant_type' => 'authorization_code',
'code' => $authCode->getKey(),
'client_id' => $authCode->getData()['client_id'],
'redirect_uri' => $authCode->getData()['redirect_uri'],
'code' => $authCode,
'client_id' => $authCodeData['client_id'],
'redirect_uri' => $authCodeData['redirect_uri'],
'code_verifier' => $codeVerifier
], $params));
@ -644,21 +645,22 @@ EOT
// Create an auth code.
$codeVerifier = generateRandomString(32);
$authCode = $storage->createAuthCode([
$authCodeData = [
'client_id' => 'https://client.example.com/',
'redirect_uri' => 'https://client.example.com/auth',
'code_challenge' => generatePKCECodeChallenge($codeVerifier),
'state' => '12345',
'code_challenge_method' => 'S256',
'scope' => 'create update'
]);
];
$authCode = $storage->createAuthCode($authCodeData);
// Create what would by default be a successful request, then merge specific error-inducing params.
$req = (new ServerRequest('POST', 'https://example.com'))->withParsedBody([
'grant_type' => 'authorization_code',
'code' => $authCode->getKey(),
'client_id' => $authCode->getData()['client_id'],
'redirect_uri' => $authCode->getData()['redirect_uri'],
'code' => $authCode,
'client_id' => $authCodeData['client_id'],
'redirect_uri' => $authCodeData['redirect_uri'],
'code_verifier' => $codeVerifier
]);
@ -675,7 +677,7 @@ EOT
// Create an auth code.
$codeVerifier = generateRandomString(32);
$authCode = $storage->createAuthCode([
$authCodeData = [
'client_id' => 'https://client.example.com/',
'redirect_uri' => 'https://client.example.com/auth',
'code_challenge' => generatePKCECodeChallenge($codeVerifier),
@ -686,14 +688,15 @@ EOT
'profile' => [
'name' => 'Me'
]
]);
];
$authCode = $storage->createAuthCode($authCodeData);
// Create what would by default be a successful request, then merge specific error-inducing params.
$req = (new ServerRequest('POST', 'https://example.com'))->withParsedBody([
'grant_type' => 'authorization_code',
'code' => $authCode->getKey(),
'client_id' => $authCode->getData()['client_id'],
'redirect_uri' => $authCode->getData()['redirect_uri'],
'code' => $authCode,
'client_id' => $authCodeData['client_id'],
'redirect_uri' => $authCodeData['redirect_uri'],
'code_verifier' => $codeVerifier
]);
@ -732,20 +735,22 @@ EOT
// Create an auth code.
$codeVerifier = generateRandomString(32);
$authCode = $storage->createAuthCode([
$authCodeData = [
'client_id' => 'https://client.example.com/',
'redirect_uri' => 'https://client.example.com/auth',
'code_challenge' => generatePKCECodeChallenge($codeVerifier),
'state' => '12345',
'code_challenge_method' => 'S256'
]);
'code_challenge_method' => 'S256',
'me' => 'https://me.example.com'
];
$authCode = $storage->createAuthCode($authCodeData);
// Create what would by default be a successful request, then merge specific error-inducing params.
$req = (new ServerRequest('POST', 'https://example.com'))->withParsedBody([
'grant_type' => 'authorization_code',
'code' => $authCode->getKey(),
'client_id' => $authCode->getData()['client_id'],
'redirect_uri' => $authCode->getData()['redirect_uri'],
'code' => $authCode,
'client_id' => $authCodeData['client_id'],
'redirect_uri' => $authCodeData['redirect_uri'],
'code_verifier' => $codeVerifier
]);
@ -762,7 +767,7 @@ EOT
// Create an auth code.
$codeVerifier = generateRandomString(32);
$authCode = $storage->createAuthCode([
$authCodeData = [
'client_id' => 'https://client.example.com/',
'redirect_uri' => 'https://client.example.com/auth',
'code_challenge' => generatePKCECodeChallenge($codeVerifier),
@ -773,14 +778,15 @@ EOT
'profile' => [
'name' => 'Me'
]
]);
];
$authCode = $storage->createAuthCode($authCodeData);
// Create what would by default be a successful request, then merge specific error-inducing params.
$req = (new ServerRequest('POST', 'https://example.com'))->withParsedBody([
'grant_type' => 'authorization_code',
'code' => $authCode->getKey(),
'client_id' => $authCode->getData()['client_id'],
'redirect_uri' => $authCode->getData()['redirect_uri'],
'code' => $authCode,
'client_id' => $authCodeData['client_id'],
'redirect_uri' => $authCodeData['redirect_uri'],
'code_verifier' => $codeVerifier
]);
@ -789,11 +795,11 @@ EOT
$this->assertEquals(200, $res->getStatusCode());
$this->assertEquals('no-store', $res->getHeaderLine('cache-control'));
$resJson = json_decode((string) $res->getBody(), true);
$this->assertEquals(hash_hmac('sha256', $authCode->getKey(), SERVER_SECRET), $resJson['access_token']);
$this->assertEquals(hash_hmac('sha256', $authCode, SERVER_SECRET), $resJson['access_token']);
$this->assertEquals('Bearer', $resJson['token_type']);
$this->assertEquals($authCode->getData()['me'], $resJson['me']);
$this->assertEquals($authCode->getData()['profile'], $resJson['profile']);
$this->assertTrue(scopeEquals($authCode->getData()['scope'], $resJson['scope']));
$this->assertEquals($authCodeData['me'], $resJson['me']);
$this->assertEquals($authCodeData['profile'], $resJson['profile']);
$this->assertTrue(scopeEquals($authCodeData['scope'], $resJson['scope']));
}
/**
@ -832,15 +838,15 @@ function scopeEquals($scope1, $scope2): bool {
}
class NullTokenStorage implements TokenStorageInterface {
public function createAuthCode(array $data): ?Token {
public function createAuthCode(array $data): ?string {
return null;
}
public function getAccessToken(string $token): ?Token {
public function getAccessToken(string $token): ?array {
return null;
}
public function exchangeAuthCodeForAccessToken(string $code): ?Token {
public function exchangeAuthCodeForAccessToken(string $code, callable $validateAuthCode): ?array {
return null;
}