Skip to content

Authorization Code Grant

The OpenID Connect authorization code grant allows clients to authenticate users and request permissions from them. User authentication is returned in the form of OpenID Connect ID tokens, and granted permissions are returned in user tokens.

The grant is started when the client constructs an authorization request URL, and directs the user agent to it. The user authenticates with Alias and selects which of the requested permissions to grant. The user is then redirected to the client's pre-registered redirect URI with an auth code in the querystring, which can be exchanged for tokens at the token endpoint.

If the user has an active session with Alias they may not be required to authenticate again. Similarly, if they have previously granted the client the requested permissions they may not be prompted again.

The openid scope must be included in all requests as it represents permission to access the user's alias ID, and other scopes can't be used without access to that ID. It is also what indicates an OpenID Connect request, resulting in an ID token being returned along with the user token. While other scopes are optional, users must grant the openid scope if they approve an auth request.

offline_access is a special scope that allows the client to use granted permissions - e.g. send messages - when the user isn't logged in. It must be included in an auth request to receive a refresh token (along with other requirements), and certain client endpoints only work with users that have granted it.

Available Scopes

Scope Required Description
openid Yes Access the user's alias ID.
offline_access No Use granted permissions when the user isn't logged in.
contact No Send messages to the user.
nickname No Access a nickname the user wishes to be known as.

Communication channels represent permission to send a user certain types of messages, and are defined per client. This gives users more fine-grained control of the messages they want to receive, beyond the binary contact scope. They aren't part of OAuth 2.0 or OpenID Connect specifications, but operate similarly to scopes.

