Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,60 @@ I do not have a need for a token endpoint like this myself, thus developing one

(The `href` must point at your `endpoint.php` file.)

### Metadata Discovery
Note that the use of `authorization_endpoint` and `token_endpoint` are deprecated but *SHOULD* be included for backwards compatibility.

Instead the [spec supports a metadata discovery endpoint](https://indieauth.spec.indieweb.org/#discovery).

Assuming the same configuration from above, the minimal metadata endpoint would look like
```json
{
"issuer": "https://example.com",
"authorization_endpoint": "https://example.com/auth/",
"token_endpoint": "https://example.com/auth/endpoint.php",
"introspection_endpoint": "https://example.com/auth/endpoint.php",
"code_challenge_methods_supported": ["S256"]
}
```
And the full recommended metadata endpoint would look like
```json
{
"issuer": "https://example.com",
"authorization_endpoint": "https://example.com/auth/",
"token_endpoint": "https://example.com/auth/endpoint.php",
"introspection_endpoint": "https://example.com/auth/endpoint.php",
"response_types_supported": ["code"],
"response_modes_supported": ["query"],
"grant_types_supported": ["authorization_code"],
"introspection_endpoint_auth_methods_supported": ["client_secret_basic"],
"service_documentation": "https://indieauth.spec.indieweb.org/#discovery",
"code_challenge_methods_supported": ["S256"]
}
```

## Endpoints

### Authorize
```curl
curl --include -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'grant_type=authorization_code&code=<from SelfAuth submit>&redirect_uri=https%3A%2F%2Fexample.com%2Fclient%2Fredirect.php&client_id=https%3A%2F%2Fexample.com%2Fclient%2F&code_verifier=<value used to generate code_challenge>' 'https://example.com/auth/endpoint.php?action=authorize'
```

### Consume
```curl
curl --include --oauth2-bearer <token from authorize> https://example.com/selfauth/token.php
```

### Revoke
```curl
curl --include -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'token=<token from authorize>' https://example.com/selfauth/token.php?action=revoke
```

### Introspection
```curl
curl --include -u https://example.com/client/:_ -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'token=<token from authorize>' https://example.com/selfauth/token.php?action=introspect
```
Note that this endpoint requires a fixed password of `_`.

## License

The BSD Zero Clause License (0BSD). Please see the LICENSE file for
Expand Down
48 changes: 45 additions & 3 deletions endpoint.php
Original file line number Diff line number Diff line change
Expand Up @@ -253,15 +253,57 @@ function invalidRequest(): void
header('HTTP/1.1 415 Unsupported Media Type');
exit();
}
$revoke = filter_input(INPUT_POST, 'action', FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => '@^revoke$@']]);
if (is_string($revoke)) {
$token = filter_input(INPUT_POST, 'token', FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => '@^[0-9a-f]+_[0-9a-f]+$@']]);
$action = filter_input(INPUT_GET, 'action', FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => '@^(revoke|introspect|authorize)$@']]);
$token = filter_input(INPUT_POST, 'token', FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => '@^[0-9a-f]+_[0-9a-f]+$@']]);
if (!is_string($action)) {
invalidRequest();
}
// check if is POST+revoke request
if ($action === 'revoke') {
if (is_string($token)) {
revokeToken($token);
}
header('HTTP/1.1 200 OK');
exit();
}
// check if is POST+introspection request
if ($action === 'introspect') {
$tokenInfo = retrieveToken($token);
if ($tokenInfo === null || $tokenInfo['active'] === '0') {
header('HTTP/1.1 200 OK');
header('Content-Type: application/json;charset=UTF-8');
exit(json_encode([
'active' => false,
]));
}
// Authorize resource server as per specification (see https://indieauth.spec.indieweb.org/#access-token-verification-response-p-1)
// For us this means we are expecting the Basic user to be the client ID of the consumer.
// With basic, everything after the first colon (:) is considered the password.
// Since we are working with URIs to identify, we need to handle
// the pattern scheme://domain/path:password
// To do this, we assume (and enforce) that the password is a single underscore (_).
Comment on lines +279 to +284
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should also verify that the request is from the client specified, checking their certificate to do so.

$basicAuth = $_SERVER['PHP_AUTH_USER'] . ':' . $_SERVER['PHP_AUTH_PW'];
$storedAuth = $tokenInfo['auth_client_id'] . ':_';
$storedAuthEncoded = urlencode($tokenInfo['auth_client_id']) . ':_';
if ($storedAuth !== $basicAuth && $storedAuthEncoded !== $basicAuth) {
header('WWW-Authenticate: Basic');
header('HTTP/1.0 401 Unauthorized');
exit('Unauthorized');
}
header('HTTP/1.1 200 OK');
header('Content-Type: application/json;charset=UTF-8');
exit(json_encode([
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should also markTokenUsed like in the GET/consumption endpoint.

'token_type' => 'Bearer',
'me' => $tokenInfo['auth_me'],
'sub' => $tokenInfo['auth_me'],
'client_id' => $tokenInfo['auth_client_id'],
'scope' => $tokenInfo['auth_scope'],
'iat' => strtotime($tokenInfo['created']),
'exp' => strtotime($tokenInfo['revoked']),
'active' => true,
]));
}
// else is a POST+authorization request
$request = array_merge(
filter_input_array(INPUT_POST, [
'grant_type' => [
Expand Down