RFC: Short-lived tokens for workspace apps #617
Labels
discussion
M-T: An issue where more input is needed to reach a decision
enhancement
M-T: A feature request for new functionality
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:
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.
Allow developers who have specialized needs to use short-lived tokens and implement the token refresh process on their own.
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
- anxoxr-...
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 creationclientSecret?: string
- the client secret provided by Slack on app creationWhen 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 atoken
) to call theclient.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 refreshexpires_in: string
- the number of seconds the token will remain validteam_id: string
- the team ID for which this client is currently authenticatedenterprise_id?: string
- the org ID, if one exists, for which this client is currently authenticatedDevelopers 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 itsdata
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
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).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.
A flag is set to signal that refresh is in progress. The request to
oauth.access
with grant typerefresh_token
is made. If this request fails, the flag is unset and the attempted API call fails with an error with codeErrorCode.RefreshFailed
. If the API call succeeds, the flag is unset, the last refreshed timestamp is recorded, and the newaccess_token
andexpires_in
are stored. Thetoken_refreshed
event is triggered (usingteam_id
andorg_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
, nottoken_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[ ]
)The text was updated successfully, but these errors were encountered: