Skip to content

Commit d4a49e3

Browse files
joe-nilandhailwood
authored andcommitted
feat: Modify Auth Callback Controller to support Xero errors
- adds error param validation - throw custom OAuthException if an error is found - update README to describe error handling for Laravel 8-11
1 parent 87fb327 commit d4a49e3

File tree

3 files changed

+95
-9
lines changed

3 files changed

+95
-9
lines changed

README.md

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,30 @@ Laravel.
99
## Installation
1010

1111
You can install this package via composer using the following command:
12+
1213
```
1314
composer require webfox/laravel-xero-oauth2
1415
```
1516

1617
The package will automatically register itself.
1718

1819
You should add your Xero keys to your `.env` file using the following keys:
20+
1921
```
2022
XERO_CLIENT_ID=
2123
XERO_CLIENT_SECRET=
2224
```
2325

2426
(on [Xero developer portal](https://developer.xero.com/app/manage)): ***IMPORTANT*** When setting up the application in Xero ensure your redirect url is:
27+
2528
```
2629
https://{your-domain}/xero/auth/callback
2730
```
31+
2832
*(The flow is xero/auth/callback performs the oAuth handshake and stores your token, then redirects you over to your success callback)*
2933

3034
You can publish the configuration file with:
35+
3136
```
3237
php artisan vendor:publish --provider="Webfox\Xero\XeroServiceProvider" --tag="config"
3338
```
@@ -42,12 +47,14 @@ You can see all available scopes on [the official Xero documentation](https://de
4247
## Using the Package
4348

4449
This package registers two bindings into the service container you'll be interested in:
50+
4551
* `\XeroAPI\XeroPHP\Api\AccountingApi::class` this is the main api for Xero - see the [xeroapi/xero-php-oauth2 docs](https://github.com/XeroAPI/xero-php-oauth2/tree/master/docs) for usage.
4652
When you first resolve this dependency if the stored credentials are expired it will automatically refresh the token.
47-
* `Webfox\Xero\OauthCredentialManager` this is the credential manager - The Accounting API requires we pass through a tenant ID on each request, this class is how you'd access that.
53+
* `Webfox\Xero\OauthCredentialManager` this is the credential manager - The Accounting API requires we pass through a tenant ID on each request, this class is how you'd access that.
4854
This is also where we can get information about the authenticating user. See below for an example.
4955

5056
*app\Http\Controllers\XeroController.php*
57+
5158
```php
5259
<?php
5360

@@ -92,6 +99,7 @@ class XeroController extends Controller
9299
```
93100

94101
*resources\views\xero.blade.php*
102+
95103
```
96104
@extends('_layouts.main')
97105
@@ -118,6 +126,7 @@ class XeroController extends Controller
118126
```
119127

120128
*routes/web.php*
129+
121130
```php
122131
/*
123132
* We name this route xero.auth.success as by default the config looks for a route with this name to redirect back to
@@ -126,17 +135,58 @@ class XeroController extends Controller
126135
Route::get('/manage/xero', [\App\Http\Controllers\XeroController::class, 'index'])->name('xero.auth.success');
127136
```
128137

138+
### Error Handling
139+
140+
In the event that a user denies access on the Xero Authorisation page, the package will throw a `OAuthException` from the [AuthorizationCallbackController](src/Controllers/AuthorizationCallbackController.php). This can be caught and acted upon however you prefer.
141+
142+
#### Laravel 11
143+
144+
To do this in Laravel 11, bind a custom exception renderer in `bootstrap/app.php`:
145+
146+
```php
147+
return Application::configure(basePath: dirname(__DIR__))
148+
->withRouting(
149+
web: __DIR__ . '/../routes/web.php',
150+
commands: __DIR__ . '/../routes/console.php',
151+
health: '/up',
152+
)
153+
->withMiddleware(function (Middleware $middleware) {
154+
//
155+
})
156+
->withExceptions(function (Exceptions $exceptions) {
157+
// Handle when the user clicks cancel on the Xero authorization screen
158+
$exceptions->render(function (OAuthException $e, Request $request) {
159+
return redirect('/my/xero/connect/page')->with('errorMessage', $e->getMessage());
160+
});
161+
})->create();
162+
```
163+
164+
#### Laravel 8-10
165+
166+
Use the `reportable` method in the `App\Exceptions\Handler` class:
167+
168+
```php
169+
public function register()
170+
{
171+
$this->reportable(function (OAuthException $e) {
172+
// Handle when the user clicks cancel on the Xero authorization screen
173+
return redirect('/my/xero/connect/page')->with('errorMessage', $e->getMessage());
174+
});
175+
}
176+
```
177+
129178
## Credential Storage
179+
130180
Credentials are stored in a JSON file using the default disk on the Laravel Filesystem, with visibility set to private. This allows credential sharing across multiple servers using a shared disk such as S3, regardless of which server conducted the OAuth flow.
131181

132182
To use a different disk, change the `xero.credential_disk` config item to another disk defined in `config/filesystem.php`.
133183

134-
You can switch out the credential store (e.g. for your own `UserStore` if you wanted to store
184+
You can switch out the credential store (e.g. for your own `UserStore` if you wanted to store
135185
the credentials against your user) in one of two ways:
136186

137187
1. If it's a simple store and Laravel can automatically resolve your bindings, simply change the `xero.credential_store` config
138188
key to point to your new implementation.
139-
2. If it requires more advanced logic (e.g. using the current user to retrieve the credentials) then you can rebind this
189+
2. If it requires more advanced logic (e.g. using the current user to retrieve the credentials) then you can rebind this
140190
in your `AppServiceProvider` or a Middleware
141191
e.g.
142192

@@ -148,11 +198,12 @@ $this->app->bind(OauthCredentialManager::class, function(Application $app) {
148198
$app->make(\Webfox\Xero\Oauth2Provider::class) // Used for getting redirect url and refreshing token
149199
);
150200
});
151-
```
201+
```
152202

153203
An example UserStorageProvider [can been found here](https://github.com/webfox/laravel-xero-oauth2/issues/45#issuecomment-757552563)
154204

155205
## Using Webhooks
206+
156207
On your application in the Xero developer portal create a webhook to get your webhook key.
157208

158209
You can then add this to your `.env` file as
@@ -228,7 +279,7 @@ Once you've have an instance of \XeroAPI\XeroPHP\Api\AccountingApi::class you're
228279

229280
The XeroAPI PHP Oauth2 App repository has this list of examples of implementing calls to the API: e.g. invoice creation etc.
230281

231-
https://github.com/XeroAPI/xero-php-oauth2-app/blob/master/example.php
282+
<https://github.com/XeroAPI/xero-php-oauth2-app/blob/master/example.php>
232283

233284
## License
234285

src/Controllers/AuthorizationCallbackController.php

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
use Webfox\Xero\OauthCredentialManager;
1212
use Illuminate\Support\Facades\Redirect;
1313
use Illuminate\Foundation\Validation\ValidatesRequests;
14+
use Illuminate\Support\Str;
15+
use Webfox\Xero\Exceptions\OAuthException;
1416

1517
class AuthorizationCallbackController extends Controller
1618
{
@@ -20,19 +22,34 @@ public function __invoke(Request $request, OauthCredentialManager $oauth, Identi
2022
{
2123
try {
2224
$this->validate($request, [
23-
'code' => ['required', 'string'],
25+
'error' => ['sometimes', 'required', 'string'],
26+
'error_description' => ['required_with:error', 'string'],
27+
'code' => ['required_if:error,null', 'string'],
2428
'state' => ['required', 'string', "in:{$oauth->getState()}"]
2529
]);
2630

31+
if ($request->has('error')) {
32+
throw new OAuthException(
33+
Str::headline(
34+
sprintf(
35+
'%s: %s',
36+
$request->get('error'),
37+
$request->get('error_description')
38+
)
39+
)
40+
);
41+
}
42+
2743
$accessToken = $provider->getAccessToken('authorization_code', $request->only('code'));
2844
$identity->getConfig()->setAccessToken((string)$accessToken->getToken());
2945

3046
//Iterate tenants
3147
$tenants = array();
32-
foreach($identity->getConnections() as $c) {
48+
foreach ($identity->getConnections() as $c) {
3349
$tenants[] = [
3450
"Id" => $c->getTenantId(),
35-
"Name"=> $c->getTenantName()
51+
"Name" => $c->getTenantName(),
52+
"ConnectionId" => $c->getId(),
3653
];
3754
}
3855

@@ -55,5 +72,4 @@ public function onFailure(\throwable $e)
5572
{
5673
throw $e;
5774
}
58-
5975
}

src/Exceptions/OAuthException.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace Webfox\Xero\Exceptions;
4+
5+
use Exception;
6+
use Throwable;
7+
8+
class OAuthException extends Exception
9+
{
10+
public function __construct($message, $code = 0, Throwable $previous = null)
11+
{
12+
parent::__construct($message, $code, $previous);
13+
}
14+
15+
public function __toString()
16+
{
17+
return __CLASS__ . ": [{$this->code}]: {$this->message}\n";
18+
}
19+
}

0 commit comments

Comments
 (0)