pelmered / laravel-http-client-auth-helper
Requires
- php: ^8.2
- guzzlehttp/guzzle: ^7.0
- illuminate/support: ^10 || ^11
Requires (Dev)
- laravel/pint: ^1.0
- orchestra/testbench: ^9.0
- pestphp/pest: ^3.x-dev
- pestphp/pest-plugin-mutate: ^3.x-dev
- pestphp/pest-plugin-type-coverage: ^3.0.0
- phpmd/phpmd: ^2.15
- phpstan/phpstan: ^1.10
- spatie/laravel-ray: ^1.37.1
README
An easy-to-use helper for Laravel HTTP Client to make manage API requests with a two-step auth flow.
For example, OAuth2 or refresh tokens to get a new short-lived access token.
This helper takes care of all the headaches and boilerplate code with a simple and easy-to-use interface.
Features:
- Automatically fetches new access tokens when needed.
- Automatically caches the access tokens for their lifetime.
- Extends the Laravel HTTP Client to make it straightforward to use. Se the usage section below for examples.
- Supports common auth flows like OAuth2 and refresh tokens with most grant types.
- No configuration and no boilerplate code required. Just require and use with a simple and consise API.
- Supports callbacks to customize the behaviour when needed.
Vision, roadmap & plans for the future
I want to support as many common auth flows as possible.
If you have a use case that is not super obscure,
please open an issue where you provide as much detail as possible,
or submit a PR with a working solution.
Plans for 1.1 - Milestone
- Helper for automatically refresh tokens in the background before they expire.
- Add support for authorization_code and implicit OAuth2 grants.
- Tokens owned by a model. For example, tokens associated with a user model rather than the whole application.
Table of contents
- Requirements
- Vision, roadmap & plans for the future
- Contributing
- Installation
- Options reference
- Usage
Requirements
- PHP 8.2 or higher
- Laravel 10
Vision, roadmap & plans for the future
I want to support as many common auth flows as possible.
If you have a use case that is not super obscure,
please open an issue where you provide as much detail as possible,
or submit a PR.
Contributing
See Contribution Guide before sending pull requests.
Issues & Bug Reports
When you are submitting issues, I appreciate if you could provide a failing test case. That makes my job a lot easier.
I will try to fix reported issues as soon as possible, but I do this in my spare time, so I might not be able to do it immediately.
Installation
composer require pelmered/laravel-http-client-auth-helper
Options reference
scopes - array
Scopes to send when requesting an access token.
Typically only used for OAuth2 flows.
Possible options: array with strings
Default: []
authType - string
The type of authorization for the refresh token request.
Possible options: Credentials::AUTH_TYPE_BEARER
, Credentials::AUTH_TYPE_BODY
, Credentials::AUTH_TYPE_QUERY
, Credentials::AUTH_TYPE_BASIC
, Credentials::AUTH_TYPE_CUSTOM
,
Default: Credentials::AUTH_TYPE_BEARER
(='Bearer'
)
grantType - string
Grant type for OAuth2 flows.
Possible options: Credentials::GRANT_TYPE_CLIENT_CREDENTIALS
, Credentials::GRANT_TYPE_PASSWORD_CREDENTIALS
(authorization_code and implicit grants are not yet supported. See issue #3)
Default: Credentials::GRANT_TYPE_CLIENT_CREDENTIALS
(='client_credentials'
)
tokenType - string
How the access token should be applied to all subsequent requests.
Possible options: AccessToken::TOKEN_TYPE_BEARER
, AccessToken::TOKEN_TYPE_QUERY
, AccessToken::TOKEN_TYPE_CUSTOM
Default: AccessToken::TOKEN_TYPE_BEARER
(='Bearer'
)
tokenName - string
The name of the token field. This only applies for when the token is applied as a query parameter or to the body of the request.
Possible options: Any string
Default: 'token'
expires - int|string|Closure|Carbon
This determines when the access token expires.
Possible options:
integer - for how long until expiry in seconds)
string - Can be key of the field in response that contains the expiry of the token. Can also be a string with a date. This is then parsed by Carbon::parse so any format that Carbon can parse is acceptable.
Closure - A closure that receives the refresh response and can return any other acceptable value (integer, string or Carbon object).
Carbon - A Carbon object with the time of the expiry.
Default: 3600
accessToken - string|Closure
This is where the access token can be found on the refresh response.
Possible options:
string - The key of the access token in the refresh response.
Closure - A closure that receives the refresh response and should return the token as a string.
Default: 'access_token'
tokenTypeCustomCallback - ?Closure
A callback for giving dull control of how the authentication should be applied.
The closure receives the Http client and should return a new Http Client where the auth information has been appended.
Possible options:\ Any closure that returns a Http Client (Illuminate\Http\Client\PendingRequest
).
Default: null
cacheKey - ?string
The cache key that should be used to save the access tokens.
If left empty, it will be generated based on the refresh URL.
Possible options:
Default: null
cacheDriver - ?string
The cache driver/store that should be used for storing the access tokens.
If left empty, the Laravel default will be used.
Possible options:
Default: null
Usage
It's really simple to use. Just add the withRefreshToken
method to your HTTP request and provide the necessary parameters. No configuration needed.
Minimal example:
$response = Http::withRefreshToken( 'https://example.com/token.oauth2', [ 'client_id', 'client_secret', ] )->get( 'https://example.com/api', );
All parameters with default values:
$response = Http::withRefreshToken( 'https://example.com/token.oauth2', [ 'client_id', 'client_secret', ], [ 'scopes' => [], 'expires' => 'expires_in', // When token should be considered expired. A string key in the response JSON for the expiration. We try to parse different formats and then remove 1 minute to be on the safe side. 'auth_type' => 'body', // 'body' or 'header' 'access_token' => 'access_token', // Key for the access token in the response JSON ], 'Bearer' )->get( 'https://example.com/api', );
For full type safety, you can also provide objects instead of arrays:
use Pelmered\LaravelHttpOAuthHelper\AccessToken; use Pelmered\LaravelHttpOAuthHelper\Credentials; use Pelmered\LaravelHttpOAuthHelper\Options; use Pelmered\LaravelHttpOAuthHelper\RefreshToken; $response = Http::withRefreshToken( 'https://example.com/token.oauth2', new Credentials( clientId: 'client_id', clientSecret: 'client_secret', ), new Options( scopes: ['scope1', 'scope2'], expires: 3600, grantType: 'password_credentials', authType: Credentials::AUTH_TYPE_BODY, tokenType: AccessToken::TOKEN_TYPE_BEARER, ), )->get( 'https://example.com/api', );
Customize with callbacks
You can also provide callbacks for expires
, auth_type
, and access_token
to customize the behavior.
$response = Http::withRefreshToken( 'https://example.com/token.oauth2', [ 'client_id', 'client_secret', ], [ 'expires' => fn($response) => $response->json()['expires_in'] - 300, // Should return the ttl in seconds that has been parsed from the response and can be manipulated as you want. 'access_token' => fn($response) => $response->access_token, // Should return the access token that has been parsed from the response. ], 'Bearer' )->get( 'https://example.com/api', );
Custom auth for refreshing token:
use Illuminate\Http\Client\PendingRequest; $response = Http::withRefreshToken( 'https://example.com/token.oauth2', [ 'client_id', 'client_secret', ], [ 'expires' => fn($response) => $response->json()['expires_in'] - 300, // Should return the ttl in seconds that has been parsed from the response and can be manipulated as you want. 'access_token' => fn($response) => $response->access_token, // Should return the access token that has been parsed from the response. 'auth_type' => 'custom', 'apply_auth_token' => fn(PendingRequest $httpClient) => $request->withHeader('Authorization', 'Bearer ' . $token), )->get( 'https://example.com/api', );
For more examples, check out the Macro tests.
Integration tips
If you use the same token in multiple places, you can create the client only once and save it. For example:
$this->client = Http::withRefreshToken( 'https://example.com/token.oauth2', [ 'client_id', 'client_secret', ], [ 'scopes' => ['read:posts', 'write:posts', 'read:comments'], ] )->baseUrl('https://example.com/api');
to use it later like this:
$this->client->get('posts'); $this->client->get('comments'); $this->client->post('posts', [ 'title' => 'My post', 'content' => 'My content', ]);
You can also resolve it in the container if you want. In your service provider:
$this->app->singleton('my-oauth-client', function ($app) { return Http::withRefreshToken( 'https://example.com/token.oauth2', [ 'client_id', 'client_secret', ], [ 'scopes' => ['read:posts', 'write:posts', 'read:comments'], ] )->baseUrl('https://example.com/api'); });
and then use it anywhere like this:
app('my-oauth-client')->get('posts');