OAuth / OIDC

Customer Authentication

Use Tiquo as an OAuth 2.0 Authorization Server and OpenID Connect Provider

Customer authentication with OAuth 2.0 / OIDC

Tiquo acts as an OAuth 2.0 Authorization Server and OpenID Connect (OIDC) Provider. Your application can sign customers in with the standard authorization code flow. Customers sign in on Tiquo's branded sign-in page with a one-time code sent to their email.

After sign-in, Tiquo issues JWT access tokens that work with the Customer API, just like tokens from the DOM Package. The ID token and access token also carry the customer's profile and current memberships.

Endpoints

Most OIDC libraries configure themselves from the issuer's discovery document, https://auth.tiquo.app/.well-known/openid-configuration. Use the issuer https://auth.tiquo.app.

EndpointURLDescription
Discoveryhttps://auth.tiquo.app/.well-known/openid-configurationOIDC discovery metadata (also served at https://edge.tiquo.app/api/auth/.well-known/openid-configuration)
OAuth Metadatahttps://edge.tiquo.app/api/auth/.well-known/oauth-authorization-serverOAuth 2.0 server metadata
Authorizationhttps://edge.tiquo.app/api/auth/oauth2/authorizeStart the authorization flow
Tokenhttps://edge.tiquo.app/api/auth/oauth2/tokenExchange a code or refresh token for tokens
UserInfohttps://edge.tiquo.app/api/auth/oauth2/userinfoGet the customer's current claims
Revocationhttps://edge.tiquo.app/api/auth/oauth2/revokeRevoke a refresh token
End sessionhttps://edge.tiquo.app/api/auth/oauth2/logoutSign out of Tiquo
JWKShttps://edge.tiquo.app/api/auth/jwksPublic keys for token verification

Setup

1. Register an OAuth client

OAuth clients are registered through the Tiquo dashboard. Dynamic client registration is not available.

OAuth client management is unavailable in the demo platform. The Add Client button is disabled in Preview Mode.

  1. Go to Settings > IDP.
  2. In OAuth Clients, select Add Client.
  3. Enter a name and the exact redirect URI or URIs used by your application, for example https://yourapp.com/api/auth/callback/tiquo. Redirect URIs must match exactly.
  4. Copy the client_id and client_secret. Store the secret on your server; it is needed for every token request.

The OAuth client belongs to your organization, and that organization is the one customers sign in to. You can choose a theme for the client so the sign-in page and the email with the sign-in code carry your branding.

2. Authorization flow

Redirect the customer to the authorization endpoint:

https://edge.tiquo.app/api/auth/oauth2/authorize?
  client_id=your_client_id
  &response_type=code
  &redirect_uri=https://yourapp.com/callback
  &scope=openid profile email offline_access
  &state=random_state_value
  &nonce=random_nonce_value
  &code_challenge=your_code_challenge
  &code_challenge_method=S256
ParameterRequiredDescription
client_idYesYour OAuth client ID
response_typeYesMust be code. Other values return unsupported_response_type.
redirect_uriYesOne of the client's registered redirect URIs
scopeNoSpace-separated scopes. Include offline_access when the application needs a refresh token. Unsupported scopes are ignored.
stateRecommendedRandom value your application checks on return, for CSRF protection
nonceRecommendedRandom value that Tiquo copies into the ID token, so your application can bind the token to this request
code_challengeRecommendedPKCE code challenge (S256 method)
code_challenge_methodRecommendedMust be S256 when using PKCE
promptNologin asks for a new sign-in even when the customer is already signed in to Tiquo. none never shows the sign-in page: without a Tiquo session, Tiquo returns error=login_required.
max_ageNoMaximum age of the customer's sign-in, in seconds. An older sign-in asks the customer to sign in again.

The customer signs in with their email and a one-time code. Tiquo then redirects back to your redirect_uri with an authorization code:

https://yourapp.com/callback?code=auth_code_here&state=random_state_value

Matching the customer profile

Tiquo links the sign-in to the customer profile in your organization that has the customer's email address, whether it is the profile's primary email or one of its other emails. If a deleted profile has the address, Tiquo restores it. If no profile has the address, Tiquo creates one. The tokens carry the customer from their first sign-in.

Errors returned to your application

Once the redirect URI is verified, errors are returned to it as error and error_description query parameters, with your state:

ErrorWhen
access_deniedThe email address is on more than one customer profile, for example after an import, so it can't be used to sign in until the profiles are merged or the address is removed.
login_requiredprompt=none was requested and the customer has no Tiquo sign-in session.
unsupported_response_typeresponse_type is not code.
invalid_requestA parameter is invalid, for example prompt=none combined with other values, or a malformed max_age or nonce.

An unknown client_id or a redirect_uri that is not registered returns a 400 JSON error instead of a redirect.

3. Exchange code for tokens

Exchange the authorization code for tokens from your server. OAuth clients created in the dashboard authenticate with HTTP Basic authentication (client_secret_basic): send the client ID and secret in the Authorization header, not in the request body.

curl -X POST "https://edge.tiquo.app/api/auth/oauth2/token" \
  -u "your_client_id:your_client_secret" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=auth_code_here" \
  -d "redirect_uri=https://yourapp.com/callback" \
  -d "code_verifier=your_code_verifier"

Response:

{
  "access_token": "eyJhbGciOiJSUzI1NiJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "rt_xxx...",
  "id_token": "eyJhbGciOiJSUzI1NiJ9...",
  "scope": "openid profile email offline_access"
}

Authorization codes are valid for 10 minutes and can be used once.

4. Use the access token

The access token works with the authenticated Customer API endpoints:

curl -X GET "https://edge.tiquo.app/api/client/v1/memberships" \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiJ9..."

Supported grant types

Grant TypeDescription
authorization_codeStandard authorization code flow (with optional PKCE)
refresh_tokenExchange a refresh token for new tokens

Scopes

ScopeDescription
openidRequired for OIDC. Returns an ID token.
profileProfile information such as the customer's name
emailThe customer's email address
offline_accessRequests a refresh token so the application can renew access after the access token expires

PKCE support

Tiquo supports Proof Key for Code Exchange (PKCE) using the S256 challenge method, and recommends it for every client.

  1. Generate a random code_verifier (43-128 characters, URL-safe)
  2. Compute the code_challenge as the Base64-URL-encoded SHA-256 hash of the verifier
  3. Include code_challenge and code_challenge_method=S256 in the authorization request
  4. Include the original code_verifier in the token exchange request

OAuth clients created in the dashboard are confidential clients with a client secret. Keep the secret on a server and exchange codes there. Never embed it in a mobile app or browser code.

Token refresh

Refresh tokens are issued only when the original authorization request includes the offline_access scope. They last as long as your organization's session duration, set under Settings > Website SDK > Session Duration (30 days by default), measured from the original sign-in.

To get new tokens using a refresh token:

curl -X POST "https://edge.tiquo.app/api/auth/oauth2/token" \
  -u "your_client_id:your_client_secret" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "refresh_token=rt_xxx..."

Refresh tokens are rotated on each use: always store and use the new refresh token from the response. A replaced refresh token keeps working for 60 seconds, so two requests that refresh at the same moment don't sign the customer out, and is refused after that. A refresh token can only be used by the client it was issued to.

Each refreshed access token carries the customer's claims and memberships as they are now, so a cancelled membership shows up within the hour without a new sign-in. The refresh response does not include a new ID token.

Membership claims

The ID token, the access token and the UserInfo response include the customer's current memberships:

{
  "has_active_membership": true,
  "memberships": [
    {
      "id": "k17membership0000000000000000000",
      "source": "tiquo",
      "plan_id": "k17plan000000000000000000000000",
      "name": "Gold",
      "status": "active",
      "payment_status": "paid",
      "starts_at": 1749945600,
      "renews_at": 1794700800
    },
    {
      "id": "ext_hubspot_456789",
      "source": "hubspot",
      "name": "Club Membership",
      "status": "active",
      "payment_status": "past_due",
      "starts_at": 1710028800
    }
  ]
}
ClaimDescription
has_active_membershiptrue while the customer has at least one current membership
membershipsUp to 10 current memberships
memberships[].idMembership ID. IDs of memberships from a connected system start with ext_.
memberships[].sourcetiquo, or the connected system that holds the membership, such as hubspot
memberships[].plan_idThe membership plan, for memberships sold in Tiquo only
memberships[].nameMembership name
memberships[].statusMembership status, for example active
memberships[].payment_statusPayment status. Connected-system memberships use past_due for an unpaid invoice.
memberships[].starts_at, renews_at, ends_atUnix timestamps in seconds

A membership is current while its status is active. A HubSpot subscription with an unpaid invoice stays active until HubSpot marks it cancelled. For every membership, including ended ones and prices, call GET /api/client/v1/memberships with the access token.

To restrict an area of your application to members, check has_active_membership on your server. Refreshing the access token re-reads it.

UserInfo endpoint

Retrieve the customer's current claims. UserInfo reads the customer profile and memberships at the time of the request, so it reflects changes made since sign-in:

curl -X GET "https://edge.tiquo.app/api/auth/oauth2/userinfo" \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiJ9..."

Response:

{
  "sub": "user_abc123",
  "email": "customer@example.com",
  "email_verified": true,
  "name": "Ada Member",
  "given_name": "Ada",
  "family_name": "Member",
  "org_customer_id": "cust_456",
  "customer_number": "CUST-000001",
  "organization_id": "org_xyz",
  "customer_status": "active",
  "has_active_membership": true,
  "memberships": []
}

email_verified is always true: every sign-in proves the email address with a one-time code.

Revoking tokens

Revoke a refresh token when the customer signs out of your application (RFC 7009). Authenticate the client the same way as the token endpoint:

curl -X POST "https://edge.tiquo.app/api/auth/oauth2/revoke" \
  -u "your_client_id:your_client_secret" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "token=rt_xxx..." \
  -d "token_type_hint=refresh_token"

The endpoint returns 200 whether or not the token was found. A client can only revoke refresh tokens issued to it. A revoked refresh token stops working immediately. Access tokens cannot be revoked; they expire within an hour.

Signing out

To end the customer's Tiquo sign-in session, redirect the browser to the end session endpoint (OIDC RP-Initiated Logout). The next sign-in then asks for a new one-time code.

https://edge.tiquo.app/api/auth/oauth2/logout?
  client_id=your_client_id
  &post_logout_redirect_uri=https://yourapp.com/
  &state=random_state_value
ParameterDescription
client_id or id_token_hintIdentifies your client. An expired ID token is accepted as a hint.
post_logout_redirect_uriWhere to return after sign-out. It must be on the same origin as one of the client's redirect URIs; otherwise Tiquo shows a signed-out page.
stateReturned to post_logout_redirect_uri unchanged

A complete sign-out clears your application's session, revokes its refresh token, then redirects to this endpoint.

JWKS (token verification)

Tiquo signs tokens with RS256. The public keys are published at the JWKS endpoint:

curl "https://edge.tiquo.app/api/auth/jwks"

Select the key by the kid in the token header. Tiquo rotates its signing key every 90 days. The new key is published as soon as it is used, and the previous key stays published for a week, longer than any token it signed is valid. Most JWT libraries refresh the key set automatically when they see a new kid.

Tokens your server receives directly from the token endpoint over HTTPS can be trusted without checking their signature, as OIDC allows. Verify the signature, issuer, audience and expiry of any token that reaches you another way, for example an access token your API receives from your mobile app.

Token details

TokenAlgorithmLifetimeIssuerAudience
Access tokenRS2561 hourhttps://auth.tiquo.apptiquo-client-api
ID tokenRS25610 hourshttps://auth.tiquo.appYour client ID
Refresh tokenN/AYour organization's session duration (30 days by default)N/AN/A

Access token claims

