OAuth 2.0

Introduction

This article provides detailed information about how Appian can connect to systems that use the OAuth 2.0 Authorization Code grant. Designers can configure these integrations to using a connected system.

Token Request Sequence

The OAuth 2.0 framework is defined by the ITEF RFC 6749 standard.

Appian supports the authorization code and client credentials grant types.

This standard lays out the sequence of steps involved with the Authorization Code grant. There are four main roles in this sequence:

  • Client - The system initiating the resource request. In this case, Appian.
  • Resource Owner - The user requesting the resource (e.g. third party data, files, etc).
  • Authorization Server - The server that is ensuring the user can and wants to permit resources to be shared.
  • Resource Server - The server that holds the desired resources.

The following sequence diagram describes the steps involved in a successful authorization, with a resource returned to the client.

images:oauth_sequence_diagram.png

The first part of the authorization process involved the client initiating a request to the authorization server on behalf of the user. When the authorization server receives this, it will ask the user if they grant permission for this request.

This is probably the most recognized step in the Authorization Code sequence as granting permission is the only step that involves user input. Most people are familiar with an alert from their phones or a browser asking to allow or deny access to an application.

The client will get the authorization code and send out a token request. The authorization server will send back an access token. This access token will allow the client to request whatever resources were approved by the user. If the system supports refresh tokens, it will generally send that back during this step as well.

Other Important Sequences

Once someone has an access token, future request will be granted until, (1) the user revokes permission, or (2) the access token has expired (and there is no refresh token). As long as a user has a valid access token, the resource server will be able to provide users with the specified resources.

If an access token is revoked or expired, and no refresh token is available, a user will have to reauthorize again before being able to access the specific resource. If a refresh token is provided to the client when the access token is sent, the resource sever can re-query the authorization server to grant a new access token.

Most of the steps in these sequences are not visible to the user and designers. However, understanding these sequences will help designers troubleshoot problems if they occur.

Important Design Considerations

There are several important design considerations when using OAuth 2.0.

It is very important to review and understand the requirements in the third-party system for a successful OAuth connection.

Registration in the Third-Party System

In order for Appian to successfully connect to the desired resources, you will have to register the connection in the third-party system. This is typically done under a third party system's Developer UI. The terminology varies, but registration usually requires creating an application or project in that system.

When registering an app or project in a third-party system a few things need to be considered:

  • Access to the third-party application or project: Because the OAuth 2.0 application or project in the third-party system will contain a client secret, essentially the password for the OAuth connection, an appropriate user should be selected to register the application or project.
  • One registration per environment: For every instance of Appian, like dev, test, and production, there should be a single registration. Some third-party systems prevent multiple environments using the same application registrations by default.
  • Determine where scope should be defined: A registered application's scope may need to be configured in Appian or in the registered application. Be sure to refer the other system's documentation to know where to define scope.

Setting the Properties

A successful connection using the Authorization Code grant requires that configuration parameters are both set in an Appian connected system and the third-party-system.

Parameters Set in the Other System

A callback url, also known as a redirect url, needs to be provided to the third-party system. The connected system will provide this url to paste into the registered application or project in the other system.

Field Description
Callback URI Required (in the other system). This URI provided within the instructions. Follows the format: http://<domain>/suite/oauth/callback. This value should be entered into the third-party system's callback url field.

Parameters Set in the Connected System

The following parameters from the third-party system will need to be entered into the connected system. Refer to the third-party's documentation for more information.

Field Description
Authorization Endpoint Required. The third-party system's endpoint that will present the user with an authorization screen. This value can typically be found in the third-party's documentation.
Client ID Required. ID provided by the third-party system during the registration process.
Client Secret Required. The password provided by the third-party system during the registration process. This field is masked to prevent unauthorized users from seeing and should be treated as a password.
Scope Optional. Defines what resources need to be accessed from the resource server. Depending on what system Appian is connecting to, scope may be set from the connected system or within the third-party system directly. Multiple scope values are typically space-delimited.
Token Request Endpoint Required. The endpoint that provided the access token for the specified resources. This value can typically be found in the third-party's documentation.

Providing Users a Way to Authorize

Because access tokens are per user, Designers need to consider how users will grant access to a protected resource.

