diff --git a/README.md b/README.md index 80d24f8..542cc78 100644 --- a/README.md +++ b/README.md @@ -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=&redirect_uri=https%3A%2F%2Fexample.com%2Fclient%2Fredirect.php&client_id=https%3A%2F%2Fexample.com%2Fclient%2F&code_verifier=' 'https://example.com/auth/endpoint.php?action=authorize' +``` + +### Consume +```curl +curl --include --oauth2-bearer https://example.com/selfauth/token.php +``` + +### Revoke +```curl +curl --include -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'token=' 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=' 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 diff --git a/endpoint.php b/endpoint.php index 5333fde..d4c3661 100644 --- a/endpoint.php +++ b/endpoint.php @@ -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 (_). + $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([ + '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' => [