Skip to content

Commit

Permalink
Light review and cleanup of PKCE OAuth2 flow docs.
Browse files Browse the repository at this point in the history
  • Loading branch information
freakboy3742 committed Feb 11, 2023
1 parent 0499a96 commit 5ef2cae
Showing 1 changed file with 66 additions and 65 deletions.
131 changes: 66 additions & 65 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pip install pyxero
### Using OAuth2 Credentials

OAuth2 is an open standard authorization protocol that allows users to
provide specific permissions to apps that want to use their account. OAuth2
provide specific permissions to apps that want to use their account. OAuth2
authentication is performed using *tokens* that are obtained using an API;
these tokens are then provided with each subsequent request.

Expand Down Expand Up @@ -188,40 +188,38 @@ def some_view_which_calls_xero(request):

### Using PKCE Credentials

PKCE is an alternative flow for providing authentication via OAuth2,
it works largely the same as the standard OAuth2 mechanism, but unlike
the normal flow is designed to work with applications which cannot
keep private keys secure, such as desktop, mobile or single page apps where
such secrets could be extracted from the binary. (Or even the source, if it
is available). A client ID however is still required.
PKCE is an alternative flow for providing authentication via OAuth2. It works
largely the same as the standard OAuth2 mechanism, but unlike the normal flow is
designed to work with applications which cannot keep private keys secure, such
as desktop, mobile or single page apps where such secrets could be extracted. A
client ID is still required.

As elsewhere, OAuth2 tokens have a 30 minute expiry, but can be only swapped for
a new token if the `offline_access` scope is requested.

As elsewher OAuth2 tokens have a 30 minute expiry, But can be only swapped for a new
token, if a the `offline_access` scope is requested.

Xero documentation on the PKCE flow can be found at

[here](https://developer.xero.com/documentation/guides/oauth2/pkce-flow)
.
The procedure
for creating and authenticating credentials is as follows *(with a CLI
Xero documentation on the PKCE flow can be found
[here](https://developer.xero.com/documentation/guides/oauth2/pkce-flow). The
procedure for creating and authenticating credentials is as follows *(with a CLI
example at the end)*:

1) [Register your app](https://developer.xero.com/myapps) with Xero, using a
redirect URI which will be served by your app in order to complete the
authorisation e.g. `http://localhost:<port>/callback/`.
You can chose any port, anc can pass it to the credentials object on construction,
allow with the the Client Id you are provded with.
authorisation e.g. `http://localhost:<port>/callback/`. You can chose any
port, anc can pass it to the credentials object on construction, allow with
the the Client Id you are provded with.

2) Construct an `OAuth2Credentials` instance using the details from the first
step.

```python
>>> from xero.auth import OAuth2Credentials
>>>
>>> credentials = OAuth2PKCECredentials(client_id, port=my_port)
```
If neccessary pass in a list of scopes to define the scopes required by your
app. E.g. if write access is required to transactions and payroll employees:

If neccessary, pass in a list of scopes to define the scopes required by
your app. E.g. if write access is required to transactions and payroll
employees:

```python
>>> from xero.constants import XeroScopes
Expand All @@ -231,10 +229,12 @@ example at the end)*:
>>> credentials = OAuth2Credentials(client_id, scope=my_scope
>>> port=my_port)
```

The default scopes are `['offline_access', 'accounting.transactions.read',
'accounting.contacts.read']`. `offline_access` is required in order for
tokens to be refreshable. For more details on scopes see Xero's
[documentation]( https://developer.xero.com/documentation/oauth2/scopes).
tokens to be refreshable. For more details on scopes see [Xero's
documentation on oAuth2
scopes](https://developer.xero.com/documentation/oauth2/scopes).

3) Call `credentials.logon()` . This will open a browser window, an visit
a Xero authentication page.
Expand All @@ -243,25 +243,23 @@ example at the end)*:
>>> credentials.logon()
```

As well as opening that browser window the Authenticator will start
up a local webserver on the provided port, to collect the tokens
Xero returns.
The Authenticator will also start a local webserver on the provided port.
This webserver will be used to collect the tokens that Xero returns.

The default PCKEAuthReceiver class has no reponse
pages defined so the browser will show an error, on empty page
for all transactions. But the application is now authorised and
will continue.
If you wish you can override the `send_access_ok()` method, and the
`send_error_page()` method to create a more userfriendly experience.
The default `PCKEAuthReceiver` class has no reponse pages defined so the
browser will show an error, on empty page for all transactions. But the
application is now authorised and will continue. If you wish you can
override the `send_access_ok()` method, and the `send_error_page()` method
to create a more userfriendly experience.

In either case once the callback url has been visited the local
server will shutdown.

In either case once the callback url has been visited the local server will
shutdown.

4) You can now continue as per the normal OAuth2 flow. Now the credentials may
be used to authorize a Xero session. As OAuth2 allows authentication for
multiple Xero Organisations, it is necessary to set the tenant_id against
which the xero client's queries will run.

4) You can now continue as per the normal OAuth2 flow.
Now the credentials may be used to authorize a Xero session. As OAuth2
allows authentication for multiple Xero Organisations, it is necessary to
set the tenant_id against which the xero client's queries will run.
```python
>>> from xero import Xero
>>> # Use the first xero organisation (tenant) permitted
Expand All @@ -271,12 +269,13 @@ example at the end)*:
>>> ...
```
If the scopes supplied in Step 2 did not require access to organisations
(e.g. when only requesting scopes for single sign) it will not be
possible to make requests with the Xero API and `set_default_tenant()` will
raise an exception.
(e.g. when only requesting scopes for single sign) it will not be possible
to make requests with the Xero API and `set_default_tenant()` will raise an
exception.

To pick from multiple possible Xero organisations the `tenant_id` may be set
explicitly:

```python
>>> tenants = credentials.get_tenants()
>>> credentials.tenant_id = tenants[1]['tenantId']
Expand All @@ -287,6 +286,7 @@ example at the end)*:
5) When using the API over an extended period, you will need to exchange tokens
when they expire. If a refresh token is available, it can be used to
generate a new token:

```python
>>> if credentials.expired():
>>> credentials.refresh()
Expand All @@ -299,37 +299,39 @@ example at the end)*:
persist the new state.

```
#### CLI OAuth2 App Example
This example shows authorisation, automatic token refreshing and API use in
a Django app which has read/write access to contacts and transactions.

#### CLI OAuth2 App Example

This example shows authorisation, automatic token refreshing and API use in
a Django app which has read/write access to contacts and transactions.

Each time this app starts it asks for authentication, but you
could consider using the user `keyring` to store tokens.

```python
from xero import Xero
from xero.auth import OAuth2PKCECredentials
from xero.constants import XeroScopes

# Get client_id, client_secret from config file or settings then
credentials = OAuth2PKCECredentials(
client_id, port=8080,
scope=[XeroScopes.OFFLINE_ACCESS, XeroScopes.ACCOUNTING_CONTACTS,
XeroScopes.ACCOUNTING_TRANSACTIONS]
)
credentials.logon()
credentials.set_default_tenant()
```python
from xero import Xero
from xero.auth import OAuth2PKCECredentials
from xero.constants import XeroScopes

for contacts in xero.contacts.all()
print contact["Name"]
```
# Get client_id, client_secret from config file or settings then
credentials = OAuth2PKCECredentials(
client_id, port=8080,
scope=[XeroScopes.OFFLINE_ACCESS, XeroScopes.ACCOUNTING_CONTACTS,
XeroScopes.ACCOUNTING_TRANSACTIONS]
)
credentials.logon()
credentials.set_default_tenant()

for contacts in xero.contacts.all()
print contact["Name"]
```

### Older authentication methods ###

In the past, Xero had the concept of "Public", "Private", and "Partner"
In the past, Xero had the concept of "Public", "Private", and "Partner"
applications, which each had their own authentication procedures. However,
they removed access for Public applications on 31 March 2021; Private
applications were removed on 30 September 2021. Partner applications
they removed access for Public applications on 31 March 2021; Private
applications were removed on 30 September 2021. Partner applications
still exist, but the only supported authentication method is OAuth2; these
are now referred to as "OAuth2 apps". As Xero no longer supports these older
authentication methods, neither does PyXero.
Expand Down Expand Up @@ -584,4 +586,3 @@ New features or bug fixes can be submitted via a pull request. If you want
your pull request to be merged quickly, make sure you either include
regression test(s) for the behavior you are adding/fixing, or provide a
good explanation of why a regression test isn't possible.

0 comments on commit 5ef2cae

Please sign in to comment.