A user needs to grant Appian access to the other system so that Appian can obtain an access token. To achieve this, Designers must use an a!authorizationLink() on the relevant interface.

How the link is presented to the user is up to the Designer, however, a few basic principles should apply:

  • The authorization link is only needed for a failed attempt due to system not having a valid access token for the user.
    • This state occurs when: the user first accesses the protected system, the user revokes access, or if their access token expires.
  • The authorization link does not need to be visible if the integration's request was successful.
  • It is the integration that is returning the connected system value to generate the authorization link.

For integrations that query data from another system, dot notation is used to get the connected system. The interface example below shows this in the authorization link using local!integrationResult.connectedSystem to generate the appropriate authorization link.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
load(
  local!integrationResult: rule!EXAMPLE_OAuthIntegration(),
  {
    a!sectionLayout(
      contents:{
      /* Interface components would go here to show data from integration results */
      },
      showWhen: local!integrationResult.success
    ),
    a!boxLayout(
      label: "Some information cannot be displayed until you authorize with LinkedIn",
!      showWhen: contains({401, 403}, local!integrationResult.statusCode),
      style: "WARN",
      contents: {
        a!columnsLayout(
          columns:{
            a!columnLayout(
              contents:{
                a!richTextDisplayField(
                  value: {
                    a!richTextItem(
                      text: "To view this information, go to "
                    ),
                    a!richTextItem(
                      text: "LinkedIn",
 !                    link: a!authorizationLink(
 !                      label: "Authorize",
 !                      connectedSystem: local!integrationResult.connectedSystem
                      )
                    ),
                    a!richTextItem(
                      text: " to allow access. When done, click refresh to reload the page."
                    )
                  }
                )
              }
            ),
            a!columnLayout(
              a!buttonLayout(
                primaryButtons:{
                  a!buttonWidget(
                    label:"Reload",
                    saveInto:{
                      a!save(local!integrationResult, rule!EXAMPLE_OAuthIntegration())
                    }
                  )
                }
              )
            )
          }
        )
      }
    )
  }
)

This example calls the integration first and then determines whether the desired UI is displayed or further action is required before showing a user data from another system.

images:auth_link_read.png

It's important to note that write-based errors that warrant authorization links have a much lower likelihood of occurring. Because interfaces that write data to another system typically query data as well, the initial query-based integration call would pick up the authorization issue first. However, it's still useful to create a write-based authorization link to ensure the data submitted by the user makes it to the other system.

For integrations that write data, error handling is best handled in process. Instead of writing calling the integration on a form submission, use the call integration smart service and pass in form data. Store the connected system result into a process variable and handle errors in a gateway.

images:oauth_write_process_model.png

An interface expression, like the example below, would then be used to let the user authorize properly before submission.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
load(
  local!verified,
  a!formLayout(
    label: "Share URL on LinkedIn",
    contents: {
      a!sectionLayout(
        contents: {
          a!boxLayout(
            label: "Problem with update. You need to authorize access with LinkedIn.",
            style: "ERROR",
            showWhen: isnull(local!verified),
            contents: {
              a!richTextDisplayField(
                value: {
                  a!richTextItem(
                    text: "Before you can save this information, you must sign in to "
                  ),
                  a!richTextItem(
                    text: "LinkedIn",
                    link: a!authorizationLink(
                      label: "Authorize",
                      connectedSystem: ri!connectedSystem
                    )
                  ),
                  a!richTextItem(
                    text: " and grant permission. When done, return here and try again."
                  ),
                  a!richTextItem(
                    text: char(10) & char(10)
                  )
                }
              ),
              a!buttonLayout(
                primaryButtons: {
                  a!buttonWidget(
                    size: "SMALL",
                    label: "Submit Again",
                    style: "PRIMARY",
                    submit: true
                  )
                },
                secondaryButtons: {
                  a!buttonWidget(
                    size: "SMALL",
                    label: "Go Back",
                    style: "SECONDARY",
                    value: true,
                    saveInto: a!save(
                      local!verified,
                      null
                    )
                  )
                }
              )
            }
          )
          /* Previous form content would go here if user wanted to edit their changes */
        }
      )
    }
  )
)

images:auth_ling_write.png

FEEDBACK