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
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
name | data type | description |
---|---|---|
redirect_uri |
Valid https URI | If the authorization is successful, /authorize will redirect back to this URI. |
state |
Unique string, less than 128 characters long | Serves as a CSRF token |
scope |
Space-delimited string array | OpenID scopes. See available scopes. Must contain openid
|
response_type |
OAuth grant to execute | Only the grant code is supported |
scope |
Space-delimited string array | OpenID scopes. See available scopes. Must contain openid
|
client_id |
Client ID | Unique client identifier, provided by Cylindo |
code_challenge |
PKCE Code challenge | Must be derived from a PKCE Code verifier. See documentation |
code_challenge_method |
PKCE Code challenge method | Only the method S256 is supported |
nonce |
Unique string, less than 128 characters long | Serves as a token validation parameter. |
Responses
http code | content-type | Description |
---|---|---|
307 |
None | See redirected location |
400 |
application/json |
An invalid_redirect_uri JSON error. |
405 |
application/json |
A 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:
name | data type | Description |
---|---|---|
code |
Opaque token | The authorization code intended to be exchanged in the /token request |
state |
Unique string | CSRF 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:
name | data type | Description |
---|---|---|
error |
string | The error code |
error_description |
string | A 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
name | data type | description |
---|---|---|
redirect_uri |
Valid https URI | Must be the same as in the /authorize call. Used for verification. |
grant_type |
OAuth grant type | Must be set to authorization_code
|
code |
Opaque token | The authorization code obtained from the /authorize redirection
|
client_id |
Client ID | Unique client identifier, provided by Cylindo |
code_verifier |
PKCE Code verifier | Must match the code_challenge passed to the /authorize request. See documentation
|
Responses
http code | content-type | Description |
---|---|---|
200 |
application/json |
A token object |
400 |
application/json |
An invalid_redirect_uri , bad_content_type , invalid_param , unsupported_grant_type or invalid_client_id JSON error. |
401 |
application/json |
An invalid_origin or invalid_code JSON error. |
404 |
application/json |
A resource_not_found JSON error. |
405 |
application/json |
A 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
scope | data type | description |
---|---|---|
email |
string | User email |
customer-id |
number | User’s Cylindo customer-id |
Token object
key | data type | description |
---|---|---|
token_type |
string | Always set to Bearer
|
access_token |
Opaque token | An opaque access token, used for authenticated request with the cylindo API |
id_token |
Signed JWT token | The ID token containing the claims defined by the scope. Its signature can be verified against the Cylindo public key |
expires_in |
number | The tokens expiration time, in seconds |
scope |
Space-delimited string array | The OpenID scope requested in the /authorize request
|
JSON Errors
key | data type | description |
---|---|---|
error |
string | The error code |
error_description |
string | A more detailed description of the error |
Error codes
code | description |
---|---|
method_not_allowed |
The given HTTP method is not allowed |
invalid_redirect_uri |
The given redirect_uri is not valid |
invalid_param |
The given parameter is missing or invalid |
param_too_large |
The given parameter is over the maximum limit of characters |
unauthorized_redirect_uri |
The given redirect_uri has not been authorized for the given client_id |
invalid_client_id |
The given client_id is not valid |
unsupported_response_type |
The given response_type is unsupported |
invalid_origin |
The request Origin or Referer header hasn’t been authorized for the given client_id |
invalid_scope |
The given scope is not valid, or not accessible for the given client_id |
bad_content_type |
The request Content-Type header is not supported |
unsupported_grant_type |
The given grant_type is not supported |
invalid_code |
The given code is not valid |
resource_not_found |
The 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.