OAuth2 Identity Service

accesso provides an OIDC and OAuth2.1-compliant service capable of authenticating users via stored passwords or through social or federated identity with other upstream OIDC providers. Upon successful authentication, the endpoint returns a JSON Web Token (JWT) "access_token", which may then be used for making calls to accesso's APIs.

Standard Conventions

Our implementation of authentication is based on IETF standards and includes support for the following RFCs:

Our efforts for standard compliance are intended to ensure that client applications can reuse standard libraries for authentication flows. We provide full support for the OIDC Discovery protocol so that clients can acquire most of their configuration details directly from our service. E.g. see https://api.us.te2.io/.well-known/openid-configuration

We have also designed our services to enable capabilities like federated and social authentication in a way that is fully encapsulated in our service configuration so that client applications can use the standard authentication flow regardless of whether social authentication is enabled or not. It’s possible to change to an alternative upstream authenticator at a later time without affecting clients using these APIs.

accesso’s access_tokens are in the form of JSON Web Tokens, or JWT. Learn more about JWTs and list libraries for working with JWTs. refresh_tokens are obscure byte sequences and are not valid JWTs.

OAuth2 / OIDC Authentication APIs

accesso’s Authentication API is an OAuth2 and OIDC-compliant set of endpoints which enables an authorized user to acquire an access_token needed for accesso’s APIs.

The Authorization Service supports the following authentication use cases:

The Authorization Service supports the following OAuth2.1 grant types (and extensions):

On success, the OAuth2 token endpoint will return a json object which will contain an access_token in JSON Web Token (JWT) format. A refresh_token will also be returned, and (if requested) an idtoken can also be returned per the OIDC standard. All requests to the token endpoint are URL encoded (application/x-www-form-urlencoded) per the OAuth2 standard.

Regardless of the type of authentication, a successful authentication results in an HTTP response status of 200 and the following response payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
    "access_token": "eyJhbGciOiJSUz...",
    "token_type": "bearer",
    "refresh_token": "eyJhbGciOiJSUzI1Ni...",
    "expires_in": 86399,
    "scope": "authenticated openid services:all",
    "account_id": "user@yourcompany.com",
    "user_type": "Guests",
    "venueId": null,
    "organization": "your-company",
    "amr": [
        {
            "authority": "credential"
        }
    ],
    "iss": "https://www.theexperienceengine.com/",
    "phone_number": null,
    "source": "UNKNOWN",
    "iat": 1576075405,
    "email": "user@yourcompany.com",
    "uuid": "404589da-7a39-4e9c-8697-ce05fb57449e",
    "jti": "ec2488b7-f364-437c-a640-204670b9c44d"
}

Our response is based on the OAuth2.1 standard but includes additional fields in the JSON object response.

Notable fields above include:

The JWTs returned from the tokens API contain all of the data that is also returned in the api response. It is only necessary to retain the access and refresh tokens.

Authentication Scenarios

End-user grant_types

Sign-in via OAuth2 login screen

For end users, the standard grant_type=authorization_code flow is supported, conforming to the OAuth2.1 specification. The authorization service serves a login screen and (optional) consent screen which can be branded via configuration to use any html/css desired. In summary, this process works as follows:

  1. The client application pops a webview or otherwise redirects the user’s browser to the authorization service’s login screen, passing the following parameters:
    • response_type=code
    • client_id=your-client-id
    • redirect_uri=your-redirect-uri
    • scope=openid services:all
    • state=your-state
  2. The user enters their credentials and submits the form.
  3. The authorization service validates the credentials and redirects the user back to the redirect_uri, passing the following parameters:
    • code=your-code
    • state=your-state
  4. The client application exchanges the code for an access_token by calling the token endpoint, passing the following parameters:
    • grant_type=authorization_code
    • code=your-code
    • redirect_uri=your-redirect-uri
    • client_id=your-client-id
    • client_secret=your-client-secret

The login screen served can also be configured to present social authentication options based on any desired OIDC-compliant social providers such as Google, Facebook, etc. Regardless of whether the user signs in using credentials managed by our service or via an upstream social provider, the service will return an authorization code which can then be exchanged for an access_token.

A standard OAuth2 display of the login screen is done via the https://api.{region}.te2.io/oauth2/authorize endpoint. For example:

1
2
3
4
5
6
7
GET /oauth2/authorize?response_type=code\
&client_id={your client id}\
&scope={scopes desired}\
&state={any optional state you want n the redirect URL}\
&redirect_uri=https://{yourapplication.com/yourpath}\
&nonce={optional nonce}\ 
 HTTP/1.1

The initial display of the login screen requires the client_id be passed as a query parameter, but the client_secret does not need to be passed. The client_secret is only required during step 4 of OAuth2 interaction when the client exchanges the authorization code for an access_token. That can be done by a server-side component of the client application thus ensuring the secret is never exposed in a guest’s browser. This approach is recommended, as it keeps the client’s credentials secure.

Once an authorization code is acquired, the OAuth2 /token endpoint can be called to exchange the code for an access_token. For example:

1
2
3
4
5
6
curl --location --request POST 'https://api.{region}.te2.io/oauth2/token' \
--header 'accept: application/json' \
--header 'content-type: application/x-www-form-urlencoded' \
--header 'Authorization: Basic {client_id:client_secret}' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode 'code={authorization_code received via redirect}' \

Our service also supports the PKCE standard and recommend its use as a way to further ensure that the authorization code cannot be intercepted and used by a malicious actor.

