Cylindo OpenID Connect

Cylindo allows third-party clients to authenticate their users on Cylindo’s behalf, using a standard OpenID Connect authentication flow. Cylindo then acts as an OIDC Provider and expose a number of user resources to the client.

As of now, the only supported authentication flow is the Authorization code flow with PKCE (Proof Key for Code Exchange). This flow is intended for third-party public clients (such as an SPA).

Authorization code flow with PKCE

Flow schema

cylindo-oidc.drawio (1).png

Endpoints documentation

The OIDC issuer is https://app.cylindo.com/api/rest/services/oidc. All OIDC endpoints are subroutes of this URL.

GET /authorize endpoint

This endpoint serves the authorization step of the authentication (see OAuth’s documentation). This endpoint is intended to be navigated to directly from a browser, by a redirection from the authorized client.

Query parameters

redirect_uriValid https URIIf the authorization is successful, /authorize will redirect back to this URI.
stateUnique string, less than 128 characters longServes as a CSRF token
scopeSpace-delimited string arrayOpenID scopes. See available scopes. Must contain openid
response_typeOAuth grant to executeOnly the grant code is supported
scopeSpace-delimited string arrayOpenID scopes. See available scopes. Must contain openid
client_idClient IDUnique client identifier, provided by Cylindo
code_challengePKCE Code challengeMust be derived from a PKCE Code verifier. See documentation
code_challenge_methodPKCE Code challenge methodOnly the method S256 is supported
nonceUnique string, less than 128 characters longServes as a token validation parameter.

Responses

307NoneSee redirected location
400application/jsonAn invalid_redirect_uri JSON error.
405application/jsonA method_not_allowed JSON error.

Redirected location

As long as the redirect_uri query parameter is valid, the /authorize endpoint will redirect to it.

If the authorization is successful, the redirected location will contain the following query parameters:

codeOpaque tokenThe authorization code intended to be exchanged in the /token request
stateUnique stringCSRF token, intended to be validated against the one used for the request call.

If the authorization failed, the redirected location will contain the following query parameters:

errorstringThe error code
error_descriptionstringA more detailed description of the error

Example cURL

 curl -H 'referer: https://client-app.com' \ 'https://app.cylindo.com/api/rest/services/oidc/authorize?redirect_uri=https%3A%2F%2Fclient-app.com%2Fredirect&scope=openid+email&state=EatlqjKZ&nonce=n3UdyX2s&code_challenge=xLO0yqyWUBp_EZxnw23V0JNFM3TkfRvB9WF-7ZwUJCY&code_challenge_method=S256&client_id=I2r6wGHFOlvptyUOLhD8cR57&response_type=code'

POST /token endpoint

This endpoint serves the authentication step of the flow (see OAuth’s documentation). This endpoint is intended to be requested directly from the public client itself (in this case, an SPA).

The POST /token request must have its parameter URI-encoded in its body, and its Content-Type must be application/x-www-form-urlencoded.

Body parameters

redirect_uriValid https URIMust be the same as in the /authorize call. Used for verification.
grant_typeOAuth grant typeMust be set to authorization_code
codeOpaque tokenThe authorization code obtained from the /authorize redirection
client_idClient IDUnique client identifier, provided by Cylindo
code_verifierPKCE Code verifierMust match the code_challenge passed to the /authorize request. See documentation

Responses

200application/jsonA token object
400application/jsonAn invalid_redirect_uri, bad_content_type, invalid_param, unsupported_grant_type or invalid_client_id JSON error.
401application/jsonAn invalid_origin or invalid_code JSON error.
404application/jsonA resource_not_found JSON error.
405application/jsonA method_not_allowed JSON error.

Example cURL

 curl 'https://app.cylindo.com/api/rest/services/oidc/token' \ -H 'content-type: application/x-www-form-urlencoded;charset=UTF-8' \ -H 'origin: https://client-app.com' \ --data-raw 'redirect_uri=https%3A%2F%2Fclient-app.com%2Fredirect&code=eyJh...F7Pw&code_verifier=XDY-0Oxq1V0S2IqwbZwPNiiQt2xEMO7BvBCjZbvkT38&grant_type=authorization_code&client_id=I2r6wGHFOlvptyUOLhD8cR57'

Implementation example

The following example uses the openid-client and jose libraries.

Flow initialization

This code is intended to be run when the user click a "Sign in with Cylindo" button.

