OAuth web request workflow
The following code defines an entity with a workflow which makes web requests, automatically refreshing with an OAuth token if the error message indicates that it should, and using backoff/retry strategies.
The blueprint assumes that a Google App requiring OAuth is set up.
This is easy done at Google, or the code should be straightforward to adapt for any other OAuth-based site.
The blueprint expects the following three values from an externalized config provider
called google-oauth
:
google_client_id
- the client ID for the Google App (created when the App is created there)google_client_secret
- the client secret for the Google App (created when the App is created there)google_refresh_token
- a refersh token acquired for the app for a logged in user; because this interaction is intended to be automated, it expects to be configured with a valid refresh token (rather than redirect a user to a webpage); this is not a token for accessing the API directly, but for acquiring a token to do so, and can be retrieved by interacting with the OAuth API ahead of time or by inspecting the traffic for a UI-based log-in
The blueprint is as follows:
name: google_oauth_example
services:
- type: org.apache.brooklyn.entity.stock.BasicEntity
brooklyn.config:
google_client_id: $brooklyn:external("google-oauth", "google_client_id")
google_client_secret: $brooklyn:external("google-oauth", "google_client_secret")
google_refresh_token: $brooklyn:external("google-oauth", "google_refresh_token")
brooklyn.initializers:
- type: workflow-effector
brooklyn.config:
name: get-userinfo
steps:
- step: http www.googleapis.com/oauth2/v2/userinfo
idempotent: yes
headers:
Authorization: Bearer ${entity.sensor.google_access_token}
on-error:
- step: goto refresh_token
condition:
regex: .*InvalidReference.*google_access_token.*.? # refresh token if there is no token
- step: goto refresh_token
condition:
target: ${status_code} # refresh token if we got a 401
equals: 401
- fail rethrow
- # any other error, just retry up to 5 times with exponential backoff,
# resetting after 1m in case the refresh token comes through several minutes later
retry limit 5 in 1m backoff 100ms increasing 2x
- log Got userinfo ${content}
- let map userinfo = ${content}
- set-sensor discovered-name = ${userinfo.name}
- set-sensor discovered-email = ${userinfo.email}
- return Completed, user confirmed as ${userinfo.name}.
# if there is an error
- id: refresh_token
step: let refresh_token = ${entity.sensor.google_refresh_token} ?? ${entity.config.google_refresh_token}
- step: http https://oauth2.googleapis.com/token
query:
client_id: ${entity.config.google_client_id}
client_secret: ${entity.config.google_client_secret}
refresh_token: ${refresh_token}
grant_type: refresh_token
method: post
idempotent: yes
on-error:
- # any error here, we just retry up to 5 times, first rapidly then waiting 1m between requests
# (could be smarter about which errors permit retry or not)
retry limit 5 backoff 100ms 1s 1m
- let map refresh_result = ${content}
- set-sensor google_access_token = ${refresh_result.access_token}
- let new_refresh_token = ${refresh_result.refresh_token} ?? ""
- step: set-sensor google_refresh_token = ${refresh_result.refresh_token}
condition:
target: ${new_refresh_token}
when: truthy
- # re-run the request
goto start