cloudsoft.io

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