Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: Short-lived tokens for workspace apps #617

Closed
3 tasks done
aoberoi opened this issue Aug 15, 2018 · 0 comments
Closed
3 tasks done

RFC: Short-lived tokens for workspace apps #617

aoberoi opened this issue Aug 15, 2018 · 0 comments
Labels
discussion M-T: An issue where more input is needed to reach a decision enhancement M-T: A feature request for new functionality

Comments

@aoberoi
Copy link
Contributor

aoberoi commented Aug 15, 2018

The Slack platform will soon allow developers to use short-lived access tokens, which is a security feature first discussed at Spec.

This issue will capture the work associated with supporting short-lived tokens in this package. At this point we'd like to share the planned API changes, and we're hoping for your feedback and comments.

Goals:

  1. Give developers a low friction option to use short-lived tokens by automatically handling token refresh without any intervention.

    A. These developers should be allowed to opt-in (but are not required) to observe the token refresh process in order to persist the latest access token to their own storage when appropriate.

  2. Allow developers who have specialized needs to use short-lived tokens and implement the token refresh process on their own.

  3. Allow developers who are not using short-lived tokens to be unaffected by the code changes, and for their usage to continue as expected.

API Changes

Constructor

new WebClient(token?: string, options?: WebClientOptions)

The WebClientOptions interface will gain three new optional properties:

  • refreshToken?: string - an xoxr-... token provided by the initial token exchange with Slack. it also might be loaded from the environment or a database when the application is launched.
  • clientId?: string - the client ID provided by Slack on app creation
  • clientSecret?: string - the client secret provided by Slack on app creation

When all three of these options are specified, the client will function in automatically-refreshing mode. That means it will attempt to handle failures to API calls due to token expiration (see token refresh below). If any of these options is specified, all must be, otherwise the constructor will throw an error.

The token is still optional, but when it is not provided and the client is in automatically-refreshing mode, it will treat the first API call the same as one which failed due to token expiration and initiate token refresh before attempting the call.

The client does not handle initial token exchange to retrieve a refresh token. Developers can initialize an unauthenticated WebClient instance (without a token) to call the client.oauth.access() method and use the values in the result to initialize another, authenticated, WebClient instance. Or, they may get the values from an existing OAuth 2.0 library/framework.

Events

token_refreshed

This is a new event type that will be emitted after a successful token refresh occurs. By the time this event is triggered, the client has already been updated to use the refreshed values. A value is emitted with this event type, and its of a new interface type TokenRefreshedEvent with properties as follows:

  • access_token: string - the token from the refresh
  • expires_in: string - the number of seconds the token will remain valid
  • team_id: string - the team ID for which this client is currently authenticated
  • enterprise_id?: string - the org ID, if one exists, for which this client is currently authenticated

Developers who wish to persist the latest token to their own storage will need to handle this event. In order to support the needs of storing tokens, the properties of TokenRefreshedEvent are intended to be the most commonly needed values to create or update a record in a database.

Properties

token?: string

This property will transition from readonly to mutable. Developers who wish to handle token refresh manually would assign a value to this property.

readonly refreshToken?: string

The refresh token, if one is provided during initialization. This change intentionally doesn't allow the value to mutate. Instead, it is recommended that developers instantiate a new client if the refresh token was revoked. This prevents issues where the refresh token, client ID, and client secret are not synchronized.

readonly clientId?: string

The client ID, if one is provided during initialization. Not mutable for reasons cited above.

readonly clientSecret?: string

The client secret, if one is provided during initialization. Not mutable for reasons cited above.

Methods

apiCall(method: string, options?: WebAPICallOptions): Promise<WebAPICallResult> (and named aliases)

This method has no API changes, but has a few behavioral changes that are worth noting. When the client is in automatically-refreshing mode, token refresh may occur, and the returned promise will represent the result of the attempt after the refresh completes. When the client is not in automatically-refreshing mode and the token is expired, the promise will continue to reject with a WebAPIPlatformError, and its data property will contain the response. This allows a developer to deal with token refresh manually.

Errors

ErrorCode.RefreshFailed

A new error code to signal that automatic refresh was attempted and failed.

Token refresh algorithm

  1. Before an API call occurs, the cached expires_in value is checked. If the lifetime of the current token has certainly been exceeded, or if there is no token, then attempting the API call can be skipped and we proceed to (3).

  2. Attempt the API call as is done today, except the timestamp of the attempt is stored on the request (or some representative object). When the attempt completes, if the result is a platform error with a invalid_auth reason, the "should refresh" checks are performed (2A) and then a task is queued to refresh the token (3). Otherwise the result or error is returned as is done today.

    A. If the refresh in progress flag is set (see next step) OR the last refreshed timestamp exists and is later than the timestamp for the attempt (see next step), then the original API call is attempted again (2). Otherwise, proceed.

  3. A flag is set to signal that refresh is in progress. The request to oauth.access with grant type refresh_token is made. If this request fails, the flag is unset and the attempted API call fails with an error with code ErrorCode.RefreshFailed. If the API call succeeds, the flag is unset, the last refreshed timestamp is recorded, and the new access_token and expires_in are stored. The token_refreshed event is triggered (using team_id and org_id from the successful refresh). Then the original API call is attempted (2).

NOTE: The refresh in progress flag and the last refreshed timestamp are used to handle the case where an earlier API call fails either when in the process of refreshing, or when refresh completed recently and the failure is from earlier in the queue. You wouldn't want to immediately start refreshing again.

Edit 08/24/2018: The error returned from the platform in the case of an expired token is invalid_auth, not token_expired. The platform is intentionally vague about the reason, so that malicious actors cannot figure out whether a token was ever valid.

Requirements (place an x in each of the [ ])

  • I've read and understood the Contributing guidelines and have done my best effort to follow them.
  • I've read and agree to the Code of Conduct.
  • I've searched for any related issues and avoided creating a duplicate issue.
@aoberoi aoberoi added enhancement M-T: A feature request for new functionality discussion M-T: An issue where more input is needed to reach a decision labels Aug 15, 2018
This was referenced Aug 26, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discussion M-T: An issue where more input is needed to reach a decision enhancement M-T: A feature request for new functionality
Projects
None yet
Development

No branches or pull requests

1 participant