{
  "sub": "user_abc123",
  "email": "customer@example.com",
  "token_source": "oauth",
  "organization_id": "org_xyz",
  "org_customer_id": "cust_456",
  "customer_number": "CUST-000001",
  "given_name": "Ada",
  "family_name": "Member",
  "display_name": "Ada Member",
  "name": "Ada Member",
  "customer_status": "active",
  "has_active_membership": true,
  "memberships": [],
  "client_id": "your_client_id",
  "scope": "openid profile email offline_access",
  "iss": "https://auth.tiquo.app",
  "aud": "tiquo-client-api",
  "exp": 1706580000,
  "iat": 1706576400
}

OAuth access tokens include client_id, scope, the customer's name fields and the membership claims, which are not present in DOM Package tokens. Spend totals and payment details are not included; read them from the Customer API.

ID token claims

The ID token is returned when the openid scope is requested. It contains sub, email, email_verified, name, given_name, family_name, display_name, picture, org_customer_id, customer_number, customer_status, organization_id, has_active_membership and memberships. It also contains the nonce from the authorization request and auth_time, when the customer entered their sign-in code.

Customer fields are present once the sign-in is linked to an active customer profile. Treat the claims in the token or UserInfo response as the authoritative claim set for the signed-in customer.

Native mobile app integration

Run the sign-in in the system browser and exchange the code on your server, which holds the client secret. Generate state, nonce and the PKCE verifier on your server or in the app, and send the returned code to your server.

iOS (Swift)

Use ASWebAuthenticationSession to run the sign-in in iOS:

import AuthenticationServices

let authURL = URL(string: "https://edge.tiquo.app/api/auth/oauth2/authorize?client_id=\(clientId)&response_type=code&redirect_uri=myapp://callback&scope=openid%20profile%20email%20offline_access&state=\(state)&nonce=\(nonce)&code_challenge=\(codeChallenge)&code_challenge_method=S256")!

let session = ASWebAuthenticationSession(
    url: authURL,
    callbackURLScheme: "myapp"
) { callbackURL, error in
    guard let url = callbackURL,
          let code = URLComponents(url: url, resolvingAgainstBaseURL: false)?
              .queryItems?.first(where: { $0.name == "code" })?.value
    else { return }

    // Send the code to your server, which exchanges it for tokens
    sendCodeToServer(code: code, codeVerifier: codeVerifier)
}

session.presentationContextProvider = self
session.start()

Android (Kotlin)

Use Custom Tabs or AppAuth for the sign-in:

val authUri = Uri.parse("https://edge.tiquo.app/api/auth/oauth2/authorize")
    .buildUpon()
    .appendQueryParameter("client_id", clientId)
    .appendQueryParameter("response_type", "code")
    .appendQueryParameter("redirect_uri", "myapp://callback")
    .appendQueryParameter("scope", "openid profile email offline_access")
    .appendQueryParameter("state", state)
    .appendQueryParameter("nonce", nonce)
    .appendQueryParameter("code_challenge", codeChallenge)
    .appendQueryParameter("code_challenge_method", "S256")
    .build()

val customTabsIntent = CustomTabsIntent.Builder().build()
customTabsIntent.launchUrl(context, authUri)

How it works with the customer API

The OAuth flow issues the same type of JWT access tokens as the DOM Package. Once you have an access token, you use it with the Customer API in exactly the same way:

  1. Register an OAuth client under Settings > IDP > OAuth Clients and configure its redirect URIs
  2. Redirect the customer to the authorization endpoint
  3. The customer signs in with a one-time code on the Tiquo sign-in page, and Tiquo links them to their customer profile
  4. Tiquo redirects back to your app with an authorization code
  5. Your server exchanges the code for an access token, ID token, and, when offline_access was requested, a refresh token
  6. Use the access token with Customer API endpoints (/profile, /memberships, /orders, /bookings, /enquiries)
  7. When the access token expires, use the refresh token to get new tokens
  8. When the customer signs out, revoke the refresh token and redirect to the end session endpoint

The key difference from the DOM Package is that OAuth uses a redirect-based flow, while the DOM Package handles everything in-page. Choose based on your platform and requirements.

On this page