Federated Sign-in via external OIDC-compliant service

Federated authorization is also supported in which our service delegates to an upstream OIDC-compliant service. This also works using the standard grant_type=authorization_code flow of OAuth2, with the login screen and authentication being handled by the upstream service. Once that is complete, our service issues an authorization_code based on that successful upstream authentication, and the rest of the normal OAuth2 flow applies.

Thus federated (or social) login scenarios proceed exactly as with the standard authorization_code OAuth2 grant just described. Ultimately, a standard accesso access_token is issued, but the user’s credentials are confirmed by the upstream service.

One other difference with this scenario is that the access_token that is acquired from the upstream service is embedded into the acccesso access_token as a JWT claim. This allows the application to access the upstream token if it is needed for any reason. For example, to call an API in external services which only accepts access_tokens from that upstream authorization service.

Federated Sign-in via token exchange

If the federated authentication flow is appropriate, but the user has already signed into the upstream service, then it would not be ideal to send them back through that flow to get an accesso access_token. As an alternative to doing so, a grant_type of grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer is supported which allows a client to exchange an idtoken acquired from the upstream service for an accesso access_token.

An example of the api call to acquire a token via this mechanism:

1
2
3
4
5
6
7
curl --location --request POST 'https://api.{region}.te2.io/oauth2/token' \
--header 'accept: application/json' \
--header 'content-type: application/x-www-form-urlencoded' \
--header 'Authorization: Basic {client_id:client_secret}' \
--data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer' \
--data-urlencode 'assertion={your id_token}' \
--data-urlencode 'scope={desired scopes}'

Parameters used above include:

Required properties

Optional properties

Required headers

accesso never records or logs the credential of an account when using federated authentication.

Anonymous Sign-in

For end users who do not wish to sign-up / sign-in, but who are working with a personal device (e.g. mobile or confirmed personal computer) an extension of the OAuth2.1 specification is supported: grant-type=urn:accesso:oauth2:grant-type:anonymous. This allows a client-facing application to request an access_token representing the unidentified guest user of the system.
When an anonymous token is requested, a new user is inserted into the database. The ID of the user is then returned along with a valid access token and valid refresh token. These can be stored on the device to “remember” that specific anonymous user so that over time, the anonymous user can continue to interact with the system as the same individual, and their activity will continue to be associated with that specific user profile.

An example anonymous sign-in request:

1
2
3
4
5
6
curl --location --request POST 'https://api.us.te2.io/oauth2/token' \
--header 'accept: application/json' \
--header 'content-type: application/x-www-form-urlencoded' \
--header 'Authorization: Basic {client_id:client_secret}' \
--data-urlencode 'grant_type=urn:accesso:oauth2:grant-type:anonymous'
--data-urlencode 'scope={desired scopes and may include openid}'

Parameters used above include:

Required properties

Optional properties

Required headers

Because requesting an anonymous token inserts a record into the database, you should request an anonymous token only when absolutely necessary. It is also important to use this only when the user is on a personal device, and not on a shared device. If the user is on a shared device, then the next user of that device will be able to access the system as the previous user.

Refresh

Access tokens issued by accesso expire after 24 hours. These tokens can be reissued, or refreshed, at any time by using the refresh token which was issued alongside the access token. Refreshing will issue both a new access token as well as a new refresh token.

1
2
3
4
5
6
curl --location --request POST 'https://api.{region}.te2.io/v4/tokens' \
--header 'accept: application/json' \
--header 'content-type: application/x-www-form-urlencoded' \
--header 'Authorization: Basic {apiKey}' \
--data-urlencode 'grant_type=refresh_token' \
--data-urlencode 'refresh_token={credential}'

Parameters used above include:

Required properties

Required headers

Unlike most OAuth2 systems, refresh tokens issued by accesso expire after 90 days. At refresh, the caller should always replace their held access and refresh tokens with the new tokens returned.

System Integration grant_types

For system-to-system integration use cases, the standard grant_type=client_credentials flow is supported, conforming to the OAuth2.1 specification specification.

The access_tokens which are issued for client_credentials are not associated with any specific end users of the system, and can (with the right scopes) give the associated application access to any data in the system. To help ensure that the client_credentials used in this way are harder to compromise, accesso has standardized on the use of JWT tokens for client authentication, as explained in https://datatracker.ietf.org/doc/html/rfc7523#section-2.2. This ensures that the client ‘secret’ can be a full RS256 private key which is never transmitted over the network. It is instead used to sign a JWT which is sent as the credential for the token request.

An example of the api call to acquire a token via this mechanism:

1
2
3
4
5
6
7
8
curl --location --request POST 'https://api.{region}.te2.io/oauth2/token' \
--header 'accept: application/json' \
--header 'content-type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'scope={desired scopes as space delimited list}' \
--data-urlencode 'client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer' \
--data-urlencode 'client_assertion={Ecoded JWT Token}' \
--data-urlencode 'client_id=happyland'

For more details on the construction of the JWT token, and this form of credential in general, see OAuth2 Service tokens guide.

Our implementation can also perform client_credentials grants based on credentials passed via basic auth headers, but this is not recommended for production use. Use of that model for production systems will be evaluated and approved on a case-by-case basis. Approval is typically based restricting the maximum scope of access which such credentials would provide if they were compromised, and the presence of other security measures which could prevent their misuse.

Best practices

  Do:

  Don’t: