OAuth Applications

An OAuth application is the record that lets another program obtain access tokens from CityScreen Deals. CityScreen Deals is its own authorization server, so the application talks to your organization’s address directly and no outside identity provider is involved. Two flows are available. An application that works on its own behalf, such as a nightly synchronization job, uses client credentials. An application that works for an employee who allowed it, such as a dashboard or a web application with single sign-on, uses the authorization code flow with PKCE.

This function is licensed. If your organization’s license does not include it, contact CityScreen. The license entry is called "OAuth applications". Without it every OAuth endpoint on this page answers 404.

If the address of your organization is not an https address, the two discovery documents (see Endpoints) answer 503 Service Unavailable, because clients would send codes and tokens in clear text. Contact CityScreen in that case.

An employee who needs a token for a script of their own creates a personal access token, see Access Tokens and App Passwords.

Endpoints

All endpoints are on the address of your organization, for example https://deals.example.com. The endpoints are at the root of the address, and only the REST API that the tokens open is under /api.

Address Method Purpose

/oauth/authorize

GET

Starts the authorization code flow in the employee’s browser.

/oauth/token

POST

Issues access tokens for all grants and refreshes them.

/oauth/revoke

POST

Revokes a token.

/oauth/register

POST

Lets an application register itself, see Applications that register themselves.

/oauth/userinfo

GET, POST

Returns the claims of the employee for a token with the scope openid, see Single Sign-On with OpenID Connect.

/oauth/jwks

GET

Publishes the keys that verify ID tokens.

/oauth/end-session

GET, POST

Lets an application ask for the employee’s sign-out.

/.well-known/openid-configuration and /.well-known/oauth-authorization-server

GET

Describe the server and all of the above for client libraries.

Where applications are managed

Open Employees and then the OAuth applications tab. Viewing the tab needs the "OAuth applications" View permission, see Roles & Permissions.

The block at the top shows the "Issuer" and the "Discovery URL" of your organization, with a Copy discovery URL button. The table has these columns:

Column Content

Name

The name and description of the application, and "Registered by" with the employee who registered it.

Client ID

The client ID, with "Secret prefix", the first characters of the client secret.

Roles

The roles a client credentials application acts with.

Last used

When the application last obtained or used a token, or "Never".

Authorized employees

How many employees allowed the application.

The buttons Create application, Edit, Regenerate secret, Delete and Rotate signing key appear when your role holds the matching permission. Rotate signing key is described in Single Sign-On with OpenID Connect.

Registering an Application

  1. Open Employees > OAuth applications and press Create application.

  2. Fill in the form described below.

  3. Press Create.

  4. Copy the client secret from the dialog "OAuth application created" before you press Done.

Registering needs the "OAuth applications" Create permission at organization scope. Before you register an application for someone who asks, confirm who is asking and which system will hold the client secret. A client credentials application acts with its roles without any employee.

Field What to enter

Name

Required, up to 255 characters.

Description

Up to 2000 characters. Say what the application does and who maintains it.

Grant type

Client credentials (the default), Authorization code, or both. The choice cannot be changed after creation.

Issue refresh tokens

Shown under Authorization code and on by default; the application then receives a refresh token with the access token.

Public client (no client secret)

Shown under Authorization code; a public client has no client secret and proves itself with PKCE. It cannot use client credentials, and this choice cannot be changed after creation. Choose it for applications that cannot keep a secret, such as single-page or mobile applications.

Redirect URIs

