Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
| Total | n/a |
0 / 0 |
n/a |
0 / 0 |
CRAP | n/a |
0 / 0 |
|||
| 1 | <?php declare(strict_types=1); |
| 2 | |
| 3 | namespace Taproot\IndieAuth\Storage; |
| 4 | |
| 5 | /** |
| 6 | * Token Storage Interface |
| 7 | * |
| 8 | * This interface defines the bare minimum methods required by the Server class in order to |
| 9 | * implement auth code issuing and exchange flows, as well as to let external code get access |
| 10 | * tokens (for validating requests authenticated by an access_token) and revoke access tokens. |
| 11 | * |
| 12 | * The contract made between Server and implementations of TokenStorageInterface can broadly |
| 13 | * be summarized as follows: |
| 14 | * |
| 15 | * * The Server class is responsible for performing all validation which is |
| 16 | * defined in the IndieAuth spec and is not implementation-specific. For example: checking |
| 17 | * validity of all the authorization request parameters, checking that client_id, request_uri |
| 18 | * and code_verifier parameters in token exchange requests match with the stored data. |
| 19 | * * The TokenStorageInterface class is responsible for performing implementation-specific |
| 20 | * validation, such as assigning and checking expiry times for auth codes and access tokens. |
| 21 | * |
| 22 | * Implementations of TokenStorageInterface will usually implement additional methods to allow |
| 23 | * for lower-level querying, saving, updating and deletion of token data. These can be used to, |
| 24 | * for example, implement a UI for users to review and revoke currently valid access tokens. |
| 25 | * |
| 26 | * The behaviour of `TokenStorageInterface` is somewhat coupled with the implementation of your |
| 27 | * authentication handler callback (documented in `Server::__construct`) and `AuthorizationFormInterface`, |
| 28 | * so you should refer to the documentation for both while implementing `TokenStorageInterface`. |
| 29 | * |
| 30 | * Periodic deletion of expired tokens is out of the scope of this interface. Implementations may |
| 31 | * choose to offer a clean-up method, and potentially the option to call it once automatically |
| 32 | * on instantiation. |
| 33 | * |
| 34 | * None of the methods defined on TokenStorageInterface should throw exceptions. Failure, for any |
| 35 | * reason, is indicated by returning either `null` or `false`, depending on the method. |
| 36 | */ |
| 37 | interface TokenStorageInterface { |
| 38 | /** |
| 39 | * Create Authorization Code |
| 40 | * |
| 41 | * This method is called on a valid authorization token request. The `$data` |
| 42 | * array is guaranteed to have the following keys: |
| 43 | * |
| 44 | * * `client_id`: the validated `client_id` request parameter |
| 45 | * * `redirect_uri`: the validated `redirect_uri` request parameter |
| 46 | * * `state`: the `state` request parameter |
| 47 | * * `code_challenge`: the `code_challenge` request parameter |
| 48 | * * `code_challenge_method`: the `code_challenge_method` request parameter |
| 49 | * * `requested_scope`: the value of the `scope` request parameter |
| 50 | * * `me`: the value of the `me` key from the authentication result returned from the authentication request handler callback |
| 51 | * |
| 52 | * It may also have additional keys, which can come from the following locations: |
| 53 | * |
| 54 | * * All keys from the the authentication request handler callback result which do not clash |
| 55 | * with the keys listed above (with the exception of `me`, which is always present). Usually |
| 56 | * this is a `profile` key, but you may choose to return additional data from the authentication |
| 57 | * callback, which will be present in `$data`. |
| 58 | * * Any keys added by the `transformAuthorizationCode` method on the currently active instance |
| 59 | * of `Taproot\IndieAuth\Callback\AuthorizationFormInterface`. Typically this is the `scope` |
| 60 | * key, which is a valid space-separated scope string listing the scopes granted by the user on |
| 61 | * the consent screen. Other implementations of `AuthorizationFormInterface` may add additional |
| 62 | * data, such as custom token-specific settings, or a custom token lifetime. |
| 63 | * |
| 64 | * This method should store the data passed to it, generate a corresponding authorization code |
| 65 | * string, and return it. |
| 66 | * |
| 67 | * The method call and data is structured such that implementations have a lot of flexibility |
| 68 | * about how to store authorization code data. It could be a record in an auth code database |
| 69 | * table, a record in a table which is used for both auth codes and access tokens, or even |
| 70 | * a stateless self-encrypted token — note that in the latter case, you must persist a copy |
| 71 | * of the auth code with its exchanged access token to check against, in order to prevent it |
| 72 | * being exchanged more than once. |
| 73 | * |
| 74 | * On an error, return null. The reason for the error is irrelevant for calling code, but it’s |
| 75 | * recommended to log it internally for reference. For the same reason, this method should not |
| 76 | * throw exceptions. |
| 77 | */ |
| 78 | public function createAuthCode(array $data): ?string; |
| 79 | |
| 80 | /** |
| 81 | * Exchange Authorization Code for Access Token |
| 82 | * |
| 83 | * Attempt to exchange an authorization code identified by `$code` for |
| 84 | * an access token. Return an array of access token data to be passed onto |
| 85 | * the client app on success, and null on error. |
| 86 | * |
| 87 | * This method is called at the beginning of a code exchange request, before |
| 88 | * further error checking or validation is applied. It should proceed as |
| 89 | * follows. |
| 90 | * |
| 91 | * * Attempt to fetch the authorization code data identified by $code. If |
| 92 | * it does not exist or has expired, return null; |
| 93 | * * Pass the authorization code data array to $validateAuthCode for validation. |
| 94 | * If there is a problem with the code, a `Taproot\IndieAuth\IndieAuthException` |
| 95 | * will be thrown. This method should catch it, invalidate the authorization |
| 96 | * code data, then re-throw the exception for handling by Server. |
| 97 | * * If the authorization code data passed all checks, convert it into an access |
| 98 | * token, invalidate the auth code to prevent re-use, and store the access token |
| 99 | * data internally. |
| 100 | * * Return an array of access token data to be passed onto the client app. It MUST |
| 101 | * contain the following keys: |
| 102 | * * `me` |
| 103 | * * `access_token` |
| 104 | * Additonally, it SHOULD contain the following keys: |
| 105 | * * `scope`, if the token grants any scope |
| 106 | * And MAY contain additional keys, such as: |
| 107 | * * `profile` |
| 108 | * * `expires_at` |
| 109 | * |
| 110 | * If the authorization code was redeemed at the authorization endpoint, Server will |
| 111 | * only pass the `me` and `profile` keys onto the client. In both cases, it will filter |
| 112 | * out `code_challenge` keys to prevent that data from accidentally being leaked to |
| 113 | * clients. If an access token is present, the server will add `token_type: Bearer` |
| 114 | * automatically. |
| 115 | * |
| 116 | * A typical implementation might look like this: |
| 117 | * |
| 118 | * ```php |
| 119 | * function exchangeAuthCodeForAccessToken(string $code, callable $validateAuthCode): ?array { |
| 120 | * if (is_null($authCodeData = $this->fetchAuthCode($code))) { |
| 121 | * return null; |
| 122 | * } |
| 123 | * |
| 124 | * if (isExpired($authCodeData)) { |
| 125 | * return null; |
| 126 | * } |
| 127 | * |
| 128 | * try { |
| 129 | * $validateAuthCode($authCodeData); |
| 130 | * } catch (IndieAuthException $e) { |
| 131 | * $this->deleteAuthCode($code); |
| 132 | * throw $e; |
| 133 | * } |
| 134 | * |
| 135 | * return $this->newTokenFromAuthCodeData($authCodeData); |
| 136 | * } |
| 137 | * ``` |
| 138 | * |
| 139 | * Refer to reference implementations in the `Taproot\IndieAuth\Storage` namespace for |
| 140 | * reference. |
| 141 | * |
| 142 | * @param string $code The Authorization Code to attempt to exchange. |
| 143 | * @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. |
| 144 | * @return array|null An array of access token data to return to the client on success, null on any error. |
| 145 | */ |
| 146 | public function exchangeAuthCodeForAccessToken(string $code, callable $validateAuthCode): ?array; |
| 147 | |
| 148 | /** |
| 149 | * Get Access Token |
| 150 | * |
| 151 | * Fetch access token data identified by the token `$token`, returning |
| 152 | * null if it is expired or invalid. |
| 153 | */ |
| 154 | public function getAccessToken(string $token): ?array; |
| 155 | |
| 156 | /** |
| 157 | * Revoke Access Token |
| 158 | * |
| 159 | * Revoke the access token identified by `$token`. Return true on success, |
| 160 | * or false on error, including if the token did not exist. |
| 161 | */ |
| 162 | public function revokeAccessToken(string $token): bool; |
| 163 | } |