Skip to content

Commit

Permalink
Merge pull request #546 from pyinat/validate-token
Browse files Browse the repository at this point in the history
Add function to validate token
  • Loading branch information
JWCook committed Jan 29, 2024
2 parents 4013fb3 + 6740518 commit 613209c
Show file tree
Hide file tree
Showing 8 changed files with 216 additions and 177 deletions.
1 change: 1 addition & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* ⚠️ Drop support for python 3.7
* Fix `KeyError` when using `create_observation()` in dry-run mode
* Increase default request timeout from 10 to 20 seconds
* Add `validate_token()` function to manually check if an access token is valid

## 0.19.0 (2023-12-12)

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ token = get_access_token(
app_secret='my_app_secret',
)
```
See [Authentication](https://pyinaturalist.readthedocs.io/en/stable/user_guide.html#authentication)
See [Authentication](https://pyinaturalist.readthedocs.io/en/stable/authentication.md)
for more options including environment variables, keyrings, and password managers.

Now we can [create a new observation](https://pyinaturalist.readthedocs.io/en/stable/modules/pyinaturalist.v1.observations.html#pyinaturalist.v1.observations.create_observation):
Expand Down Expand Up @@ -183,7 +183,7 @@ As with observations, there is a lot of information in the response, but we'll p
## Next Steps
For more information, see:

* [User Guide](https://pyinaturalist.readthedocs.io/en/stable/user_guide.html):
* [User Guide](https://pyinaturalist.readthedocs.io/en/stable/user_guide/index.html):
introduction and general features that apply to most endpoints
* [Endpoint Summary](https://pyinaturalist.readthedocs.io/en/stable/endpoints.html):
a complete list of endpoints wrapped by pyinaturalist
Expand Down
174 changes: 3 additions & 171 deletions docs/user_guide/advanced.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Advanced Usage
This page describes some advanced features of pyinaturalist.

## Authentication
See {ref}`auth` for details on using authenticated endpoints.

## Pagination
Most endpoints support pagination, using the parameters:
* `page`: Page number to get
Expand All @@ -13,177 +16,6 @@ The default and maximum `per_page` values vary by endpoint, but it's 200 for mos
To get all pages of results and combine them into a single response, use `page='all'`.
Note that this replaces the `get_all_*()` functions from pyinaturalist\<=0.12.

(auth)=
## Authentication
For any endpoints that create, update, or delete data, you will need to authenticate using an
OAuth2 access token. This requires both your iNaturalist username and password, and separate
"application" credentials.

:::{note} Read-only requests generally don't require authentication; however, if you want to access
private data visible only to your user (for example, obscured or private coordinates),
you will need to use an access token.
:::

**Summary:**
1. Create an iNaturalist application
2. Use {py:func}`.get_access_token` with your user + application credentials to get an access token
3. Pass that access token to any API request function that uses it

### Creating an Application
:::{dropdown} Why do I need to create an application?
:icon: info

iNaturalist uses OAuth2, which provides several different methods (or "flows") to access the site.
For example, on the [login page](https://www.inaturalist.org/login), you have the option of logging
in with a username/password, or with an external provider (Google, Facebook, etc.):

```{image} ../images/inat-user-login.png
:alt: Login form
:width: 150
```

Outside of iNaturalist.org, anything else that uses the API to create or modify data is considered
an "application," even if you're just running some scripts on your own computer.

See [iNaturalist documentation](https://www.inaturalist.org/pages/api+reference#auth)
for more details on authentication.
:::

First, go to [New Application](https://www.inaturalist.org/oauth/applications/new) and fill out the
following pieces of information:

* **Name:** Any name you want to come up with. For example, if this is associated with a GitHub repo,
you can use your repo name.
* **Description:** A brief description of what you'll be using this for. For example,
*"Data access for my own observations"*.
* **Confidential:** ✔️ This should be checked.
* **URL and Redirect URI:** Just enter the URL to your GitHub repo, if you have one; otherwise any
placeholder like "<https://www.inaturalist.org>" will work.

```{image} ../images/inat-new-application.png
:alt: New Application form
:width: 300
```

You should then see a screen like this, which will show your new application ID and secret. These will
only be shown once, so save them somewhere secure, preferably in a password manager.
```{image} ../images/inat-new-application-complete.png
:alt: Completed application form
:width: 400
```

### Basic Usage
There are a few different ways you can pass your credentials to iNaturalist. First, you can pass
them as keyword arguments to {py:func}`.get_access_token`:

```python
>>> from pyinaturalist import get_access_token
>>> access_token = get_access_token(
>>> username='my_inaturalist_username', # Username you use to login to iNaturalist.org
>>> password='my_inaturalist_password', # Password you use to login to iNaturalist.org
>>> app_id='33f27dc63bdf27f4ca6cd95dd', # OAuth2 application ID
>>> app_secret='bbce628be722bfe2abde4', # OAuth2 application secret
>>> )
```

### Environment Variables

You can also provide credentials via environment variables instead of arguments. The
environment variable names are the keyword arguments in uppercase, prefixed with `INAT_`:

* `INAT_USERNAME`
* `INAT_PASSWORD`
* `INAT_APP_ID`
* `INAT_APP_SECRET`

**Examples:**

::::{tab-set}
:::{tab-item} Python
:sync: python

```python
>>> import os
>>> os.environ['INAT_USERNAME'] = 'my_inaturalist_username'
>>> os.environ['INAT_PASSWORD'] = 'my_inaturalist_password'
>>> os.environ['INAT_APP_ID'] = '33f27dc63bdf27f4ca6cd95df'
>>> os.environ['INAT_APP_SECRET'] = 'bbce628be722bfe283de4'
```
:::
:::{tab-item} Unix (MacOS / Linux)
:sync: unix

```bash
export INAT_USERNAME="my_inaturalist_username"
export INAT_PASSWORD="my_inaturalist_password"
export INAT_APP_ID="33f27dc63bdf27f4ca6cd95df"
export INAT_APP_SECRET="bbce628be722bfe283de4"
```
:::
:::{tab-item} Windows CMD
:sync: cmd

```bat
set INAT_USERNAME="my_inaturalist_username"
set INAT_PASSWORD="my_inaturalist_password"
set INAT_APP_ID="33f27dc63bdf27f4ca6cd95df"
set INAT_APP_SECRET="bbce628be722bfe283de4"
```
:::
:::{tab-item} PowerShell
:sync: ps1

```powershell
$Env:INAT_USERNAME="my_inaturalist_username"
$Env:INAT_PASSWORD="my_inaturalist_password"
$Env:INAT_APP_ID="33f27dc63bdf27f4ca6cd95df"
$Env:INAT_APP_SECRET="bbce628be722bfe283de4"
```
:::
::::

Note that in any shell, these environment variables will only be set for your current shell
session. I.e., you can't set them in one terminal and then access them in another.

### Keyring Integration
To handle your credentials more securely, you can store them in your system keyring.
You could manually store and retrieve them with a utility like
[secret-tool](https://manpages.ubuntu.com/manpages/xenial/man1/secret-tool.1.html)
and place them in environment variables as described above, but there is a much simpler option.

Direct keyring integration is provided via [python keyring](https://github.com/jaraco/keyring). Most common keyring bakcends are supported, including:

* macOS [Keychain](https://en.wikipedia.org/wiki/Keychain_%28software%29)
* Freedesktop [Secret Service](http://standards.freedesktop.org/secret-service/)
* KDE [KWallet](https://en.wikipedia.org/wiki/KWallet)
* [Windows Credential Locker](https://docs.microsoft.com/en-us/windows/uwp/security/credential-locker)

To store your credentials in the keyring, run {py:func}`.set_keyring_credentials`:
```python
>>> from pyinaturalist.auth import set_keyring_credentials
>>> set_keyring_credentials(
>>> username='my_inaturalist_username',
>>> password='my_inaturalist_password',
>>> app_id='33f27dc63bdf27f4ca6cd95df',
>>> app_secret='bbce628be722bfe283de4',
>>> )
```

Afterward, you can call {py:func}`.get_access_token` without any arguments, and your credentials
will be retrieved from the keyring. You do not need to run {py:func}`.set_keyring_credentials`
again unless you change your iNaturalist password.

### Password Manager Integration
Keyring integration can be taken a step further by managing your keyring with a password
manager. This has the advantage of keeping your credentials in one place that can be synced
across multiple machines. [KeePassXC](https://keepassxc.org/) offers this feature for
macOS and Linux systems. See this guide for setup info:
[KeepassXC and secret service, a small walk-through](https://avaldes.co/2020/01/28/secret-service-keepassxc.html).

```{figure} ../images/password_manager_keying.png
Credentials storage with keyring + KeePassXC
```

## Sessions
If you want more control over how requests are sent, you can provide your own {py:class}`.ClientSession`
object using the `session` argument for any API request function:
Expand Down
183 changes: 183 additions & 0 deletions docs/user_guide/authentication.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
(auth)=
# Authentication
For any endpoints that create, update, or delete data, you will need to authenticate using an
access token. There are two main ways to get one: manually via a browser, or programatically, using
your iNaturalist credentials plus separate application credentials. These tokens are typically valid
for 24 hours, after which you will need to get a new one.

You can then pass the access token to any API request function that uses it via the
`access_token` argument. For example:
```py
from pyinaturalist import create_observation

create_observation(
...,
access_token='my_access_token',
)
```

:::{note} Read-only requests generally don't require authentication; however, if you want to access
private data visible only to your user (for example, obscured or private coordinates), you will need
to use an access token.
:::

## Manual Authentication
You can get an access token from a browser by logging in to inaturalist.org and going to
https://www.inaturalist.org/users/api_token.

This has the advantage of not needing to create an iNaturalist application (see section below), but
has the disadvantage of requiring you to manually copy and paste the token.

## Creating an Application
:::{dropdown} Why do I need to create an application?
:icon: info

iNaturalist uses OAuth2, which provides several different methods (or "flows") to access the site.
For example, on the [login page](https://www.inaturalist.org/login), you have the option of logging
in with a username/password, or with an external provider (Google, Facebook, etc.):

```{image} ../images/inat-user-login.png
:alt: Login form
:width: 150
```

Outside of iNaturalist.org, anything else that uses the API to create or modify data is considered
an "application," even if you're just running some scripts on your own computer.

See [iNaturalist documentation](https://www.inaturalist.org/pages/api+reference#auth)
for more details on authentication.
:::

First, go to [New Application](https://www.inaturalist.org/oauth/applications/new) and fill out the
following pieces of information:

* **Name:** Any name you want to come up with. For example, if this is associated with a GitHub repo,
you can use your repo name.
* **Description:** A brief description of what you'll be using this for. For example,
*"Data access for my own observations"*.
* **Confidential:** ✔️ This should be checked.
* **URL and Redirect URI:** Just enter the URL to your GitHub repo, if you have one; otherwise any
placeholder like "<https://www.inaturalist.org>" will work.

```{image} ../images/inat-new-application.png
:alt: New Application form
:width: 300
```

You should then see a screen like this, which will show your new application ID and secret. These will
only be shown once, so save them somewhere secure, preferably in a password manager.
```{image} ../images/inat-new-application-complete.png
:alt: Completed application form
:width: 400
```

## Basic Usage
There are a few different ways you can pass your credentials to iNaturalist. First, you can pass
them as keyword arguments to {py:func}`.get_access_token`:

```python
>>> from pyinaturalist import get_access_token
>>> access_token = get_access_token(
>>> username='my_inaturalist_username', # Username you use to login to iNaturalist.org
>>> password='my_inaturalist_password', # Password you use to login to iNaturalist.org
>>> app_id='33f27dc63bdf27f4ca6cd95dd', # OAuth2 application ID
>>> app_secret='bbce628be722bfe2abde4', # OAuth2 application secret
>>> )
```

## Environment Variables
You can also provide credentials via environment variables instead of arguments. The
environment variable names are the keyword arguments in uppercase, prefixed with `INAT_`:

* `INAT_USERNAME`
* `INAT_PASSWORD`
* `INAT_APP_ID`
* `INAT_APP_SECRET`

**Examples:**

::::{tab-set}
:::{tab-item} Python
:sync: python

```python
>>> import os
>>> os.environ['INAT_USERNAME'] = 'my_inaturalist_username'
>>> os.environ['INAT_PASSWORD'] = 'my_inaturalist_password'
>>> os.environ['INAT_APP_ID'] = '33f27dc63bdf27f4ca6cd95df'
>>> os.environ['INAT_APP_SECRET'] = 'bbce628be722bfe283de4'
```
:::
:::{tab-item} Unix (MacOS / Linux)
:sync: unix

```bash
export INAT_USERNAME="my_inaturalist_username"
export INAT_PASSWORD="my_inaturalist_password"
export INAT_APP_ID="33f27dc63bdf27f4ca6cd95df"
export INAT_APP_SECRET="bbce628be722bfe283de4"
```
:::
:::{tab-item} Windows CMD
:sync: cmd

```bat
set INAT_USERNAME="my_inaturalist_username"
set INAT_PASSWORD="my_inaturalist_password"
set INAT_APP_ID="33f27dc63bdf27f4ca6cd95df"
set INAT_APP_SECRET="bbce628be722bfe283de4"
```
:::
:::{tab-item} PowerShell
:sync: ps1

```powershell
$Env:INAT_USERNAME="my_inaturalist_username"
$Env:INAT_PASSWORD="my_inaturalist_password"
$Env:INAT_APP_ID="33f27dc63bdf27f4ca6cd95df"
$Env:INAT_APP_SECRET="bbce628be722bfe283de4"
```
:::
::::

Note that in any shell, these environment variables will only be set for your current shell
session. I.e., you can't set them in one terminal and then access them in another.

## Keyring Integration
To handle your credentials more securely, you can store them in your system keyring.
You could manually store and retrieve them with a utility like
[secret-tool](https://manpages.ubuntu.com/manpages/xenial/man1/secret-tool.1.html)
and place them in environment variables as described above, but there is a much simpler option.

Direct keyring integration is provided via [python keyring](https://github.com/jaraco/keyring). Most common keyring bakcends are supported, including:

* macOS [Keychain](https://en.wikipedia.org/wiki/Keychain_%28software%29)
* Freedesktop [Secret Service](http://standards.freedesktop.org/secret-service/)
* KDE [KWallet](https://en.wikipedia.org/wiki/KWallet)
* [Windows Credential Locker](https://docs.microsoft.com/en-us/windows/uwp/security/credential-locker)

To store your credentials in the keyring, run {py:func}`.set_keyring_credentials`:
```python
>>> from pyinaturalist.auth import set_keyring_credentials
>>> set_keyring_credentials(
>>> username='my_inaturalist_username',
>>> password='my_inaturalist_password',
>>> app_id='33f27dc63bdf27f4ca6cd95df',
>>> app_secret='bbce628be722bfe283de4',
>>> )
```

Afterward, you can call {py:func}`.get_access_token` without any arguments, and your credentials
will be retrieved from the keyring. You do not need to run {py:func}`.set_keyring_credentials`
again unless you change your iNaturalist password.

### Password Manager Integration
Keyring integration can be taken a step further by managing your keyring with a password
manager. This has the advantage of keeping your credentials in one place that can be synced
across multiple machines. [KeePassXC](https://keepassxc.org/) offers this feature for
macOS and Linux systems. See this guide for setup info:
[KeepassXC and secret service, a small walk-through](https://avaldes.co/2020/01/28/secret-service-keepassxc.html).

```{figure} ../images/password_manager_keying.png
Credentials storage with keyring + KeePassXC
```

0 comments on commit 613209c

Please sign in to comment.