Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

The purpose of this document is to design an oauth flow that can be re-used in any plugin that requires oauth in order to talk to the system that is being integrated with.

1. General concepts

Before using OAuth2 usually user has to create an application via service site (e.g. Twitter) and register redirect uri, than receive a client_id and client_secret. Which will be used by an application during authentication.

The further steps are shown in the diagram below:

We are implementing grant types "Authorization Code Grant" and "Refresh Token". Other types are not suitable or rarely used. Click here for some context.

Input values of the module:

NameDescription
Callback URL

A page where authCode is sent when user enter login and password. This address does not need to publicly accessible.

Auth URL

A page, where the user is directed to enter his credentials.

Example: https://www.facebook.com/dialog/oauth


Token URL

A page, where CDAP can exchange authCode for accessToken and refreshToken. Or refresh the accessToken.

Example: https://graph.facebook.com/v3.3/oauth/access_token

Client IDUser should obtain this when registering the OAuth2 application in the service (e.g. Twitter).
Client Secret

User should obtain this when registering the OAuth2 application in the service (e.g. Twitter).

Scope

This is optional.

Scope is a mechanism in OAuth 2.0 to limit an application's access to a user's account. An application can request one or more scopes, this information is then presented to the user in the consent screen, and the access token issued to the application will be limited to the scopes granted.


2. OAuth2 Implementation details

  1. Plugin developer adds this to widget:

    Button "Authenticate via OAuth2":

    Code Block
    {
      "widget-type": "oauth",
      "label": "Login",
      "name": "refreshToken",
      "widget-attributes" : {
        "client-id": {
          "type": "reference",
          "value": "clientId"
        },
        "client-secret": {
          "type": "reference",
          "value": "clientSecret"
        },
        "token-url": {
          "type": "value",
          "https://graph.facebook.com/v3.3/oauth/access_token"
        },
        "scopes": {
          "type": "value",
          "value": "ReadContacts+ReadMessage+ReadAccountInformation"
        },
        "auth-url": {
          "type": "value",
          "value": "https://www.facebook.com/dialog/oauthn"
        }
      }
    {
      "widget-type": "textbox",
      "label": "Client ID",
      "name": "clientId"
    },
    {
      "widget-type": "password",
      "label": "Client Secret",
      "name": "clientSecret"
    }
    
    
    

              Textbox:

    Code Block
       
        {
              "widget-type": "textbox",
              "label": "Refresh Token",
              "name": "refreshToken",
              "widget-attributes" : {
                "default": "${secure(myplugin_key)}"
              }
            }
    
    



  2. User clicks button "Authenticate via OAuth2". And the dialog appears which asks the user to enter:

    1. unique secure store key. (Where UI will saves refresh token)

    2. loginUrl="http://localhost:11011/oauth2_callback" this field is grayed out and cannot be changed, but user has to know loginUrl to set it in his remote application.
    Code Block
    secure_store_key=myplugin_key # user enters this key
    callback_url=http://localhost:11011/oauth2_callback # this is grayed out in window. This is constant which user will have to see and set in his application.
  3. UI opens the following URL:

    Code Block
    https://www.facebook.com/v3.3/dialog/oauth?
      response_type=code
      &client_id=gdsjglfdskgjrpestgreg
      &redirect_uri=http://localhost:11011/oauth2_callback
      &scope=readContacts+sendMessages
  4. This shows a popurl window to user:
  5. User enters his credentials and agrees to grant our applications permissions to "Read contacts and Send messages"
  6. When #5 is done client Browser automatically issues this:

    Code Block
    http://localhost:11011/oauth2_callback
     ?code=g0ZGZmNjVmOWIjNTk2NTk4ZTYyZGI3
     &state=xcoiv98y2kd22vusuye3kch
  7. UI handles the call to "/oauth2_callback" and saves the code=g0ZGZmNjVmOWIjNTk2NTk4ZTYyZGI3
  8. UI makes a request to tokenUrl with the code=g0ZGZmNjVmOWIjNTk2NTk4ZTYyZGI3 it previously received.

    No Format
    https://graph.facebook.com/v3.3/oauth/access_token?
    grant_type=authorization_code
    &code=g0ZGZmNjVmOWIjNTk2NTk4ZTYyZGI3
    &redirect_uri=http://localhost:11011/oauth2_callback
    &clientId=fgdsjglfdskgjrpestgreg
    &clientSecret=ksgh43trtgreghdfgassa
  9. UI get request response:

    Code Block
    HTTP/1.1 200 OK
    Content-Type: application/json
    Cache-Control: no-store
    Pragma: no-cache
    
    {
      "access_token":"MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3",
      "token_type":"bearer",
      "expires_in":3600,
      "refresh_token":"IwOGYzYTlmM2YxOTQ5MGE3YmNmMDFkNTVk",
      "scope":"readContacts+sendMessages"
    }
  10. Now UI saves refresh_token it got to secure store with key myplugin_key, which was passed from user. And populates the field refreshToken with value ${secure(myplugin_key)}
  11. After this at some point user starts a pipeline
  12. Plugin (backend) gets refreshToken from myplugin_key.
  13. Than backend runs the following request to retrieve a short-lived accessToken:

    Code Block
    POST /token HTTP/1.1
    Host: server.example.com
    Content-Type: application/x-www-form-urlencoded
    
    https://graph.facebook.com/v3.3/oauth/access_token?
    grant_type=refresh_token
    &refresh_token=IwOGYzYTlmM2YxOTQ5MGE3YmNmMDFkNTVk
    &client_id=s6BhdRkqt3
    &client_secret=7Fjfp0ZBr1KtDRbnfVdmIw
  14. Plugin uses acessToken it received to make requests by using the following header:

    Code Block
    Authorization: Bearer N2FmNzhhNGM2MTI5N2JhMWJlYjEdZjA0ZWM3ZTRhMTM1OGM0ODJjMzQzYjM7NTk3ZTEzNTVjZDczZTljMDk2MQ

Facebook case

All the APIs I checked: Google APIs, PayPal, WordPress, Microsoft Azure, Okta support refreshing access token. Actually this is parf of RFC. The only API which does not is Facebook. Instead they use concept they have made up called fb_exchange_token. Here's more info. Since facebook is widely spread, I suggest we should handle this separately. For facebook we should UI should put accessToken intead of refreshToken to secure store on step #11. And on step #14 backend needs a request to refresh it.

Table of Contents

Table of Contents
stylecircle

Checklist

  •  User stories documented 
  •  User stories reviewed 
  •  Design documented 
  •  Design reviewed 
  •  Feature merged 
  •  Examples and guides 
  •  Integration tests 
  •  Documentation for feature 
  •  Short video demonstrating the feature