A user must always grant the special _meta channel if they grant the contact scope. This channel is reserved for important messages about the user's connection with the client, e.g. notifying the user of a terms and conditions change, or a data breach. The client doesn't need to (and can't) include it explicitly in an auth request and it isn't explicitly listed in tokens or token endpoint responses, but it is included in a user's list of granted channels.

Authorization Request

GET https://projectalias.com/oauth2/authorize

Initiate an auth request by directing users to this endpoint with a number of querystring options. If the user approves the request, they will be redirect back to the client's redirect URI with an auth code, which can be exchanged at the token endpoint.

The include_granted_scopes option allows requesting only the scopes currently required and ensuring any other previously granted scopes are included in the resulting user token. This is useful for progressively requesting scopes in response to user actions, rather than requesting all possible scopes up front. Note that the openid scope must still always be included explicitly. The include_granted_channels option works similarly, but for channels.

The state option should be used to protect against CSRF attacks on the redirect URI. See the CSRF section in the OAuth 2.0 spec for details.

Similarly, the nonce option should be used to protect against replay attacks on the redirect URI. See the nonce implementation notes in the OpenID Connect spec for details.

See the OAuth 2.0 spec for more details on the authorization endpoint.

Querystring Options

Option Type Required Description
scope string Yes A space separated list of scopes to request.
include_granted_scopes boolean No Include previously granted scopes in the request, in addition to those in scope
channels string No A space separated list of channels to request. Ignored if contact scope isn't requested.
include_granted_channels boolean No Include previously granted channels in the request, in addition to those in channels. Ignored if contact scope isn't requested.
response_type string Yes Must be code.
client_id string Yes The ID for the client.
redirect_uri string Yes The URI to use for the callback. Must be one of the client's pre-registered redirect URIs.
state string No A string selected by the client that will be included in the callback.
nonce string No A string selected by the client that will be included in the ID token.
prompt string No none, or a space separated list of prompt options from login, consent, select_account.
max_age integer No Maximum allowed time in seconds since the user was last authenticated with Alias.

Callback Querystring

Field Description
code An auth code that can be exchanged at the token endpoint.
state The nonce provided by the client in the initial auth request, if any.

Errors

If a request fails due to issues with the provided redirect_uri or client_id, the user will be shown an error page on Alias, and won't be redirected back to the client's redirect URI. For other client errors, or if the user rejects the request, they will be redirected back to the client's redirect URI with error information in the querystring.

See the relevant sections of the OAuth 2.0 and OpenID Connect specs for details.

Examples

Example Request

GET /oauth2/authorize?scope=openid%20offline_access%20contact%20nickname&channels=notifications%20product_updates&response_type=code&client_id=bjOB6HV7cTKtxvfr2k2ucg&redirect_uri=https%3A%2F%2Fexample.com%2Falias-callback&state=4bqlMpbTFb_yXBkHNL6R1Q&nonce=rgnxaHF9zIHaafVo4_8Pdw HTTP/1.1
Host: projectalias.com
Authorization: Bearer <access-token>
curl "https://projectalias.com/oauth2/authorize?scope=openid%20offline_access%20contact%20nickname&channels=notifications%20product_updates&response_type=code&client_id=bjOB6HV7cTKtxvfr2k2ucg&redirect_uri=https%3A%2F%2Fexample.com%2Falias-callback&state=4bqlMpbTFb_yXBkHNL6R1Q&nonce=rgnxaHF9zIHaafVo4_8Pdw" \
    -H "Authorization: Bearer <access-token>"
import requests
r = requests.get('https://projectalias.com/oauth2/authorize',
                 headers={'Authorization': 'Bearer <access-token>'},
                 params={'scope': 'openid offline_access contact nickname',
                         'channels': 'notifications product_updated',
                         'response_type': 'code',
                         'client_id': 'bjOB6HV7cTKtxvfr2k2ucg',
                         'redirect_uri': 'https://example.com/alias-callback',
                         'state': '4bqlMpbTFb_yXBkHNL6R1Q',
                         'nonce': 'rgnxaHF9zIHaafVo4_8Pdw'})

Example Callback

https://example.com/alias-callback?code=8amdpfEyvNTv1JlPMGONYQ&state=4bqlMpbTFb_yXBkHNL6R1Q

Token Request

POST https://projectalias.com/api/oauth2/token

Exchange the auth code received at the redirect URI for tokens. Auth codes have short expiries, so this should be done as soon as possible.

ID tokens - including the nonce - should be validated before use. See the ID token validation section in the OpenID Connect spec for details.

Tip

It's highly recommended you use a standard library to validate ID tokens. jwt.io maintains a list of JWT validation libraries.

See the OAuth 2.0 spec for more details on the token endpoint.

Authentication

Confidential clients must authenticate themselves with HTTP basic auth, using their client credentials. The client ID is used as the user-id, and the client secret as the password.

Warning

Confidential clients should only perform this request from a secured backend as it requires sensitive client credentials.

Request Body

The request body must be encoded as application/x-www-form-urlencoded, and the Content-Type header set appropriately.

Field Required Description
grant_type Yes Must be authorization_code
code Yes An auth code obtained from the querystring of a callback to the redirect_uri.
redirect_uri Yes The redirect_uri used to obtain the auth code.
client_id Maybe The ID for the client. Required for public clients.

Response Body

The response body is encoded as application/json.

Field Type Description
access_token string A client token.
token_type string Always Bearer.
expires_in integer How many seconds the token will be valid for. Tokens are valid for 30 minutes.
scope string A space separated list of scopes the token has.
channels string A space separated list of channels the token has. Only included if the token has the contact scope.
id_token string An ID client token for the user.
refresh_token string A refresh token. Only included in certain situations.

Errors

In case of an error, the endpoint will endevour to respond with an appropriate HTTP status code. It may include details of the error in an application/json response body.

Field Type Description
error string An error code.
error_description string An optional human-readable description of the error.

See the relevant section of the OAuth 2.0 spec for details of the OAuth 2.0 specific error codes. Note that you may receive error codes not defined in the spec.

Examples

Example Request

POST /api/oauth2/token HTTP/1.1
Host: projectalias.com
Authorization: Basic <basic-credentials>
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=8amdpfEyvNTv1JlPMGONYQ&redirect_uri=https%3A%2F%2Fexample.com%2Falias-callback
curl -XPOST "https://projectalias.com/api/oauth2/token" \
    -H "Authorization: Basic <basic-credentials>" \
    -d "grant_type=authorization_code" \
    -d "code=8amdpfEyvNTv1JlPMGONYQ" \
    -d "redirect_uri=https://example.com/alias-callback"
import requests
r = requests.post('https://projectalias.com/api/oauth2/token',
                  headers={'Authorization': 'Basic <basic-credentials>'}
                  data={'grant_type': 'authorization_code',
                        'code': '8amdpfEyvNTv1JlPMGONYQ',
                        'redirect_uri': 'https://example.com/alias-callback'})

Example Response

{
    "access_token": "x-o7XwWiGWCBkH_TKV-slC8IP_DACpN2k9qQO3q2sV1y4b_fvJgBBIP7xlnmpW1ZZ2JojcpK-SP8G1dFKETn7zBdr8Q_xQsbGQ3upIY45u1AL8vhQLsMWOK8Npv4KlblD_6Ium0jm-16cYBSd8I0l3Xy0qklwsvvNkqYnpX5mUg2eYYVUwafiW7lwApmOAzjQuYME78kRfkqXwu8gqMzmB0IK0jw3by_ZjGbYaadv3HaIzzmjs1HsAvTwvoZ4KKF-jdk5i7LevoGXrsHqeSKmM2vx5CJA0E5O_vIy5mHqU-ZxBL1v5EOdBS7QfpUVlMuByCMCq0xoww7Ygz458rLRw0sTK_jD563EfWZefePbFLI9ApTe1ULTxbZxaFNomktUKifrFruk8lyh7i9UykoKLV_Cl_MmOc1AYO_YPrg5irtMC-oHwE5xgyonjdYq4hJcgMoy5R-oUYnAiGnDyTHhMCPJ-Lmzr0kWo6v7S7VIr4mFXpqNN7isQtcMoTFd2MmYfaWyzGuTuk4xBXhilyO0OIo0_XPauD8eIC-LXyNd7l67ENTs_1iXCzOTzf_NKiXLlThXY7aA2XOOnJcQVLBIpjVRRk2aWezOIaakpXkg0HGiUzdBBttCXEZoSF68IftUXJVrmkS_oW3w_4dcIAvUeUb6opCDXcmg09fNwJgIRs",
    "token_type": "Bearer",
    "expires_in": 1800,
    "scope": "openid offline_access contact nickname",
    "channels": "notifications product_updates",
    "id_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IlJJcGJmaDRmcHFNcEhuMkM3V3hyS1EifQ.eyJqdGkiOiJEQ1oyXzdTZTN6aVhkY2RTc2NBTDB3IiwiaXNzIjoiaHR0cHM6Ly9wcm9qZWN0YWxpYXMuY29tIiwiYXVkIjoiYmpPQjZIVjdjVEt0eHZmcjJrMnVjZyIsImlhdCI6MTU5MzUxOTMzMCwiZXhwIjoxNTkzNTIxMTMwLCJub25jZSI6InJnbnhhSEY5eklIYWFmVm80XzhQZHciLCJhdF9oYXNoIjoiaU11S2FWb3ZRU29qNHVlMHN6RGlkZyIsInN1YiI6ImVwR0VjR2ZjNkxRTW1LVHFmZldXMmciLCJuaWNrbmFtZSI6IkZyZWQiLCJhdXRoX3RpbWUiOjE1OTM1MTg3MzB9.RPRBN6R7Ey6At6VtCdv_8qoS-0wQCWF6OQS7bUudt9IV01X1a5qKc1ja613u9lD0T5QNdVSZdwj6OYZ2LZ5QMRX_BDF42ZeyHhLSJ30RGyAOJoUDRIOT8CtX7fMOwmB52bh5Moo3XSJhjJ5pQ2S0hWQFIGi2eG3-CMYx0mLz8TmPoYi1AQT__Gvruc5JOcu9RvKHWBtT3SKvVZGKXxTb6KPnWbn0jnZGtQzDl3QWu6CTwQOXy_kQwqAsX94BIbHqN4hJZjplKf7OxElmNT0K0lCsDzpW6pKrK8_T0PbLTiJmlkKz3H6Ogb272r5v0sgvZqN_bNQv_2UzT5QSPrp6fxrfmhuPs3bcR6VWFtX2FOobzPJNj8WhelhgBXUp7Up3CqvcMDo3_CpMQlnkIOQoWcMRebzEBK4D8mNdi49CVzSguxtU6FjQVenwDhnZ5tGx6-hi34dlqu3zFEgOT19fnQX3ew6q8KvzQYbFdEv__TQuld5HTtrc_3hWynuS83e9",
    "refresh_token": "qBRtkoK4AN1tKZkJnSMSHLsgjB9ToFz8FCn17TI3lF2dhmLpACoE_zZmBhiBc1YvnaLi-WXHHWMBm8pcNj3ln5So-fSKjOvYoZCSbKykzsCWtIEnlSLCLuYTt7O2ulSaUuowryBcdlgsF0LWzUd0BJ7leawLyz2ZvS37AzLrQGku0phaJiuwlmY2lD7iNuPxU4mwX2cfhK21OUHVvrBBc65t7Sf9AHFUX4lnM17PuzxNsXgL308Nq06Qluq7UF1u7mPuFqb6khudsbubCY0FcUNbrX1aEdObb4kdNP87T09DwQdvhdJZfBSrh_aKzxvcioFuvtfE9cxk2bfHUlCZNLLxuJwAJ3MdsvG1MHfIWQbI3WTdOQJ8iMeeTwS13UaQm1rkETkiMbGkIb5AJskxtWABF9N2Z645HTIJSWI9iThy2bJaZaK5dhmJNnZIqb6FbR4KTlpypo-hzdvI_gRXScWFDlfrYQc-Bz_q1VnZaSUChkDg7RzkIPEbEEHNc8fc26bOgQil-vTNM00RQUFTap81iiedOwRy3T8yIeY7VAY9FGvJDmWu7Msnf1aBOnAmhBNjJHzOCJOCsR_J9GPgL7BH2O9lfXf7Jj8HW-ez5rLj_OgygvKE_2HXBXrxcm7Kst8DnGSROO010CpWpm9C9S046TaimYZBoy1DIoz_xBA"
}

Refresh Token Request

POST https://projectalias.com/api/oauth2/token

Exchange a refresh token for new tokens. New user and refresh tokens are returned - new ID tokens are not. Only confidential clients can use this request.

By default, the new user token will have the same scopes as the user token from the original auth request. However, if the user has since revoked some of those scopes, they won't be included.

If the new user token only needs a subset of these scopes, this can be specified with the scope field. If this includes any scopes that weren't one of the original scopes - or that have subsequently been revoked - the request will fail.

The updated refresh token returned always has the same functionality (i.e. can produce user tokens with the same scopes) as the one used - just with a later expiry.

See the OAuth 2.0 spec for more details on refreshing access tokens.

Authentication

The client must authenticate themselves with HTTP basic auth, using their client credentials. The client ID is used as the user-id, and the client secret as the password.

Warning

This request should only be performed from a secured backend as it requires sensitive client credentials.

Request Body

The request body must be encoded as application/x-www-form-urlencoded, and the Content-Type header set appropriately.

Field Required Description
grant_type Yes Must be refresh_token
refresh_token Yes A refresh token obtained from a previous request to the token endpoint.
scope No A space separated list of scopes to request.

Response Body

The response body is encoded as application/json.

Field Type Description
access_token string A client token.
token_type string Always Bearer.
expires_in integer How many seconds the token will be valid for. Tokens are valid for 30 minutes.
scope string A space separated list of scopes the token has.
channels string A space separated list of channels the token has. Only included if the token has the contact scope.
refresh_token string An updated refresh token.

Errors

In case of an error, the endpoint will endevour to respond with an appropriate HTTP status code. It may include details of the error in an application/json response body.

Field Type Description
error string An error code.
error_description string An optional human-readable description of the error.

See the relevant section of the OAuth 2.0 spec for details of the OAuth 2.0 specific error codes. Note that you may receive error codes not defined in the spec.

Examples

Example Request

POST /api/oauth2/token HTTP/1.1
Host: projectalias.com
Authorization: Basic <basic-credentials>
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&refresh_token=qBRtkoK4AN1tKZkJnSMSHLsgjB9ToFz8FCn17TI3lF2dhmLpACoE_zZmBhiBc1YvnaLi-WXHHWMBm8pcNj3ln5So-fSKjOvYoZCSbKykzsCWtIEnlSLCLuYTt7O2ulSaUuowryBcdlgsF0LWzUd0BJ7leawLyz2ZvS37AzLrQGku0phaJiuwlmY2lD7iNuPxU4mwX2cfhK21OUHVvrBBc65t7Sf9AHFUX4lnM17PuzxNsXgL308Nq06Qluq7UF1u7mPuFqb6khudsbubCY0FcUNbrX1aEdObb4kdNP87T09DwQdvhdJZfBSrh_aKzxvcioFuvtfE9cxk2bfHUlCZNLLxuJwAJ3MdsvG1MHfIWQbI3WTdOQJ8iMeeTwS13UaQm1rkETkiMbGkIb5AJskxtWABF9N2Z645HTIJSWI9iThy2bJaZaK5dhmJNnZIqb6FbR4KTlpypo-hzdvI_gRXScWFDlfrYQc-Bz_q1VnZaSUChkDg7RzkIPEbEEHNc8fc26bOgQil-vTNM00RQUFTap81iiedOwRy3T8yIeY7VAY9FGvJDmWu7Msnf1aBOnAmhBNjJHzOCJOCsR_J9GPgL7BH2O9lfXf7Jj8HW-ez5rLj_OgygvKE_2HXBXrxcm7Kst8DnGSROO010CpWpm9C9S046TaimYZBoy1DIoz_xBA
curl -XPOST "https://projectalias.com/api/oauth2/token" \
    -H "Authorization: Basic <basic-credentials>" \
    -d "grant_type=refresh_token" \
    -d "refresh_token=qBRtkoK4AN1tKZkJnSMSHLsgjB9ToFz8FCn17TI3lF2dhmLpACoE_zZmBhiBc1YvnaLi-WXHHWMBm8pcNj3ln5So-fSKjOvYoZCSbKykzsCWtIEnlSLCLuYTt7O2ulSaUuowryBcdlgsF0LWzUd0BJ7leawLyz2ZvS37AzLrQGku0phaJiuwlmY2lD7iNuPxU4mwX2cfhK21OUHVvrBBc65t7Sf9AHFUX4lnM17PuzxNsXgL308Nq06Qluq7UF1u7mPuFqb6khudsbubCY0FcUNbrX1aEdObb4kdNP87T09DwQdvhdJZfBSrh_aKzxvcioFuvtfE9cxk2bfHUlCZNLLxuJwAJ3MdsvG1MHfIWQbI3WTdOQJ8iMeeTwS13UaQm1rkETkiMbGkIb5AJskxtWABF9N2Z645HTIJSWI9iThy2bJaZaK5dhmJNnZIqb6FbR4KTlpypo-hzdvI_gRXScWFDlfrYQc-Bz_q1VnZaSUChkDg7RzkIPEbEEHNc8fc26bOgQil-vTNM00RQUFTap81iiedOwRy3T8yIeY7VAY9FGvJDmWu7Msnf1aBOnAmhBNjJHzOCJOCsR_J9GPgL7BH2O9lfXf7Jj8HW-ez5rLj_OgygvKE_2HXBXrxcm7Kst8DnGSROO010CpWpm9C9S046TaimYZBoy1DIoz_xBA"
import requests
r = requests.post('https://projectalias.com/api/oauth2/token',
                  headers={'Authorization': 'Basic <basic-credentials>'}
                  data={'grant_type': 'refresh_token',
                        'refresh_token': 'qBRtkoK4AN1tKZkJnSMSHLsgjB9ToFz8FCn17TI3lF2dhmLpACoE_zZmBhiBc1YvnaLi-WXHHWMBm8pcNj3ln5So-fSKjOvYoZCSbKykzsCWtIEnlSLCLuYTt7O2ulSaUuowryBcdlgsF0LWzUd0BJ7leawLyz2ZvS37AzLrQGku0phaJiuwlmY2lD7iNuPxU4mwX2cfhK21OUHVvrBBc65t7Sf9AHFUX4lnM17PuzxNsXgL308Nq06Qluq7UF1u7mPuFqb6khudsbubCY0FcUNbrX1aEdObb4kdNP87T09DwQdvhdJZfBSrh_aKzxvcioFuvtfE9cxk2bfHUlCZNLLxuJwAJ3MdsvG1MHfIWQbI3WTdOQJ8iMeeTwS13UaQm1rkETkiMbGkIb5AJskxtWABF9N2Z645HTIJSWI9iThy2bJaZaK5dhmJNnZIqb6FbR4KTlpypo-hzdvI_gRXScWFDlfrYQc-Bz_q1VnZaSUChkDg7RzkIPEbEEHNc8fc26bOgQil-vTNM00RQUFTap81iiedOwRy3T8yIeY7VAY9FGvJDmWu7Msnf1aBOnAmhBNjJHzOCJOCsR_J9GPgL7BH2O9lfXf7Jj8HW-ez5rLj_OgygvKE_2HXBXrxcm7Kst8DnGSROO010CpWpm9C9S046TaimYZBoy1DIoz_xBA'})

Example Response

{
    "access_token": "jsbt16o__lp5Jq22y0P75fU-tV8frztJwUJM7AfbGVusjQWoMCqWHdpBwTrsJ6xU5AYXvfS5cykuls3Dl7eBQ4Pu6_xcJGVxgmKEgBGmZJYH_b1xnkDSUvYEdqEuRUGujx-SQuvSjjWo4ABW-6BvM-yCUlS32RLLYdzFeF9brzvzJ9bE3bZOJBEGRufxG_DVI9zgkENpvp8CMKEjiOnaCjHOmU8y39ZDjVv8InF8BPCMJANi84YJtAkOjATD8dTZgwLpTQnnIXiS4l4bsVkWhUBYBRDSvicgVAuPGgTiOO251KEJMjS33zYU7PRmrqUSOIRSN0oNrzv4w2SQum01_gIwHwbkZgyyfIHMpyDedYX7LyWiird8bEElGGs00QwUcNR13RZrKBtAfgZAMsC7BxcGQu3S4_HfaPNDlLtqyzGTNgl1IOnkB22QfH_-dlRx8bZ2DMHgQMhiNi3Wmxy02V4eU2aMAeYBkG0sLxTHDT2IZnLP7dGWVJ_PGi7wPtVgE3wW1uK_ZqJIlKp0h315Z1VHf5JwF4cYrtg9HPZpXVE7VxxMhJCZ04yP9w2h_ys_q12-9vc4m0pt-hM3WTM2lVMU1S9WXmRjk_3m7lnbguFoKszhGm1py8XapmQE6YQZgwvvRcakFg7Dg2WPo-Zwrksxs1GTYtT57qR1OF7vwh8",
    "token_type": "Bearer",
    "expires_in": 1800,
    "scope": "openid offline_access contact nickname",
    "channels": "notifications product_updates",
    "refresh_token": "LwYSrfsVB8vqbxGyZg_M9xUe1cEqL24xYC_9PTYIPc9Bj3tPVii7UIVbbR3VZyT63GHqgY6rwW05_Qr89wjhQ4fU94YK91_HO2iCqUM4Swn0qN3Nql43vb6o6ETgotVzuYsYGysn5fmgL6tO-zqKRPjU6uuW05iLjfv0eDZ6CAA_--9OJPYJux_DTBYfg9aXqRzuQyKPGLOfQBCowD3wjIrCHpbBLhuiZsITL-uZ9WsqLKfUAZceMDGoDP3Ycu4Jm5fzTPFu2UGtSKIbuYirr3yv_pDX7MASYs-k5LqKK16KVV2I_9nlQqNY_Qg-2n9J5SNzy0e4g5pe1CXyY9qNiilhqK-8D5LIxdCaPoA15710SCxUsUMwLAw-6WDeXRqf05CDyjk6vQGxviSYWboPMrynM3Dhp-sGVanELRb8u6R6QibpcfzqH9sJ-IhwwjnwFiBAaeck9wah4EfOdAAV3jK6w4Z0bW9dqvfccik4NKhio0xdpgzcteWlTXzAQPsUMdVKqO-nWVzwlWKO1szAIHkgMorrHaVND-vik2y8YmiGLny-Q5_QYT5OObgX_r9BfWFxoGI8imwfnL7OPuxIxBMuW6D8OP4HMrWeeVZCk89jrdbk34rHDEaogevX68VogofuqJDMxDwbrWz_hfbCpPrKT0WDnOY6cJZ3HJcNHQA"
}