OAuth¶
Contents¶
Introduction and overview¶
OAuth 2.0 is used to authorize 3rd party applications to access wire-server
resources on behalf of a Wire user.
Currently, only 3rd party apps that have been implemented and approved by Wire are supported. OAuth is not open for public use.
Supported OAuth apps:
- Outlook Calendar Extension
wire-server
implements a subset of the The OAuth 2.0 Authorization Framework (RFC 6749).
Please refer to the documentation below for a reference of the subset that is implemented without the noise of having to go through the complete RFC.
Roles¶
The user (resource owner)¶
The user is the resource owner who gives permission to the OAuth client to access parts of their resources.
3rd party application (OAuth client)¶
A 3rd party app is attempting to get access a resource on behalf of the user. It needs to get permission from the user in order to do so. The terminology is a bit fuzzy here: we use the terms app, application, client synonymous. To disambiguate, we qualify with “oauth” (oauth client, …).
Resource server¶
The resource server is the API server the 3rd party app attempts to access on behalf of the user. In our case the resource server is wire-server
.
Authorization server¶
The authorization server does the authentication of the user and establishes whether the user approves or denies the client’s access request. In this case the authorization server is the same server as the resource server which is wire-server
.
Supported OAuth flow¶
wire-server
currently only supports the Authorization Code Flow with Proof Key for Code Exchange (PKCE) which is optimized for public clients such as Outlook Calendar Extension.
OAuth client developer reference¶
Registering an OAuth client¶
A new OAuth client can be register only via the internal API of brig
by providing an application name and a redirect URL:
Parameters:
Parameter | Description |
---|---|
redirect_url | The URL to which Wire app will redirect the browser after authorization has been granted by the user |
application_name | The name of the application that will be shown on the consent page |
Client credentials will be generated and returned by wire-server:
These credentials have to be stored in a safe place and cannot be recovered if they are lost.
Authorization request¶
When the user wants to use the 3rd party app for the first time, they need to authorize it to access Wire resources on their behalf.
They first need to click on the “Login” (or similar) button (1. in OAuth 2.0 authorization code flow diagram above) which will redirect them to a Wire login page to authenticate (2.-3. in diagram above). Once authenticated, they are redirected to the consent page.
If the user is already logged in the authentication will be skipped and they are directly shown the consent page.
On the consent page, the user is asked to authorize the client’s access request. They can either grant or deny the request and the corresponding scope, a list of permissions to give to the 3rd party app, (4. in diagram above).
The client needs to create a unique code_verifier
as described in RFC 7636 section 4.1 and send a code_challenge
, which is the unpadded base64url-encoded SHA256 hash of the code verifier as described in RFC 7636 section 4.2. The code_challenge
must be included in the request. The S256
code challenge method is mandatory. The code_verifier
must not be included in the request.
Example request:
Url encoded query parameters:
Parameter | Description |
---|---|
scope | Required. The scope of the access request. |
response_type | Required. Value MUST be set to code . |
client_id | Required. The client identifier. |
redirect_url | Required. MUST match the URL that was provided during client registration |
state | Required. An opaque value used by the client to maintain state between the request and callback.The authorization server includes this value when redirecting the user-agent back to the client.The parameter is used for preventing cross-site request forgery. |
code_challenge | Required. Generated by the client from the code_verifier |
code_challenge_method | Required. It MUST be set to S256 |
Once the user consents, the browser will be redirected back to the 3rd party app, using the redirect URI provided during client registration, with an authorization code and the state value as query parameters (5. in diagram above). The authorization code can now be used by the 3rd party app to retrieve an access token and a refresh token and is good for one use.
Example response:
Retrieve access and refresh token¶
The 3rd party app sends the authorization code together with the client credentials and the parameters shown below using the application/x-www-form-urlencoded
format with character encoding of UTF-8 to the authorization server (6. in diagram above) to retrieve an access token and a refresh token (7.-8. in diagram above):
Parameters:
Parameter | Description |
---|---|
code | Required. The authorization code received from the authorization server. |
client_id | Required. The client identifier. |
grant_type | Required. Value MUST be set to authorization_code . |
redirect_uri | Required. The value MUST be identical to the one provided in the authorization request |
code_verifier | Required. The code verifier as described above. |
Example response:
The expiration time in the response (expires_in
) refers to the expiration time of the access token.
Accessing a resource¶
The access token, presented as Bearer <token>
in the Authorization
header, can now be used by the 3rd party app to access resources on behalf of the user (9.-11. in diagram above).
Refresh access token¶
Access tokens are short lived and need to be refreshed regularly. To do so, the client makes a refresh request to the token endpoint by adding the parameters shown below using the application/x-www-form-urlencoded
format with a character encoding of UTF-8 in the HTTP request entity-body.
Example request:
Parameters:
Parameter | Description |
---|---|
refresh_token | Required. The refresh token issued to the client. |
client_id | Required. The client identifier. |
grant_type | Required. Value MUST be set to refresh_token . |
client_secret | Required. The client’s secret. |
Example response:
Revoke a refresh token¶
A refresh token can be revoked as follows:
Parameters:
Parameter | Description |
---|---|
client_id | Required. The client identifier. |
refresh_token | Required. The refresh token issued to the client. |
client_secret | Required. The client’s secret. |
Example response:
Wire client developer reference (ZAuth authorized API)¶
Retrieve OAuth client info¶
Authenticated endpoint to retrieve client information, necessary to display authorization prompt/user consent page.
See swagger docs.
Retrieve a list of 3rd party apps with account access¶
Authenticated endpoint to retrieve a list of all applications that have account access via OAuth.
See swagger docs.
Revoke account access¶
3rd party app access can be revoked, by invalidating all active refresh tokens, as follows:
See swagger docs.
Site admin reference (Configuration)¶
Enable/disable OAuth¶
If not configured, OAuth is disabled per default. OAuth can be enabled in the wire-server Helm as follows:
Setting up public and private keys¶
To use the OAuth functionality, you will need to set up a public and private JSON web key pair (JWK) in the wire-server Helm chart. This key pair will be used to sign and verify OAuth access tokens.
Key can be generated e.g. with jwx like this:
jwx
is available via nix: nix-shell -p jwx
.
To configure the JWK, go to the wire-server Helm chart and provide the JWK information, private and public key set for brig
and the public key for nginz
, as in the examples below:
Note that the JWK is a sensitive configuration value, so it is recommended to use Helm’s support for managing secrets instead of including it in a plaintext values.yaml
file.
OAuth authorization code, access token, and refresh token expiration¶
The the OAuth authorization code expiration and access and refresh token expiration can be overridden in the Helm file as follows:
Maximum number of active refresh tokens¶
The maximum number of active OAuth refresh tokens a user is allowed to have can be configured as follows:
Enable 3rd party apps for teams¶
3rd party apps are enabled based on the team’s payment plan by ibis
.
Implementation details¶
Token handling¶
Authorization code¶
The authorization code is stored as plain text rather than a “scrypted” hash because it is the key to look up the associated information like the client ID, the user ID, the scope and the redirect URL. An authorization code can only be used once and has a very short time to live.
Access token¶
Access tokens are self-contained JSON Web Tokens (JWT) that contain the following claims:
iss
: The issuer, e.g.wire-server
aud
: The resource server (in our case the same asiss
)iat
: The time at which the token was issuedsub
: Identifier of the resource owner, the Wire user IDexp
: The expiration time of the tokenscope
: A whitespace separated list of permissions
Example token payload:
- The access tokens are created and signed by
brig
. - When accessing a resource
nginz
validates the token and forwards the request towire-server
with theZ-User
header containing the user ID taken from thesub
claim. - Token validation includes signature, expiration, and scope validation.
- Access tokens are short lived.
- Access tokens are bearer tokens and cannot be revoked directly, therefore 3rd party access revocation will entail the token expiration.
Refresh token¶
- A refresh token is always associated with
- a user
- a 3rd party app (the OAuth client)
- and a scope (list of permissions given to the app)
- A user can have more than one active refresh token for the same 3rd party app (e.g. they might use multiple devices, replace devices, or run multiple instances of the app somehow)
- The maximum number of active refresh tokens per user and app is limited (see
values.yaml
for default settings) - Once a new refresh token is requested and the limit is exceeded, the oldest refresh token will be deleted/invalidated
- If the bearer of the invalidated token is not identical to the requester, it could mean that the bearer of the invalidated token needs to re-authorize
- Once a refresh token is used, it will be invalidated and a new refresh token will be generated and returned as part of the response (token rotation)
- For now, we will not yet implement re-use detection, but in the future this should be possible
- The refresh token is given to the client/app as a signed JWT containing only the refresh token ID which is used internally to look up the refresh token info
- Refresh tokens are long-lived, and the expiration is configurable on the server level
Scopes¶
Endpoints that support OAuth have the required scope listed in the swagger documentation.
Scope implementation details¶
To enable OAuth access for a resource a scope has to be defined in the nginx location config that matches the endpoint’s path.
The current convention is that scope names should match the resource’s paths separated by an underscore. E.g. /conversations/:cid/code
becomes conversations_code
(path parameters are omitted).
Furthermore, the scope must be prefixed (separated by a colon) with
admin
,write
, orread
for endpoints with HTTP methodGET
admin
, orwrite
for endpoints with HTTP methodsPOST
orPUT
- and
admin
for endpoints with HTTP methodDELETE
E.g. the required scope for POST /conversations/:cid/code
is write:conversations_code
.
Steps for adding a new scope (making an endpoint accessible via OAuth)¶
- Add a new constructor to the type
OAuthScope
in/home/leif/Repositories/wire-server/libs/wire-api/src/Wire/API/OAuth.hs
- Implement
IsOAuthScope
- Update
ToByteString
andFromByteString
instances and verify that the roundtrip tests run successfully - Add the servant combinator
DescriptionOAuthScope
to the endpoint in question which will render the correct swagger description - Finally assign the scope name (without the prefix) to the location config via the
charts/nginz/values.yaml
file to theoauth_scope
as shown in the example below
Example:
For local development and integration tests, add the scope to services/nginz/integration-test/conf/nginz/nginx.conf
as follows
Public/private keys¶
- Public and private keys are provided as JSON Web Keys (JWK) or key sets
- The keys can be generated using jwx
- Keys are provided as secrets. Details depend on the type of deployment.
brig
needs to be in possession of the public and private key andnginz
needs to be provided with the public key only