- Created by Andrew Onischuk, last modified by Albert Shau on Jun 14, 2019
You are viewing an old version of this page. View the current version.
Compare with Current View Page History
« Previous Version 7 Next »
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:
Name | Description |
---|---|
Callback URL | CDAP will start a local server on the given url (let's say http://localhost:27435). Only localhost urls are allowed. For more info click here. This is an URL where service callbacks with authCode after user enters username and password and agrees to grant the permissions. This URL is also usually configured when registering the OAuth2 application in the service (e.g. Twitter). If the URL registered there is not equal to the one we send, OAuth2 will get denied. |
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. |
Client ID | User 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. |
Note: OAuth2 implementation will reside as a separate project, since this will need to be re-used by different plugins.
2. UI Changes Required. Need to expose an authentication pop-up window to a user
Plugin using auth2 will need to have one field. Refresh Token, which will save results of oauth2 along with plugin:
Name | Description | Default | Widget | Validations |
---|---|---|---|---|
Refresh Token | This is populated by the button "Login via OAuth 2.0". Since we save Refresh Token (not an access token which is short lived), this should be done only once, during initial pipeline deployment. For more information click here. UI should put an actual value into secure store and put macro function ${secure(key)} a value for extra safety. | ${secure(unique_key)} | Fail is empty and OAuth2 is enabled. |
We need to show authentication dialog from the service where user enters his username and passwords, as well as agrees to grant a certain access to our application.
This will require change to UI. We can implement this as plugin-function:
"plugin-function": { "method": "POST", "widget": "showPageToUser", "output-property": "void", "plugin-method": "showPageToUser" }
Here's how the flow can possibly work:
Once the user clicks the button, UI runs showPageToUser to get url and headers from plugin using post-function. Example of return from plugin:
url = https://www.facebook.com/v3.3/dialog/oauth? client_id=3MVG96_7YM2sI9wT6c13RTPp6RDeRBPFc0F5sYfIrKBZPdTK2Yr7jiTwq8u3ykXyBHtlf3lnNMWSN1rqfjA_y &redirect_uri=http://localhost:27435 headers = {} # empty
On the side note: The url syntax is established by an RFC: https://tools.ietf.org/html/rfc6749#section-4.1.1
IMPORTANT: During this call plugin will also start a callback server (for more information click here)
- UI shows the page.
- Once the page is closed, ui does another call to plugin.
During this call plugin waits for a callback to be complete. Than exchanges received authCode to pair accessToken, refreshToken and return it back to UI. - UI populates the widget field refreshToken with the value.
refreshToken usually has a permanent lifetime (unlike accessToken), unless invalidated manually by user. With this token during every pipeline run we can ask for an accessToken from tokenUrl. No need for callback server or UI/user involvement at this point.
If OAuth2 is enabled and refreshToken field is not populated. Pipeline deployment will fail. So effectively user will be asked to authenticate once and the information for further authentication will be saved in widget.
3. Running a localhost callback HTTP server from CDAP
We will need to start an HTTP callback server on localhost. Let's say http://localhost:27435. The port should be statically configured via widget, we cannot get a random ephemeral port, since callback_url needs to be constant. It is saved on service provider (e.g. Facebook) as a static configuration of OAuth2 application.
After user enters his credentials on authentication page (of let's say Facebook), browser will redirect the response authCode to this server. Since this request is done by client browser (not remote server), this address is not required to be public IP address and we can safely use localhost.
4. Refreshing access tokens
Since we save refresh token. instead of access token token (which are short-lived). During every pipeline we will need to get an access token. This is a very simple process. We have to execute only one request. Which will return an access token.
POST /token HTTP/1.1 Host: server.example.com Content-Type: application/x-www-form-urlencoded grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA &client_id=s6BhdRkqt3&client_secret=7Fjfp0ZBr1KtDRbnfVdmIw
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 just add ugly check "if url contains facebook.com" (or talking in fancy Java terms create a factory class, which creates oauth2 handlers depending on url provided) than save long-lived-access token instead of refreshToken and do a refresh the way facebook wants. The factory can than be used to implement behavior for other services with non-default oauth2 implementations.
Table of Contents
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
- No labels