import * as client from "openid-client"; // this code is adapted from this "Quick start" guide: // https://github.com/panva/openid-client?tab=readme-ov-file#authorization-code-flow const SESSION_STORAGE_KEY = "_cylindo_oauth_data"; const OIDC_SERVER_URL = "https://app.cylindo.com/api/rest/services/oidc"; const CLIENT_ID = "YOUR_CYLINDO_OAUTH_CLIENT_ID"; const REDIRECT_URI = "https://your-redirection-uri.com/callback"; const SCOPE = "openid email customer-id"; const setPersistentData = (data) => { const encoded = JSON.stringify(data); sessionStorage.setItem(SESSION_STORAGE_KEY, encoded); }; const getProviderConfig = () => client.discovery(new URL(OIDC_SERVER_URL), CLIENT_ID); const config = await getProviderConfig(); const code_verifier = client.randomPKCECodeVerifier(); const code_challenge = await client.calculatePKCECodeChallenge(code_verifier); const state = client.randomState(); const nonce = client.randomNonce(); const parameters = { redirect_uri: REDIRECT_URI, scope: SCOPE, state, nonce, code_challenge, code_challenge_method: "S256", }; const redirectTo = client.buildAuthorizationUrl(config, parameters); setPersistentData({ state, code_verifier, nonce }); window.open(redirectTo.href, "_self");

Code exchange

This code is intended to be run when the user lands back on the specified redirect_uri. It will read the relevant information directly from the query parameters of the current url.

import * as client from "openid-client"; import { createRemoteJWKSet, jwtVerify } from "jose"; // this code is adapted from this "Quick start" guide: // https://github.com/panva/openid-client?tab=readme-ov-file#authorization-code-flow const SESSION_STORAGE_KEY = "_cylindo_oauth_data"; const OIDC_SERVER_URL = "https://app.cylindo.com/api/rest/services/oidc"; const CLIENT_ID = "YOUR_CYLINDO_OAUTH_CLIENT_ID"; const REDIRECT_URI = "https://your-redirection-uri.com/callback"; const getPersistentData = () => { const encoded = sessionStorage.getItem(SESSION_STORAGE_KEY); return JSON.parse(encoded); }; const getProviderConfig = () => client.discovery(new URL(OIDC_SERVER_URL), CLIENT_ID); const { state, code_verifier, nonce } = getPersistentData(); const config = await getProviderConfig(); const currentUrl = new URL(window.location.href); const checks = { pkceCodeVerifier: code_verifier, expectedState: state, expectedNonce: nonce, }; const extraParams = { redirect_uri: REDIRECT_URI, }; const tokens = await client.authorizationCodeGrant( config, currentUrl, checks, extraParams ); const publicKey = createRemoteJWKSet( new URL(config.serverMetadata()["jwks_uri"]) ); await jwtVerify(tokens.id_token, publicKey); // Past this point, id_token has been verified successfully const claims = tokens.claims(); // The resources are located under claims console.log({ email: claims.email, customerId: claims["customer-id"], }); // Now that the user is properly authenticated, we can redirect to home or whichever link they were trying to access // Don't forget to replace this line with your framework's own router implementation of replaceState window.location.href = "/";

Available scopes

emailstringUser email
customer-idnumberUser’s Cylindo customer-id

Token object

token_typestringAlways set to Bearer
access_tokenOpaque tokenAn opaque access token, used for authenticated request with the cylindo API
id_tokenSigned JWT tokenThe ID token containing the claims defined by the scope. Its signature can be verified against the Cylindo public key
expires_innumberThe tokens expiration time, in seconds
scopeSpace-delimited string arrayThe OpenID scope requested in the /authorize request

JSON Errors

errorstringThe error code
error_descriptionstringA more detailed description of the error

Error codes

method_not_allowedThe given HTTP method is not allowed
invalid_redirect_uriThe given redirect_uri is not valid
invalid_paramThe given parameter is missing or invalid
param_too_largeThe given parameter is over the maximum limit of characters
unauthorized_redirect_uriThe given redirect_uri has not been authorized for the given client_id
invalid_client_idThe given client_id is not valid
unsupported_response_typeThe given response_type is unsupported
invalid_originThe request Origin or Referer header hasn’t been authorized for the given client_id
invalid_scopeThe given scope is not valid, or not accessible for the given client_id
bad_content_typeThe request Content-Type header is not supported
unsupported_grant_typeThe given grant_type is not supported
invalid_codeThe given code is not valid
resource_not_foundThe requested resource couldn’t be found

Cylindo public key

The RS256 public key can be found in a JWK format here. We recommend fetching it through jose's createRemoteJWKSet method, since it comes with cache handling.