Document an OAuth2 reauth example (#753)

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
Allen Porter 2020-12-19 16:36:03 -08:00 committed by GitHub
parent 0accb05b93
commit d6f244e610
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 93 additions and 1 deletions

View File

@ -185,3 +185,95 @@ async def async_migrate_entry(hass, config_entry: ConfigEntry):
return True return True
``` ```
## Reauthentication
Gracefully handling authentication errors such as invalid, expired, or revoked tokens is needed to advance on the [Integration Qualily Scale](integration_quality_scale_index.md). This example of how to add reauth to the OAuth flow created by `script.scaffold` following the pattern in [Building a Python library](api_lib_auth.md#oauth2).
This example catches an authentication exception in config entry setup in `__init__.py` and instructs the user to visit the integrations page in order to reconfigure the integration.
```python
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry
from homeassistant.core import HomeAssistant
from . import api
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Setup up a config entry."""
# TODO: Replace with actual API setup and exception
auth = api.AsyncConfigEntryAuth(...)
try:
await auth.refresh_tokens()
except TokenExpiredError:
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_REAUTH},
data=entry.data,
)
)
return False
# TODO: Proceed with component setup
```
The flow handler in `config_flow.py` also needs to have some additional steps to support reauth which include showing a confirmation, starting the reauth flow, updating the existing config entry, and reloading to invoke setup again.
```python
class OAuth2FlowHandler(
config_entry_oauth2_flow.AbstractOAuth2FlowHandler, domain=DOMAIN
):
"""Config flow to handle OAuth2 authentication."""
async def async_step_reauth(self, user_input=None):
"""Perform reauth upon an API authentication error."""
return await self.async_step_reauth_confirm()
async def async_step_reauth_confirm(self, user_input=None):
"""Dialog that informs the user that reauth is required."""
if user_input is None:
return self.async_show_form(
step_id="reauth_confirm",
data_schema=vol.Schema({}),
)
return await self.async_step_user()
async def async_oauth_create_entry(self, data: dict) -> dict:
"""Create an oauth config entry or update existing entry for reauth."""
# TODO: This example supports only a single config entry. Consider
# any special handling needed for multiple config entries.
existing_entry = await self.async_set_unique_id(DOMAIN)
if existing_entry:
self.hass.config_entries.async_update_entry(existing_entry, data=data)
await self.hass.config_entries.async_reload(existing_entry.entry_id)
return self.async_abort(reason="reauth_successful")
return await super().async_oauth_create_entry(data)
```
Depending on the details of the integration, there may be additional considerations such as ensuring the same account is used across reauth, or handling multiple config entries.
The reauth confirmation dialog needs additional definitions in `strings.json` for the reauth confirmation and success diaglogs:
```json
{
"config": {
"step": {
"reauth_confirm": {
"title": "[%key:common::config_flow::title::reauth%]",
# TODO: Replace with the name of the integration
"description": "The Example integration needs to re-authenticate your account"
}
},
"abort": {
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
},
}
```
See [Translations](#translations) local development instructions.
Authentication failures (such as a revoked oauth token) can be a little tricky to manually test. One suggestion is to make a copy of `config/.storage/core.config_entries` and manually change the values of `access_token`, `refresh_token`, and `expires_at` depending on the scenario you want to test. You can then walk advance through the reauth flow and confirm that the values get replaced with new valid tokens.
Automated tests should verify that the reauth flow updates the existing config entry and does not create additional entries.

View File

@ -22,7 +22,7 @@ This integration is able to cope when things go wrong. It will not print any exc
- Connection/configuration is handled via a component. - Connection/configuration is handled via a component.
- Set an appropriate `SCAN_INTERVAL` (if a polling integration) - Set an appropriate `SCAN_INTERVAL` (if a polling integration)
- Raise `PlatformNotReady` if unable to connect during platform setup (if appropriate) - Raise `PlatformNotReady` if unable to connect during platform setup (if appropriate)
- Handles expiration of auth credentials. Refresh if possible or print correct error and fail setup. If based on a config entry, should trigger a new config entry flow to re-authorize. - Handles expiration of auth credentials. Refresh if possible or print correct error and fail setup. If based on a config entry, should trigger a new config entry flow to re-authorize. ([docs](config_entries_config_flow_handler.md#reauthentication))
- Handles internet unavailable. Log a warning once when unavailable, log once when reconnected. - Handles internet unavailable. Log a warning once when unavailable, log once when reconnected.
- Handles device/service unavailable. Log a warning once when unavailable, log once when reconnected. - Handles device/service unavailable. Log a warning once when unavailable, log once when reconnected.
- Set `available` property to `False` if appropriate ([docs](core/entity.md#generic-properties)) - Set `available` property to `False` if appropriate ([docs](core/entity.md#generic-properties))