# OAuth Authentication Methods
At Airbyte, we offer two options for authentication using `OAuth2.0`:
1. **Airbyte's Own OAuth Application**: With this option, you do not need to create your own OAuth application with the data provider. This is typically applied to `Airbyte Cloud` customers as a `pick-and-use` scenario.
2. **Declarative OAuth2.0**: This option requires you to provide your own `client id` and `client secret` (parameters may vary based on the data provider's preferences). You will need to supply the configuration, which will be processed and executed by the Airbyte platform on your behalf (self-managed configuration).
## Declarative OAuth 2.0
Declarative OAuth is a powerful feature that allows connector developers to implement OAuth authentication flows without writing any code. Instead of implementing custom OAuth logic, developers can describe their OAuth flow through configuration in their connector's spec file.
**Key Benefits:**
* **Zero Code Implementation** - Define your entire OAuth flow through configuration
* **Flexible & Customizable** - Handles both standard and non-standard OAuth implementations through custom parameters, headers, and URL templates
* **Standardized & Secure** - Uses Airbyte's battle-tested OAuth implementation
* **Maintainable** - OAuth configuration lives in one place and is easy to update
**When to Use:**
* You need to implement OAuth authentication for a new connector
* Your API uses standard OR custom OAuth 2.0 flows
* You want to refactor an existing custom OAuth implementation
* You need to customize OAuth parameters, headers, or URL structures
**How it Works:**
1. Developer defines OAuth configuration in connector spec
2. Airbyte handles OAuth flow coordination:
* Generating consent URLs
* Managing state parameters
* Token exchange
* Refresh token management
* Custom parameter injection
* Header management
3. Connector receives valid tokens for API authentication
The feature is configured through the `advanced_auth.oauth_config_specification` field in your connector's spec file, with most of the OAuth-specific configuration happening in the `oauth_connector_input_specification` subfield.
### Overview
In an ideal world, implementing OAuth 2.0 authentication for each data provider would be straightforward. The main pattern involves: `Consent Screen` > `Permission/Scopes Validation` > `Access Granted`. At Airbyte, we've refined various techniques to provide the best OAuth 2.0 experience for community developers.
Previously, each connector supporting OAuth 2.0 required a custom implementation, which was difficult to maintain, involved extensive testing, and was prone to issues when introducing breaking changes.
The modern solution is the `DeclarativeOAuthFlow`, which allows customers to configure, test, and maintain OAuth 2.0 using `JSON` or `YAML` configurations instead of extensive code.
Once the configuration is set, the `DeclarativeOAuthFlow` handles the following steps:
- Formats pre-defined URLs (including variable resolution)
- Displays the `Consent Screen` for permission/scope verification (depending on the data provider)
- Completes the flow by granting the `access_token`/`refresh_token` for authenticated API calls
### Implementation Examples
Let's walk through implementing OAuth flows of increasing complexity. Each example builds on the previous one.
**Base Connector Spec**
Here is an example of a manifest for a connector that
1. Has no existing Auth
2. Connects to the Pokemon API
3. Pulls data from the moves stream
Example Base Connector Spec
```yaml
version: 6.13.0
type: DeclarativeSource
check:
type: CheckStream
stream_names:
- moves
definitions:
streams:
moves:
type: DeclarativeStream
name: moves
retriever:
type: SimpleRetriever
requester:
$ref: "#/definitions/base_requester"
path: /api/v2/move/
http_method: GET
record_selector:
type: RecordSelector
extractor:
type: DpathExtractor
field_path: []
paginator:
type: DefaultPaginator
page_token_option:
type: RequestPath
pagination_strategy:
type: CursorPagination
cursor_value: "{{ response.get('next') }}"
stop_condition: "{{ response.get('next') is none }}"
schema_loader:
type: InlineSchemaLoader
schema:
$ref: "#/schemas/moves"
base_requester:
type: HttpRequester
url_base: https://pokeapi.co
streams:
- $ref: "#/definitions/streams/moves"
spec:
type: Spec
connection_specification:
type: object
$schema: http://json-schema.org/draft-07/schema#
required: []
properties: {}
additionalProperties: true
schemas:
moves:
type: object
$schema: http://json-schema.org/schema#
additionalProperties: true
properties:
count:
type:
- number
- "null"
next:
type:
- string
- "null"
previous:
type:
- string
- "null"
results:
type:
- array
- "null"
items:
type:
- object
- "null"
properties:
name:
type:
- string
- "null"
url:
type:
- string
- "null"
```
Example Response
```json
{
"access_token": "YOUR_ACCESS_TOKEN_123"
}
```
Example Declarative OAuth Spec
```diff
--- manifest.yml
+++ simple_oauth_manifest.yml
definitions:
base_requester:
type: HttpRequester
url_base: https://pokeapi.co
+ authenticator:
+ type: OAuthAuthenticator
+ refresh_request_body: {}
+ client_id: "{{ config[\"client_id\"] }}"
+ client_secret: "{{ config[\"client_secret\"] }}"
+ access_token_value: "{{ config[\"client_access_token\"] }}"
spec:
connection_specification:
type: object
$schema: http://json-schema.org/draft-07/schema#
- required: []
- properties: {}
+ required:
+ - client_id
+ - client_secret
+ - client_access_token
+ properties:
+ client_id:
+ type: string
+ client_secret:
+ type: string
+ client_access_token:
+ type: string
+ airbyte_secret: true
additionalProperties: true
+ advanced_auth:
+ auth_flow_type: oauth2.0
+ oauth_config_specification:
+ oauth_connector_input_specification:
+ consent_url: >-
+ https://yourconnectorservice.com/oauth/consent?client_id={{client_id_value}}&redirect_uri={{
+ redirect_uri_value}}&state={{state}}
+ access_token_url: >-
+ https://yourconnectorservice.com/oauth/token?client_id={{client_id_value}}&client_secret={{client_secret_value}}&code {{auth_code_value}}
+ complete_oauth_output_specification:
+ required:
+ - access_token
+ properties:
+ access_token:
+ type: string
+ path_in_connector_config:
+ - access_token
+ path_in_oauth_response:
+ - access_token
+ complete_oauth_server_input_specification:
+ required:
+ - client_id
+ - client_secret
+ properties:
+ client_id:
+ type: string
+ client_secret:
+ type: string
+ complete_oauth_server_output_specification:
+ required:
+ - client_id
+ - client_secret
+ properties:
+ client_id:
+ type: string
+ path_in_connector_config:
+ - client_id
+ client_secret:
+ type: string
+ path_in_connector_config:
+ - client_secret
```
Example Declarative OAuth Change
```diff
--- simple_oauth_manifest.yml
+++ secret_header_manifest.yml
spec:
https://yourconnectorservice.com/oauth/consent?client_id={{client_id_value}}&redirect_uri={{
redirect_uri_value }}&state={{ state }}
access_token_url: >-
- https://yourconnectorservice.com/oauth/token?client_id={{client_id_value}}&client_secret={{client_secret_value}}&code={{auth_code_value}}
+ https://yourconnectorservice.com/oauth/token?client_id={{client_id_value}}&code={{auth_code_value}}
+ access_token_headers:
+ SECRETHEADER: "{{ client_secret_value }}"
complete_oauth_output_specification:
required:
```
Example with the header value encoded into `base64-string`
```diff
--- secret_header_manifest.yml
+++ secret_header_manifest.yml
spec:
https://yourconnectorservice.com/oauth/consent?client_id={{client_id_value}}&redirect_uri={{
redirect_uri_value }}&state={{ state }}
access_token_url: >-
- https://yourconnectorservice.com/oauth/token?client_id={{client_id_value}}&client_secret={{client_secret_value}}&code={{auth_code_value}}
+ https://yourconnectorservice.com/oauth/token?client_id={{client_id_value}}&code={{auth_code_value}}
+ access_token_headers:
- SECRETHEADER: "{{ client_secret_value }}"
+ SECRETHEADER: "{{ (client_id_value ~ ':' ~ client_secret_value) | b64encode }}"
complete_oauth_output_specification:
required:
```
Example Declarative OAuth Change
```diff
--- simple_oauth_manifest.yml
+++ secret_header_manifest.yml
spec:
https://yourconnectorservice.com/oauth/consent?client_id={{client_id_value}}&redirect_uri={{
redirect_uri_value }}&state={{ state }}
access_token_url: >-
- https://yourconnectorservice.com/oauth/token?client_id={{client_id_value}}&client_secret={{client_secret_value}}&code={{auth_code_value}}
+ https://yourconnectorservice.com/oauth/token
+ access_token_params:
+ client_id: "{{ client_id_value }}"
+ client_secret: "{{ client_secret_value }}"
+ redirect_uri: "{{ redirect_uri_value }}"
complete_oauth_output_specification:
required:
```
Example rendered `access_token_params`
```json
{
"client_id": "YOUR_CLIENT_ID_123",
"client_secret": "YOUR_CLIENT_SECRET_123",
"redirect_uri": "https://cloud.airbyte.com",
}
```
Example response
```json
{
"data": {
"super_duper_access_token": "YOUR_ACCESS_TOKEN_123"
}
}
```
Example Declarative OAuth Change
```diff
--- base_oauth.yml
+++ different_access_token_field.yml
spec:
https://yourconnectorservice.com/oauth/token?client_id={{client_id_value}}&client_secret={{client_secret_value}}&code={{auth_code_value}}
complete_oauth_output_specification:
required:
- - access_token
+ - super_duper_access_token
properties:
- access_token:
+ super_duper_access_token:
type: string
path_in_connector_config:
- access_token
+ path_in_oauth_response:
+ - data
+ - super_duper_access_token
complete_oauth_server_input_specification:
```
Example URL
`https://yourconnectorservice.com/oauth/refresh/endpoint`
Example response
```json
{
"access_token": "YOUR_ACCESS_TOKEN_123",
"refresh_token": "YOUR_REFRESH_TOKEN_123",
"expires_in": 7200,
}
```
Example Declarative OAuth Change
```diff
--- base_oauth.yml
+++ refresh_token.yml
definitions:
authenticator:
type: OAuthAuthenticator
refresh_request_body: {}
+ grant_type: refresh_token
client_id: "{{ config[\"client_id\"] }}"
client_secret: "{{ config[\"client_secret\"] }}"
+ refresh_token: "{{ config[\"client_refresh_token\"] }}"
access_token_value: "{{ config[\"client_access_token\"] }}"
+ access_token_name: access_token
+ refresh_token_updater:
+ refresh_token_name: refresh_token
+ refresh_token_config_path:
+ - client_refresh_token
+ token_refresh_endpoint: >-
+ https://yourconnectorservice.com/oauth/refresh/endpoint
streams:
- $ref: "#/definitions/streams/moves"
spec:
required:
- client_id
- client_secret
- - client_access_token
+ - client_refresh_token
properties:
client_id:
type: string
@@ -68,9 +77,9 @@ spec:
- client_access_token:
+ client_refresh_token:
type: string
- title: Access token
+ title: Refresh token
@@ -86,16 +95,22 @@ spec:
https://yourconnectorservice.com/oauth/token?client_id={{client_id_value}}&client_secret={{client_secret_value}}&code={{auth_code_value}}
complete_oauth_output_specification:
required:
- access_token
+ - refresh_token
+ - token_expiry_date
properties:
access_token:
type: string
path_in_connector_config:
- access_token
+ path_in_oauth_response:
+ - access_token
+ refresh_token:
+ type: string
+ path_in_connector_config:
+ - refresh_token
+ path_in_oauth_response:
+ - refresh_token
+ token_expiry_date:
+ type: string
+ path_in_connector_config:
+ - token_expiry_date
+ path_in_oauth_response:
+ - expires_in
complete_oauth_server_input_specification:
required:
- client_id
```
Example Declarative OAuth Change
```diff
--- base_oauth.yml
+++ scopes.yml
@@ -80,10 +80,10 @@ spec:
oauth_config_specification:
oauth_connector_input_specification:
consent_url: >-
- https://yourconnectorservice.com/oauth/consent?client_id={{client_id_value}}&redirect_uri={{
- redirect_uri_value }}&state={{ state_value }}
+ https://yourconnectorservice.com/oauth/consent?client_id={{client_id_value}}&redirect_uri={{ redirect_uri_value }}&state={{ state_value }}&scope={{scope_value}}
access_token_url: >-
https://yourconnectorservice.com/oauth/token?client_id={{client_id_value}}&client_secret={{client_secret_value}}&code={{auth_code_value}}
+ scope: my_scope_A:read,my_scope_B:read
complete_oauth_output_specification:
required:
- access_token
```
Example URL
`https://yourconnectorservice.com/oauth/consent?client_id=YOUR_CLIENT_ID_123&redirect_uri=https://cloud.airbyte.com&state=2LtdNpN8pmkYOBDqoVR3NzYQ`
Example Declarative OAuth Change
```diff
--- base_oauth.yml
+++ base_oauth_with_custom_state.yml
@@ -80,10 +80,10 @@ spec:
oauth_config_specification:
oauth_connector_input_specification:
consent_url: >-
https://yourconnectorservice.com/oauth/consent?client_id={{client_id_value}}&redirect_uri={{
redirect_uri_value }}&state={{ state_value }}
access_token_url: >-
https://yourconnectorservice.com/oauth/token?client_id={{client_id_value}}&client_secret={{client_secret_value}}&code={{auth_code_value}}
+ state: {
+ min: 10,
+ max: 27
+ }
complete_oauth_output_specification:
required:
- access_token
```
Example URL
`https://yourconnectorservice.com/oauth/consent?client_id=YOUR_CLIENT_ID_123&redirect_uri=https://cloud.airbyte.com&state=some_random_state_string&scope=my_scope_A%3Aread%20my_scope_B%3Aread`
Example Declarative OAuth Change
```diff
--- scopes.yml
+++ scopes.yml
@@ -80,10 +80,10 @@ spec:
oauth_config_specification:
oauth_connector_input_specification:
consent_url: >-
- https://yourconnectorservice.com/oauth/consent?client_id={{client_id_value}}&redirect_uri={{
- redirect_uri_value }}&state={{ state_value }}&scope={{scope_value}}
+ https://yourconnectorservice.com/oauth/consent?client_id={{client_id_value}}&redirect_uri={{ redirect_uri_value }}&state={{ state_value }}&scope={{ scope_value | urlencode }}
access_token_url: >-
https://yourconnectorservice.com/oauth/token?client_id={{client_id_value}}&client_secret={{client_secret_value}}&code={{auth_code_value}}
- scope: my_scope_A:read,my_scope_B:read
+ scope: my_scope_A:read my_scope_B:read
complete_oauth_output_specification:
required:
- access_token
```
Example Declarative OAuth Change
```diff
--- base_oauth.yml
+++ base_oauth.yml
spec:
oauth_config_specification:
oauth_connector_input_specification:
consent_url: >-
- https://yourconnectorservice.com/oauth/consent?client_id={{client_id_value}}&redirect_uri={{
- redirect_uri_value }}&state={{ state_value }}
+ https://yourconnectorservice.com/oauth/consent?client_id={{client_id_value}}&redirect_uri={{
+ redirect_uri_value }}&state={{ state_value }}&code_challenge={{ state_value | codechallengeS256 }}
access_token_url: >-
https://yourconnectorservice.com/oauth/token?client_id={{client_id_value}}&client_secret={{client_secret_value}}&code={{auth_code_value}}
complete_oauth_output_specification:
required:
- access_token
```
Example Declarative OAuth Change
```diff
--- base_oauth.yml
+++ legacy_to_declarative_oauth.yml
definitions:
authenticator:
type: OAuthAuthenticator
refresh_request_body: {}
- client_id: '{{ config["client_id"] }}'
- client_secret: '{{ config["client_secret"] }}'
- access_token_value: '{{ config["client_access_token"] }}'
+ client_id: '{{ config["super_secret_credentials"]["pokemon_client_id"] }}'
+ client_secret: '{{ config["super_secret_credentials"]["pokemon_client_secret"] }}'
+ access_token_value: '{{ config["super_secret_credentials"]["pokemon_access_token"] }}'
streams:
- $ref: "#/definitions/streams/moves"
spec:
type: object
$schema: http://json-schema.org/draft-07/schema#
required:
- - client_id
- - client_secret
- - client_access_token
+ - super_secret_credentials
properties:
- client_id:
- type: string
- title: Client ID
- airbyte_secret: true
- order: 0
- client_secret:
- type: string
- title: Client secret
- airbyte_secret: true
- order: 1
- client_access_token:
- type: string
- title: Access token
- airbyte_secret: true
- airbyte_hidden: false
- order: 2
+ super_secret_credentials:
+ required:
+ - pokemon_client_id
+ - pokemon_client_secret
+ - pokemon_access_token
+ pokemon_client_id:
+ type: string
+ title: Client ID
+ airbyte_secret: true
+ order: 0
+ pokemon_client_secret:
+ type: string
+ title: Client secret
+ airbyte_secret: true
+ order: 1
+ pokemon_access_token:
+ type: string
+ title: Access token
+ airbyte_secret: true
+ airbyte_hidden: false
+ order: 2
additionalProperties: true
advanced_auth:
auth_flow_type: oauth2.0
oauth_config_specification:
oauth_connector_input_specification:
consent_url: >-
- https://yourconnectorservice.com/oauth/consent?client_id={{client_id_value}}&redirect_uri={{
- redirect_uri_value }}&state={{ state_value }}
+ https://yourconnectorservice.com/oauth/consent?client_id={{client_id_value}}&redirect_uri={{ redirect_uri_value }}&state={{ s
tate_value }}
access_token_url: >-
https://yourconnectorservice.com/oauth/token?client_id={{client_id_value}}&client_secret={{client_secret_value}}&code={{auth_
code_value}}
+ client_id_key: pokemon_client_id
+ client_secret_key: pokemon_client_secret
complete_oauth_output_specification:
required:
- - access_token
+ - pokemon_access_token
properties:
access_token:
type: string
path_in_connector_config:
- - access_token
+ - super_secret_credentials
+ - pokemon_access_token
path_in_oauth_response:
- access_token
complete_oauth_server_input_specification:
required:
- - client_id
- - client_secret
+ - pokemon_client_id
+ - pokemon_client_secret
properties:
- client_id:
+ pokemon_client_id:
type: string
- client_secret:
+ pokemon_client_secret:
type: string
complete_oauth_server_output_specification:
required:
- - client_id
- - client_secret
+ - pokemon_client_id
+ - pokemon_client_secret
properties:
- client_id:
+ pokemon_client_id:
type: string
path_in_connector_config:
- - client_id
- client_secret:
+ - super_secret_credentials
+ - pokemon_client_id
+ pokemon_client_secret:
type: string
path_in_connector_config:
- - client_secret
+ - super_secret_credentials
+ - pokemon_client_secret
```