Required for the authorization code grant. Up to 10 addresses of up to 2000 characters each, absolute, with a scheme and without a fragment (#). The address in the authorization request must match one of them character by character, so a trailing slash, letter case, port or query makes a different address.

Allowed scopes

Shown under Authorization code; the scopes the application may ask for, see Scopes. openid, profile, email and groups are ticked by default; api is ticked by an administrator who wants the application to call the REST API. A request for any other scope is refused.

Back-channel logout URI

Optional for any grant type; an https address that receives a message when the employee signs out, see Single Sign-On with OpenID Connect.

Post-logout redirect URIs

Optional, up to 10 addresses where the browser may be sent after the application asks for a sign-out.

Roles

Shown under Client credentials; the roles whose permissions the application acts with, see Client credentials.

Name, description, roles, redirect URIs, allowed scopes and the logout addresses can be changed later with Edit. Grant types and the public or confidential choice stay as created.

The Client Secret

The dialog "OAuth application created" shows the client ID, the client secret and the address of the token endpoint. The client ID never changes. The client secret starts with csd_ocs_. The dialog warns: "Copy the client secret now. It is shown only once and cannot be shown again. Treat it like a password." Afterwards the table shows only its prefix.

A public client has no client secret.

Regenerate secret stops the current secret at once and shows a new one. Access tokens already issued stay valid until they expire. Delete stops every token of the application at once.

Regenerate secret is not offered for a public client.

Client Credentials

An application with the client credentials grant acts as a service account with the roles assigned to it in the Roles field. It holds none of the rights of the employee who registered it, and it keeps working when that employee leaves. A change to its roles applies at the next call.

An operation that needs an employee is refused to such an application with a 403. Reading the current user and creating a record that must have an employee as its author are examples.

To obtain a token, send POST /oauth/token:

  • The body is form-urlencoded. Parameters in the query string are refused.

  • grant_type=client_credentials.

  • The application authenticates with HTTP Basic, or with client_id and client_secret in the body.

  • A scope is ignored: the roles decide what the token may do.

  • An optional resource names the address the token is meant for, see the table in Authorization code with PKCE.

The answer holds access_token (starting with csd_oat_), token_type (Bearer) and expires_in in seconds. The grant issues no refresh token. The application asks for a new token when the old one expires.

Errors are invalid_client (401), unauthorized_client for an application that does not hold the grant, invalid_request and invalid_target. When an answer carries Retry-After, the application waits for the period it gives before it asks again.

Example 1: obtain a token and call the API
# 1. Get an access token with the client ID and secret of the application
curl -s https://deals.example.com/oauth/token \
  -u 'app_42:csd_ocs_REPLACE_WITH_THE_CLIENT_SECRET' \
  -d grant_type=client_credentials

The answer looks like this, with the token shortened:

{"access_token":"csd_oat_REPLACE","token_type":"Bearer","expires_in":7200}
# 2. Call the REST API with the token; the roles of the application decide what is returned
curl -s https://deals.example.com/api/clients/deals \
  -H 'Authorization: Bearer csd_oat_REPLACE'

The second call returns the deals that the application’s roles allow it to read. An application whose roles grant nothing on deals receives 403. 7200 seconds is the default lifetime of 120 minutes, see Token lifetime.

Authorization Code with PKCE

The flow follows RFC 6749, section 4.1, with PKCE (RFC 7636). The employee signs in, including two-step verification when the account has it, and allows the application.

Step 1: authorization request

GET /oauth/authorize with these parameters:

Parameter Value

response_type

code.

client_id

The client ID of the application.

redirect_uri

One of the registered redirect URIs, character for character.

scope

The scopes, separated by spaces, at most 20. Send it explicitly: a registered application that sends none receives a token without scopes.

state

Optional, up to 2048 characters. It comes back unchanged.

nonce

Optional, up to 512 characters. It ends up in the ID token.

code_challenge and code_challenge_method

The PKCE challenge and the method S256, which is the only method. Required of a public client and optional for a confidential one. A confidential client that sends a challenge must send the verifier when it exchanges the code.

resource

Optional. The address of a resource the server serves, for example https://deals.example.com/api. Any other value is refused with invalid_target. The tokens are then meant for that resource, and a later request for them must name the same resource or none.

prompt

Optional: none, login, consent or select_account, see The consent page.

An error found before the client and its redirect address are verified is shown on a page of CityScreen Deals and is never sent to the application. Any other error returns the browser to the redirect address with error, state and iss.

Step 2: the browser returns with a code

After the employee allows the application, the browser returns to the redirect address with code, state and iss, the address of the issuer. The code starts with csd_oac_, lives 5 minutes and works once. Presenting a used code again revokes the tokens that were issued from it.

Step 3: exchange the code

POST /oauth/token, form-urlencoded, with:

  • grant_type=authorization_code;

  • code;

  • redirect_uri, the same address as in the authorization request;

  • code_verifier, when the authorization request had a challenge;

  • a confidential client authenticates with HTTP Basic or with client_id and client_secret in the body, and a public client sends client_id only.

The answer holds access_token, token_type, expires_in and scope. It also holds refresh_token (starting with csd_ort_) when the application has Issue refresh tokens, and id_token when the scopes include openid. A code that is wrong, used, expired or issued to another client, a wrong verifier and a wrong redirect address all give the same invalid_grant (400). A confidential client with a wrong secret gets invalid_client (401) instead. When the answer carries Retry-After, the application waits for the period it gives before it tries again.

Step 4: refresh

POST /oauth/token with grant_type=refresh_token and refresh_token, authenticated as in step 3. A scope in the request is ignored: the new tokens keep the scopes the employee allowed.

A public client receives a new refresh token with every refresh, and the old one stops working. Presenting an old refresh token again revokes the whole family of tokens. A confidential client keeps its refresh token and receives a new access token.

A refresh token has no expiry date. It stops when it is revoked, when the employee revokes the authorization, when the application is deleted or when the employee is deactivated.

Example 2: a public client with PKCE
# 1. The application creates a verifier and its S256 challenge
verifier=$(openssl rand -base64 48 | tr '+/' '-_' | tr -d '=\n')
challenge=$(printf '%s' "$verifier" | openssl dgst -sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=')

# 2. It opens this address in the employee's browser (line breaks added for reading)
#    https://deals.example.com/oauth/authorize
#      ?response_type=code
#      &client_id=app_43
#      &redirect_uri=https%3A%2F%2Ftools.example.com%2Fcallback
#      &scope=api
#      &state=af0ifjsldkj
#      &code_challenge=<the value of $challenge>
#      &code_challenge_method=S256

# 3. The browser comes back to
#    https://tools.example.com/callback?code=csd_oac_REPLACE&state=af0ifjsldkj&iss=https%3A%2F%2Fdeals.example.com

# 4. The application exchanges the code; a public client sends no secret
curl -s https://deals.example.com/oauth/token \
  -d grant_type=authorization_code \
  -d code='csd_oac_REPLACE' \
  --data-urlencode redirect_uri='https://tools.example.com/callback' \
  -d code_verifier="$verifier" \
  -d client_id=app_43

The answer looks like this, with the tokens shortened:

{"access_token":"csd_oat_REPLACE","token_type":"Bearer","expires_in":7200,"scope":"api","refresh_token":"csd_ort_REPLACE"}
# 5. Call the REST API with the employee's access rights
curl -s https://deals.example.com/api/clients/deals \
  -H 'Authorization: Bearer csd_oat_REPLACE'

# 6. When the access token expires, refresh it
curl -s https://deals.example.com/oauth/token \
  -d grant_type=refresh_token \
  -d refresh_token='csd_ort_REPLACE' \
  -d client_id=app_43

The call in step 5 returns the deals that the employee may read. The new answer in step 6 carries a new refresh token that replaces the old one.

Scopes

Scope What it allows

openid

Makes the request an OpenID Connect request. The answer carries an ID token, and the token opens the UserInfo endpoint.

profile

Adds name, preferred_username and locale to the ID token and to UserInfo.

email

Adds email.

groups

Adds groups and group_names, see Single Sign-On with OpenID Connect.

api

Lets the token call the REST API with the employee’s access rights.

A token issued for an employee without the scope api reaches only UserInfo. The REST API answers it with 403, the challenge Bearer error="insufficient_scope", scope="api" and the message "The OAuth access token was issued without the api scope, which the CSD API requires. Authorize the application again and allow it to act in CSD." A token can do only what the employee’s own permissions allow at each call, and its scopes limit it further.

Client credentials tokens take no scopes. A request for any other scope, including offline_access, is refused.

The employee signs in and presses Allow once. With the scope api the application then calls the REST API with that employee’s own access rights, as they are at the time of each call. The employee sees every application they allowed under Account > Authorizations, and withdraws the permission there with Revoke.

The sign-in form is titled "Sign in to {{instance}} to continue to {{application}}", with the names of your organization and the application filled in. It runs the same steps as web sign-in, see Signing In and Out. The consent page is titled "{{application}} asks for access" and lists what the application will be able to do and which details about the employee it will receive. It offers Allow and Deny. Deny sends access_denied to the application. An application that registered itself carries the badge "Unverified" and a warning.

The page is skipped for an application the employee already allowed with the same scopes. The parameter prompt changes that:

prompt Effect

consent

The page is shown again.

login or select_account

The employee signs in again on the page.

none

No page is shown. The application receives a code if the employee is signed in and allowed it before, and otherwise login_required or consent_required.

Revoking in Account > Authorizations stops the application’s access and refresh tokens at once. The application can ask the employee again. An application with a back-channel logout address is told, see Single Sign-On with OpenID Connect.

Applications That Register Themselves

Some applications cannot be registered by an administrator in advance. They register with POST /oauth/register, a JSON request following RFC 7591, and need no credential.

Member Rule

redirect_uris

Required, 1 to 10 addresses without fragment or user information, each with at most 2000 characters. Each is an https address, an http address on a loopback host (127.0.0.1, [::1] or localhost) with a port, or an address with a private scheme in reverse-domain form such as com.example.app:/callback.

grant_types

Optional; only authorization_code and refresh_token are accepted, and both are registered.

response_types

Optional; the only accepted value is code.

client_name, software_id, software_version

Optional, up to 255 characters.

client_uri, logo_uri

Optional, absolute http or https addresses.

Logout members

A self-registering application has no logout address, so a request that contains logout members is refused.

The answer is 201 with a client_id of the form dcr_<22 characters> and token_endpoint_auth_method set to none. There is no client secret, so the client is public and uses PKCE. Without a scope in its authorization request the client receives api. Errors are invalid_redirect_uri, invalid_client_metadata and 429 Too Many Requests.

A registration creates nothing in your organization. A registration that no employee allows expires. When an employee allows it, a record of the application is created and owned by that employee. The record is a public client with the authorization code and refresh token grants, every known scope allowed, no roles and no secret. Creating it needs the "OAuth applications" Create permission at Own scope, and it counts toward the license limit of OAuth applications. An employee without the permission sees "Your account is not allowed to connect applications. Ask an administrator of your organization." When the limit is reached the employee sees "The limit of connected applications of your organization has been reached. Ask an administrator of your organization."

Administrators see these records in the table and can delete them. Their settings cannot be changed: "This application registered itself, so its settings cannot be changed here. You can delete it." When an employee revokes such an application under Authorizations, the record is deleted with it, which needs the Delete permission at Own scope.

Revoking Tokens

POST /oauth/revoke with the form parameter token revokes an access token or a refresh token. Revoking a refresh token also revokes its whole family: the access tokens issued from it and the refresh tokens that replaced it.

A client_id, when sent, must be the client the token was issued to, otherwise nothing is revoked. The answer is an empty 200 for every token, including one that is unknown or already revoked, so the answer does not reveal which. A request without a token gets 400 invalid_request.

Other ways to end tokens:

  • an employee revokes an authorization under Account > Authorizations;

  • an administrator deletes the application;

  • an employee is deactivated, which deletes every OAuth token issued for them.

Token Lifetime

An administrator sets "Access token lifetime for OAuth applications (minutes)" in Settings > General. The value is from 5 to 1440 minutes and defaults to 120. A change applies to tokens issued after saving. The ID token lives as long as the access token it is issued with.

Permissions

Permission Use

"OAuth applications" View, Create, Edit and Delete at organization scope

Administrators who register and manage applications.

"OAuth applications" Create and Delete at Own scope

Employees who connect applications that register themselves.

"Rotate signing key"

Administrators who rotate the signing key.

Roles are edited in Roles & Permissions.