From a603fc3c30ae847250f14c4cd9f4643a35bbc734 Mon Sep 17 00:00:00 2001 From: smiley Date: Wed, 15 May 2024 00:56:24 +0200 Subject: [PATCH] :book: --- docs/Development/Using-examples.md | 1 - docs/Readme.md | 2 +- docs/Usage/Authorization.md | 6 +- docs/Usage/Quickstart.md | 56 ++++++++++++++- docs/Usage/Using-examples.md | 107 +++++++++++++++++++++++++++++ docs/index.rst | 2 +- 6 files changed, 167 insertions(+), 7 deletions(-) delete mode 100644 docs/Development/Using-examples.md create mode 100644 docs/Usage/Using-examples.md diff --git a/docs/Development/Using-examples.md b/docs/Development/Using-examples.md deleted file mode 100644 index 25333d2..0000000 --- a/docs/Development/Using-examples.md +++ /dev/null @@ -1 +0,0 @@ -# Using the examples diff --git a/docs/Readme.md b/docs/Readme.md index 6879a79..cc51cd6 100644 --- a/docs/Readme.md +++ b/docs/Readme.md @@ -66,6 +66,7 @@ The markdown sources for the [Read the Docs online manual](https://php-oauth.rea - [Quickstart](./Usage/Quickstart.md) - [Authorization flow](./Usage/Authorization.md) +- [Using the examples](./Usage/Using-examples.md) ### Development @@ -73,7 +74,6 @@ The markdown sources for the [Read the Docs online manual](https://php-oauth.rea - [Create a Provider](./Development/Create-provider.md) - [Additional functionality](./Development/Additional-functionality.md) - [Running tests](./Development/Test-suite.md) -- [Using the examples](./Development/Using-examples.md) ### Appendix diff --git a/docs/Usage/Authorization.md b/docs/Usage/Authorization.md index 3bac4bd..053e5f7 100644 --- a/docs/Usage/Authorization.md +++ b/docs/Usage/Authorization.md @@ -20,7 +20,7 @@ echo 'connect with GitHub!'; The method `OAuthInterface::getAuthorizationURL()` takes two (optional) parameters: -- `$params`: this array contains additional query parameters that will be added to the URL query (provider dependent) +- `$params`: this array contains additional query parameters that will be added to the URL query (service dependent) - `$scopes`: this array contains all scopes that will be used for this authorization When the user clicks the log-in link, just execute a `header()` to the provider's authorization URL. @@ -30,7 +30,7 @@ if($route === 'oauth-login'){ header('Location: '.$provider->getAuthorizationURL($params, $scopes)); // -> https://github.com/login/oauth/authorize?client_id= - // &redirect_uri=https%3A%2F%2Fexample.com%2Fcallback%2F&response_type=code + // &redirect_uri=https%3A%2F%2Fexample.com%2Foauth%2F&response_type=code // &scope=&state=&type=web_server } ``` @@ -47,7 +47,7 @@ while the similar `OAuth2Interface::getAccessToken()` takes two parameters `$cod ### OAuth2 -In our GitHub OAuth2 example we're now receiving the incoming callback to `https://example.com/callback/?code=&state=`. +In our GitHub OAuth2 example we're now receiving the incoming callback to `https://example.com/oauth/?code=&state=`. The `getAccessToken()` method initiates a backend request to the provider's server to exchange the temporary credentials for an access token: ```php diff --git a/docs/Usage/Quickstart.md b/docs/Usage/Quickstart.md index 2f153c2..eec33ed 100644 --- a/docs/Usage/Quickstart.md +++ b/docs/Usage/Quickstart.md @@ -40,6 +40,7 @@ $options->sessionStart = true; $storage = new SessionStorage($options); ``` + ### Using existing tokens Most of the time you'll probably just want to use/import an existing access token to use an API on the user's behalf. @@ -79,11 +80,36 @@ $provider->storeAccessToken($token); To invoke an OAuth provider, you'll need a `OAuthOptions` instance, a [PSR-18](https://www.php-fig.org/psr/psr-18/) `ClientInterface` (such as [`guzzlehttp/guzzle`](https://github.com/guzzle/guzzle) or [`chillerlan/php-httpinterface`](https://github.com/chillerlan/php-httpinterface)), along with the [PSR-17](https://www.php-fig.org/psr/psr-17/) factories `RequestFactoryInterface`, `StreamFactoryInterface` and `UriFactoryInterface`. An `OAuthStorageInterface` and a PSR-3 logger instance are optional. +First you'll need a PSR-18 compatible HTTP client, it should (at least) support [CURLOPT_SSL_VERIFYPEER](https://curl.se/libcurl/c/CURLOPT_SSL_VERIFYPEER.html) and have it enabled, +for which it needs a valid certificate (or [certificate bundle](https://curl.se/docs/caextract.html)). + +**chillerlan/HTTP** + +```php +$httpOptions = new \chillerlan\HTTP\HTTPOptions([ + 'ca_info' => '/path/to/cacert.pem', + 'user_agent' => OAuthInterface::USER_AGENT, +]); + +$http = new \chillerlan\HTTP\CurlClient(new ResponseFactory, $httpOptions); +``` -Let's invoke a `GitHub` provider: +**GuzzleHttp** ```php +$httpOptions = [ + 'verify' => '/path/to/cacert.pem', + 'headers' => [ + 'User-Agent' => OAuthInterface::USER_AGENT, + ], +]; + +$http = new \GuzzleHttp\Client($httpOptions); +``` + +Now let's invoke a `GitHub` provider: +```php // now we can invoke the provider $provider = new GitHub( $options, @@ -95,3 +121,31 @@ $provider = new GitHub( ); ``` + +### Provider factory + +Invoking a provider can be a bit chunky and perhaps gets messy as soon as you're using more than one OAuth service in your project. +In that case you can use the `OAuthProviderFactory` for convenience: + +```php +// invoke the provider factory with the common PSR instances +$providerFactory = new OAuthProviderFactory( + $http, + new RequestFactory, + new StreamFactory, + new UriFactory, + $logger, // optional +); + +// invoke the provider instance - the $options and $storage params are optional +$provider = $providerFactory->getProvider(GitHub::class, $options, $storage); +``` + +Additionally, the provider factory offers convenience methods to set a different logger instance and to get each of the PSR-17 factories: + +```php +$providerFactory->setLogger(new NullLogger); +$providerFactory->getRequestFactory(); +$providerFactory->getStreamFactory(); +$providerFactory->getUriFactory(); +``` diff --git a/docs/Usage/Using-examples.md b/docs/Usage/Using-examples.md new file mode 100644 index 0000000..bc9912a --- /dev/null +++ b/docs/Usage/Using-examples.md @@ -0,0 +1,107 @@ +# Using the examples + +OAuth is not exactly trivial and so is live testing an OAuth flow to make sure the implementation and all its details work as expected. +The examples - specifically [the get-token examples](https://github.com/chillerlan/php-oauth/tree/main/examples/get-token) - +are abstracted and condensed as far as possible to make using them as convenient as it can get. + + +## Requirements + +### Server + +Due to the nature of OAuth callbacks, you will need a webserver with public access, or rather, that is accepted as callback URL +by the service you're about to test, so `http://127.0.0.1/oauth/` will *definitely not* work - we'll assume +`https://example.com/oauth/` as the public callback URL throughout the examples. + +Further we'll assume that `/var/www/` (with an associated user `webmaster`) is the directory that holds all web related files and dependencies, +and `/var/www/htdocs/` is the root of the public website, which translates to `https://example.com/`, hence `/var/www/htdocs/oauth/` becomes the OAuth callback entrypoint at `https://example.com/oauth/`. +A web framework would probably just have `/var/www/htdocs/index.php` as its entry point and route all requests through it, +but that is not focus of this example. + + +### Dependencies + +To install the required dependencies, create a `composer.json` in `/var/www/` with the following content and run `composer install` (as user `webmaster`): + +```json +{ + "require": { + "php": "^8.1", + "chillerlan/php-dotenv": "^3.0", + "chillerlan/php-oauth": "^1.0", + "guzzlehttp/guzzle": "^7.8", + "monolog/monolog": "^3.5" + } +} +``` + +After that, copy the `examples` directory in the library root to `/var/www/oauth-examples/`. + + +### Configuration + +We'll store the configuration files in the user directory under `/home/webmaster/oauth-config/`. +The `FileStorage` in the `OAuthExampleProviderFactory` expects a `.filestorage` directory in the configuration directory +with permissions set to at least `0755`, otherwise PHP's `is_writable()` will fail. + +The cacert is also expected in the configuration, so we'll fetch a fresh one directly into it: + +```shell +curl -o /home/webmaster/oauth-config/cacert.pem https://curl.se/ca/cacert.pem +``` + +Finally, we need a `.env` file that contains the provider configuration. We'll [create an application on GitHub](https://github.com/settings/developers) +and save the credentials - the naming convention is `_` where the prefix is `OAuthInterface::IDENTIFIER`. + +``` +GITHUB_KEY= +GITHUB_SECRET= +GITHUB_CALLBACK_URL=https://example.com/oauth/ +``` + + +## Run the authorization flow + +The call chain starts with the [`index.php`](https://github.com/chillerlan/php-oauth/blob/main/public/index.php) in `/var/www/htdocs/oauth/`. +We'll need to adjust it to our example configuration from before: + +```php +$AUTOLOADER = '/var/www/vendor/autoload.php'; +$CFGDIR = '/home/webmaster/oauth-config/'; +$ENVFILE = '.env'; + +// additional parameters +$PARAMS = []; +// scopes to use with the given provider +$SCOPES = [ + GitHub::SCOPE_USER, + GitHub::SCOPE_PUBLIC_REPO, + GitHub::SCOPE_GIST, +]; + +// uncomment in case something goes wrong +#error_reporting(E_ALL); +#ini_set('display_errors', 1); + +require_once '/var/www/oauth-examples/get-token/GitHub.php'; +``` + +You can now navigate to `https://example.com/oauth/` where you will be greeted with a link "Connect with GitHub!". + +### Call chain + +Let's break down what happens behind the scenes: + +1. the `index.php` initializes a set of variables and includes an example from `/var/www/oauth-examples/get-token/`, here `GitHub.php` +2. `.php` includes [`/var/www/oauth-examples/provider-example-common.php`](https://github.com/chillerlan/php-oauth/blob/main/examples/provider-example-common.php), which: + - includes the composer autoloader given through `$AUTOLOADER` + - invokes the PSR-18 HTTP client, which is available as `$http` + - invokes the [`OAuthExampleProviderFactory`](https://github.com/chillerlan/php-oauth/blob/main/examples/OAuthExampleProviderFactory.php), which is available as `$factory` +3. the OAuth provider is invoked via `$factory->getProvider(::class)` in `.php` and becomes available as `$provider` +4. `.php` includes either `_flow-oauth1.php` or `_flow-oauth2.php` from `/var/www/oauth-examples/get-token/` +5. the [authorization flow](./Authorization.md) is executed: + - redirect to the URL received from `$provider->getAuthorizationURL($PARAMS, $SCOPES)` + - the token request is called with the data from the incoming callback via `$provider->getAccessToken(...)` + - the access token is stored under `/home/webmaster/oauth-config/.filestorage` and is displayed in the output once access is granted + +Profit! diff --git a/docs/index.rst b/docs/index.rst index 9df6246..f1b45b3 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -26,6 +26,7 @@ This work is licensed under the Creative Commons Attribution 4.0 International ( Usage/Quickstart.md Usage/Authorization.md + Usage/Using-examples.md .. toctree:: :maxdepth: 3 @@ -34,7 +35,6 @@ This work is licensed under the Creative Commons Attribution 4.0 International ( Development/Create-provider.md Development/Additional-functionality.md Development/Test-suite.md - Development/Using-examples.md .. toctree:: :maxdepth: 3