{"id":21191683,"url":"https://github.com/indieweb/indieauth-client-php","last_synced_at":"2025-06-22T02:38:42.656Z","repository":{"id":12711355,"uuid":"15383883","full_name":"indieweb/indieauth-client-php","owner":"indieweb","description":"Sample implementation and helper methods for an IndieAuth client.","archived":false,"fork":false,"pushed_at":"2023-01-03T19:28:58.000Z","size":140,"stargazers_count":28,"open_issues_count":2,"forks_count":10,"subscribers_count":16,"default_branch":"main","last_synced_at":"2025-06-13T00:36:41.205Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/indieweb.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2013-12-22T23:31:54.000Z","updated_at":"2024-11-12T20:39:13.000Z","dependencies_parsed_at":"2023-01-13T17:06:03.005Z","dependency_job_id":null,"html_url":"https://github.com/indieweb/indieauth-client-php","commit_stats":null,"previous_names":[],"tags_count":40,"template":false,"template_full_name":null,"purl":"pkg:github/indieweb/indieauth-client-php","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/indieweb%2Findieauth-client-php","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/indieweb%2Findieauth-client-php/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/indieweb%2Findieauth-client-php/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/indieweb%2Findieauth-client-php/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/indieweb","download_url":"https://codeload.github.com/indieweb/indieauth-client-php/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/indieweb%2Findieauth-client-php/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260347821,"owners_count":22995287,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-20T19:04:27.536Z","updated_at":"2025-06-22T02:38:37.642Z","avatar_url":"https://github.com/indieweb.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# IndieAuth Client\n\nThis is a simple library to help with IndieAuth. There are two ways you may want to use it, either when developing an application that signs people in using IndieAuth, or when developing your own endpoint for issuing access tokens and you need to verify auth codes.\n\n![Build Status](https://github.com/indieweb/indieauth-client-php/actions/workflows/main.yml/badge.svg)\n\n## Installation\n\nTo install using [Composer](http://getcomposer.org), run:\n\n```\ncomposer require indieauth/client\n```\n\nThis library follows [PSR-4](https://www.php-fig.org/psr/psr-4/) for autoloading. To autoload, ensure that your application requires the Composer autoload file:\n\n```php \n\u003c?php\n\nrequire __DIR__ . '/vendor/autoload.php';\n```\n\n## Quick Start\n\nIf you want to get started quickly, and if you're okay with letting the library store things in the PHP session itself, then you can follow the examples below. If you need more control or want to step into the details of the IndieAuth flow, see the [Detailed Usage for Clients](#detailed-usage-for-clients) below.\n\n### Create a Login Form\n\nYou'll first need to create a login form to prompt the user to enter their website address. This might look something like the HTML below.\n\n```html\n\u003cform action=\"/login.php\" method=\"post\"\u003e\n  \u003cinput type=\"url\" name=\"url\"\u003e\n  \u003cinput type=\"submit\" value=\"Log In\"\u003e\n\u003c/form\u003e\n```\n\n### Begin the Login Flow\n\nIn the `login.php` file, you'll need to initialize the session, and tell this library to discover the user's endpoints. If everything succeeds, the library will return a URL that you can use to redirect the user to begin the flow.\n\nThe example below will have some really basic error handling, which you'll probably want to replace with something nicer looking.\n\nExample `login.php` file:\n\n```php\n\u003c?php\n\nif(!isset($_POST['url'])) {\n  die('Missing URL');\n}\n\n// Start a session for the library to be able to save state between requests.\nsession_start();\n\n// You'll need to set up two pieces of information before you can use the client,\n// the client ID and and the redirect URL.\n\n// The client ID should be the home page of your app.\nIndieAuth\\Client::$clientID = 'https://example.com/';\n\n// The redirect URL is where the user will be returned to after they approve the request.\nIndieAuth\\Client::$redirectURL = 'https://example.com/redirect.php';\n\n// Pass the user's URL and your requested scope to the client.\n// If you are writing a Micropub client, you should include at least the \"create\" scope.\n// If you are just trying to log the user in, you can omit the second parameter.\n\nlist($authorizationURL, $error) = IndieAuth\\Client::begin($_POST['url'], 'create');\n// or list($authorizationURL, $error) = IndieAuth\\Client::begin($_POST['url']);\n\n// Check whether the library was able to discover the necessary endpoints\nif($error) {\n  echo \"\u003cp\u003eError: \".$error['error'].\"\u003c/p\u003e\";\n  echo \"\u003cp\u003e\".$error['error_description'].\"\u003c/p\u003e\";\n} else {\n  // Redirect the user to their authorization endpoint\n  header('Location: '.$authorizationURL);\n}\n```\n\nThe following scopes have special meaning to the authorization server and will request the user's full profile info instead of just verifying their profile URL:\n\n* `profile`\n* `email`\n\nAny other scopes requested are assumed to be scopes that will request an access token be returned and the library will request an access token from the token endpoint in the next step.\n\n\n### Handling the Redirect\n\nIn your redirect file, pass all the query string parameters to the library and it will take care of things! It will use the authorization or token endpoint it found in the initial step, and will use the authorization code to verify the profile information or get an access token depending on whether you've requested any scopes.\n\nThe result will be the response from the authorization endpoint or token, which will contain the user's final `me` URL as well as the access token if you requested one or more scopes.\n\nIf there were any problems, the error information will be returned to you as well.\n\nThe library takes care of verifying the final returned profile URL has the same authorization endpoint as the entered URL.\n\nExample `redirect.php` file:\n\n```php\n\u003c?php\nsession_start();\nIndieAuth\\Client::$clientID = 'https://example.com/';\nIndieAuth\\Client::$redirectURL = 'https://example.com/redirect.php';\n\nlist($response, $error) = IndieAuth\\Client::complete($_GET);\n\nif($error) {\n  echo \"\u003cp\u003eError: \".$error['error'].\"\u003c/p\u003e\";\n  echo \"\u003cp\u003e\".$error['error_description'].\"\u003c/p\u003e\";\n} else {\n  // Login succeeded!\n  // The library will return the user's profile URL in the property \"me\"\n  // It will also return the full response from the authorization or token endpoint, as well as debug info\n  echo \"URL: \".$response['me'].\"\u003cbr\u003e\";\n  if(isset($response['response']['access_token'])) {\n    echo \"Access Token: \".$response['response']['access_token'].\"\u003cbr\u003e\";\n    echo \"Scope: \".$response['response']['scope'].\"\u003cbr\u003e\";\n  }\n\n  // The full parsed response from the endpoint will be available as:\n  // $response['response']\n\n  // The raw response:\n  // $response['raw_response']\n\n  // The HTTP response code:\n  // $response['response_code']\n\n  // You'll probably want to save the user's URL in the session\n  $_SESSION['user'] = $user['me'];\n}\n```\n\n\n## Detailed Usage for Clients\n\nThe first thing an IndieAuth client needs to do is to prompt the user to enter their web address. This is the basis of IndieAuth, where user identifiers are URLs. A typical IndieAuth sign-in form may look something like the following.\n\n```\nYour URL: [ example.com ]\n\n       [ Sign In ]\n```\n\nThis form will make a POST request to your app's server, at which point you can begin the IndieAuth discovery.\n\n### Discovering the required endpoints\n\nThe user will need to define endpoints for their URL before a client can perform authorization. These endpoints should be specified in the [IndieAuth Server Metadata](https://indieauth.spec.indieweb.org/#discovery-by-clients) endpoint using either an HTTP `Link` header or a `\u003clink\u003e` tag with relation `indieauth-metadata`:\n\n```html\n\u003clink rel=\"indieauth-metadata\" href=\"https://example.com/auth-metadata\"\u003e\n```\n\nThese discovery methods can be run all sequentially and the library will avoid making duplicate HTTP requests if it has already fetched the page once.\n\n#### Metadata Endpoint\n\nYou should first attempt to discover the metadata endpoint.\n\n```php\n// Normalize whatever the user entered to be a URL, e.g. \"example.com\" to \"https://example.com/\"\n$url = IndieAuth\\Client::normalizeMeURL($url);\n$metadataEndpoint = IndieAuth\\Client::discoverMetadataEndpoint($url);\n```\n\nIf it is found, then discover and verify the `issuer` parameter in the metadata:\n\n```php\nif ($metadataEndpoint) {\n  $response = self::discoverIssuer($metadataEndpoint);\n  if ($response instanceof IndieAuth\\ErrorResponse) {\n    // handle the error response, array with keys `error` and `error_description`\n    die(json_encode($response-\u003egetArray()));\n  }\n\n  $_SESSION['indieauth_issuer'] = $response;\n}\n```\n\nThe `issuer` value should be stored in the session to verify the Authorization Response later in the process (see below).\n\nIf the metadata endpoint is not found, the methods below will try to discover the individual `\u003clink\u003e` relations for backwards compability.\n\n#### Authorization Endpoint\n\nIn IndieAuth Server Metadata:\n```json\n{\n  \"authorization_endpoint\": \"https://indieauth.example.com/auth\"\n}\n```\n\nOr backwards compatible relations:\n```html\n\u003clink rel=\"authorization_endpoint\" href=\"https://example.com/auth\"\u003e\n```\n\n```\nLink: \u003chttps://example.com/auth\u003e; rel=\"authorization_endpoint\"\n```\n\nThe authorization endpoint allows a website to specify the location to direct the user's browser to when performing the initial authorization request.\n\nSince this can be a full URL, this allows a website to use an external server as its authorization endpoint. This allows people to delegate the handling and verification of authorization and authentication to an external service to speed up development. Of course at any point, the authorization server can be changed, and API clients and users will not need any modifications.\n\nThe following function will fetch the user's home page and return the authorization endpoint, or `false` if none was found.\n\n```php\n// Normalize whatever the user entered to be a URL, e.g. \"example.com\" to \"http://example.com/\"\n$url = IndieAuth\\Client::normalizeMeURL($url);\n$authorizationEndpoint = IndieAuth\\Client::discoverAuthorizationEndpoint($url);\n```\n\n#### Token Endpoint\n\nIn IndieAuth Server Metadata:\n```json\n{\n  \"token_endpoint\": \"https://indieauth.example.com/token\"\n}\n```\n\nOr backwards compatible relations:\n```html\n\u003clink rel=\"token_endpoint\" href=\"https://example.com/token\"\u003e\n```\n\n```\nLink: \u003chttps://example.com/token\u003e; rel=\"token_endpoint\"\n```\n\nThe token endpoint is where API clients will request access tokens. This will typically be a URL on the user's own website, although this can delegated to an external service as well.\n\nThe token endpoint is responsible for issuing an access token.\n\nThe following function will fetch the user's home page and return the token endpoint, or `false` if none was found.\n\n```php\n$url = IndieAuth\\Client::normalizeMeURL($url);\n$tokenEndpoint = IndieAuth\\Client::discoverTokenEndpoint($url);\n```\n\n#### Micropub Endpoint\n\n```html\n\u003clink rel=\"micropub\" href=\"https://example.com/micropub\"\u003e\n```\n\n```\nLink: \u003chttps://example.com/micropub\u003e; rel=\"micropub\"\n```\n\nThe [Micropub](https://indieweb.org/Micropub) endpoint defines where Micropub clients will make POST requests to create new posts on the user's website. When a Micropub client makes a request, the request will contain the previously-issued access token in the header, and the micropub endpoint will be able to validate the request given that access token.\n\nThe following function will fetch the user's home page and return the Micropub endpoint, or `false` if none was found.\n\n```php\n$url = IndieAuth\\Client::normalizeMeURL($url);\n$micropubEndpoint = IndieAuth\\Client::discoverMicropubEndpoint($url);\n```\n\nThe client may wish to discover all endpoints at the beginning, and cache the values in a session for later use.\n\n#### Microsub Endpoint\n\n```html\n\u003clink rel=\"microsub\" href=\"https://example.com/microsub\"\u003e\n```\n\n```\nLink: \u003chttps://example.com/microsub\u003e; rel=\"microsub\"\n```\n\nThe [Microsub](https://indieweb.comorg/Microsub) endpoint is for [readers](https://indieweb.org/reader). When a Micropub client makes a request, the request will contain the previously-issued access token in the header, and the endpoint will be able to validate the request given that access token.\n\nThe following function will fetch the user's home page and return the Microsub endpoint, or `false` if none was found.\n\n```php\n$url = IndieAuth\\Client::normalizeMeURL($url);\n$microsubEndpoint = IndieAuth\\Client::discoverMicrosubEndpoint($url);\n```\n\nThe client may wish to discover all endpoints at the beginning, and cache the values in a session for later use.\n\n#### Additional Endpoints\n\nIndieAuth Server Metadata allows defining some additional endpoints:\n\n**Token Revocation**\n\nReturn the `revocation_endpoint` or false if none was found:\n```php\n$url = IndieAuth\\Client::normalizeMeURL($url);\n$revocationEndpoint = IndieAuth\\Client::discoverRevocationEndpoint($url);\n```\n\n**Token Introspection**\n\nReturn the `introspection_endpoint` or false if none was found:\n```php\n$url = IndieAuth\\Client::normalizeMeURL($url);\n$introspectionEndpoint = IndieAuth\\Client::discoverIntrospectionEndpoint($url);\n```\n\n**Userinfo**\n\nReturn the `userinfo_endpoint` or false if none was found:\n```php\n$url = IndieAuth\\Client::normalizeMeURL($url);\n$userinfoEndpoint = IndieAuth\\Client::discoverUserinfoEndpoint($url);\n```\n\n### Building the authorization URL\n\nOnce the client has discovered the authorization server, it will need to build the authorization URL and direct the user's browser there.\n\nFor web sites, the client should send a 301 redirect to the authorization URL, or can open a new browser window. Native apps must launch a native browser window to the authorization URL and handle the redirect back to the native app appropriately.\n\n#### Authorization Endpoint Parameters\n* `me` - the URL the user entered to begin the flow.\n* `redirect_uri` - where the authorization server should redirect after authorization is complete.\n* `client_id` - the full URL to a web page of the application. This is used by the authorization server to discover the app's name and icon, and to validate the redirect URI.\n* `state` - the \"state\" parameter can be whatever the client wishes, and must also be sent to the token endpoint when the client exchanges the authorization code for an access token.\n* `scope` - the \"scope\" value is a space-separated list of permissions the client is requesting.\n* `code_challenge` - for [PKCE](https://oauth.net/2/pkce/) support, this is the hashed version of a secret the client generates when it starts.\n* `code_challenge_method` - this library will always use S256 as the hash method.\n\nThe following function will build the authorization URL given all the required parameters. If the authorization endpoint contains a query string, this function handles merging the existing query string parameters with the new parameters.\n\nThe following scopes have special meaning to the authorization server and will request the user's full profile info instead of just verifying their profile URL:\n\n* `profile`\n* `email`\n\n```php\n$url = IndieAuth\\Client::normalizeMeURL($url);\n\n$scope = 'profile create'; // Request profile info as well as an access token with the \"create\" scope\n\n// These are two random strings. The helper methods in the library will use an available random number generaton depending on the PHP version.\n$_SESSION['state'] = IndieAuth\\Client::generateStateParameter();\n$_SESSION['code_verifier'] = IndieAuth\\Client::generatePKCECodeVerifier();\n\n// you'll need to verify these later\n$_SESSION['user_entered_url'] = $url;\n$_SESSION['authorization_endpoint'] = $authorizationEndpoint;\n\n$authorizationURL = IndieAuth\\Client::buildAuthorizationURL($authorizationEndpoint, [\n  'me' =\u003e $url,\n  'redirect_uri' =\u003e $redirect_uri,\n  'client_id' =\u003e $client_id,\n  'scope' =\u003e $scope,\n  'state' =\u003e $_SESSION['state'],\n  'code_verifier' =\u003e $_SESSION['code_verifier'],\n]);\n```\n\nNote: Your code should include the plaintext random code verifier, the `IndieAuth\\Client` library will deal with hashing it for you in the request.\n\n### Getting authorization from the user\n\nAt this point, the authorization server interacts with the user, presenting them with a description of the request. This will look something like the following typical OAuth prompt:\n\n```\nAn application, \"Quill\" is requesting access to your website, \"aaronparecki.com\"\n\nThis application would like to be able to\n* **create** new entries on your website\n\n[ Approve ]   [ Deny ]\n```\n\nIf the user approves the request, the authorization server will redirect back to the redirect URL specified, with the following parameters added to the query string:\n\n* `code` - the authorization code\n* `state` - the state value provided in the request\n* `iss` - the issuer identifier for client validation\n\n\n### Validate the Authorization Response\n\nNext, the `state` and `iss` parameters must be verified to match the authorization request:\n\n```php\n$response = self::validateStateMatch($_GET, $_SESSION['indieauth_state']);\nif ($response instanceof IndieAuth\\ErrorResponse) {\n  // handle the error response, array with keys `error` and `error_description`\n  die(json_encode($response-\u003egetArray()));\n}\n\nif (isset($_SESSION['indieauth_issuer'])) {\n  $response = self::validateIssuerMatch($_GET, $_SESSION['indieauth_issuer']);\n  if ($response instanceof IndieAuth\\ErrorResponse) {\n    // handle the error response, array with keys `error` and `error_description`\n    die(json_encode($response-\u003egetArray()));\n  }\n}\n```\n\nIf both are valid, you can continue to exchange the authorization code.\n\n\n### Exchanging the authorization code for profile info\n\nIf the client is not trying to get an access token, just trying to verify the user's URL, then it will need to exchange the authorization code for profile information at the authorization endpoint.\n\nThe following function will make a POST request to the authorization endpoint and parse the result.\n\n```php\n$response = IndieAuth\\Client::exchangeAuthorizationCode($authorizationEndpoint, [\n  'code' =\u003e $_GET['code'],\n  'redirect_uri' =\u003e $redirect_uri,\n  'client_id' =\u003e $client_id,\n  'code_verifier' =\u003e $_SESSION['code_verifier'],\n]);\n```\n\nThe `$response` variable will include the response from the endpoint, such as the following:\n\n```php\narray(\n  'me' =\u003e 'https://aaronparecki.com/',\n  'response' =\u003e [\n    'me' =\u003e 'https://aaronparecki.com/',\n    'profile' =\u003e [\n      'name' =\u003e 'Aaron Parecki',\n      'url' =\u003e 'https://aaronparecki.com/',\n      'photo' =\u003e 'https://aaronparecki.com/images/profile.jpg'\n    ]\n  ],\n  'raw_response' =\u003e '{\"me\":\"https://aaronparecki.com/\",\"profile\":{\"name\":\"Aaron Parecki\",\"url\":\"https://aaronparecki.com/\",\"photo\":\"https://aaronparecki.com/images/profile.jpg\"}}',\n  'response_code' =\u003e 200\n);\n```\n\n\n### Exchanging the authorization code for an access token\n\nIf the client requested any scopes beyond profile scopes and is expecting an access token, it needs to exchange the authorization code for an access token at the token endpoint.\n\nTo get an access token, the client makes a POST request to the token endpoint, passing in the authorization code as well as the following parameters:\n\n* `code` - the authorization code obtained\n* `me` - the user's URL\n* `redirect_uri` - must match the redirect URI used in the request to obtain the authorization code\n* `client_id` - must match the client ID used in the initial request\n* `code_verifier` - if the client included a code challenge in the authorization request, then it must include the plaintext secret in the code exchange step here\n\nThe following function will make a POST request to the token endpoint and parse the result.\n\n```php\n$response = IndieAuth\\Client::exchangeAuthorizationCode($tokenEndpoint, [\n  'code' =\u003e $_GET['code'],\n  'redirect_uri' =\u003e $redirect_uri,\n  'client_id' =\u003e $client_id,\n  'code_verifier' =\u003e $_SESSION['code_verifier'],\n]);\n```\n\nThe `$response` variable will include the response from the token endpoint, such as the following:\n\n```php\narray(\n  'response' =\u003e [\n    'me' =\u003e 'https://aaronparecki.com/',\n    'access_token' =\u003e 'xxxxxxxxx',\n    'scope' =\u003e 'create'\n  ],\n  'raw_response' =\u003e '{\"me\":\"https://aaronparecki.com/\",\"access_token\":\"xxxxxxxxx\",\"scope\":\"create\"}',\n  'response_code' =\u003e 200\n);\n```\n\n\n### Verifying the Authorization Server\n\nIf you are using the individual methods instead of the begin/complete wrapper, then you'll need to double check that the URL returned has the same authorization endpoint as the one you used to begin the flow.\n\n```php\nif($response['me'] != $_SESSION['user_entered_url']) {\n  $authorizationEndpoint = IndieAuth\\Client::discoverAuthorizationEndpoint($response['me']);\n  if($authorizationEndpoint != $_SESSION['authorization_endpoint']) {\n    die(\"The authorization endpoint at the profile URL is not the same as the one used to begin the flow!\");\n  }\n}\n```\n\n\n### Making API requests\n\nAt this point, you are done using the IndieAuth client library and can begin making API requests directly to the user's website and micropub endpoint.\n\nTo make an API request, include the access token in an HTTP \"Authorization\" header like the following:\n\n```\nAuthorization: Bearer xxxxxxxx\n```\n\n### Additional Settings\n\nThere are a couple settings you can update if necessary:\n\n**HTTP User Agent**\n\nThe default UA mimics a browser by default. You can customize this by calling:\n\n```php\nIndieAuth\\Client::$http-\u003eset_user_agent('Your User Agent String');\n```\n\n**Number of bytes in state parameter**\n\nThe default state parameter is generated with 8 random bytes. You can change the number of bytes by calling:\n\n```php\nIndieAuth\\Client::$random_byte_count = 16;\n```\n\n\n# License\n\nCopyright 2013-2022 by Aaron Parecki and contributors\n\nAvailable under the MIT and Apache 2.0 licenses. See LICENSE.txt\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Findieweb%2Findieauth-client-php","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Findieweb%2Findieauth-client-php","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Findieweb%2Findieauth-client-php/lists"}