This documentation guides you through integrating CZL Connect into your app.
Have a question? Leave a message on the forumOAuth 2.0 Integration Guide
CZL Connect provides the standard OAuth 2.0 Authorization Code flow.
In the CZL Connect admin console, client authentication comes in two types:
secretnone
A secret client supports both standard parameter-passing methods at the protocol level: client_secret_basic and client_secret_post.
When the client authentication method is none, the authorization code exchange must use PKCE.
Scope
| Client type | Recommended authentication method |
|---|---|
| Server-side web app | secret |
| Desktop app | none + PKCE |
| Mobile app | none + PKCE |
| SPA / browser frontend | none + PKCE |
Server-side apps can store client_secret safely.
Desktop apps, mobile apps, and browser frontends cannot keep client_secret confidential, so they should use PKCE.
Authentication Methods
secret
A CZL Connect secret client supports both of the following standard OAuth 2.0 methods.
client_secret_basic
The client sends client_id and client_secret through HTTP Basic authentication.
Authorization: Basic BASE64(CLIENT_ID:CLIENT_SECRET)
client_secret_post
The client sends client_id and client_secret in an application/x-www-form-urlencoded request body.
client_id=YOUR_CLIENT_ID
client_secret=YOUR_CLIENT_SECRET
none
The client does not send client_secret.
PKCE is required in this mode.
PKCE
PKCE is for clients that cannot store client_secret securely.
The flow works as follows:
- The client generates a high-entropy random string,
code_verifier - The client computes
code_challenge = BASE64URL(SHA256(code_verifier)) - The authorization request includes
code_challengeandcode_challenge_method=S256 - The token request includes
code_verifier
CZL Connect currently supports only S256.
Endpoints
| Endpoint | URL |
|---|---|
| Authorization endpoint | https://connect.czl.net/oauth2/authorize |
| Token endpoint | https://connect.czl.net/api/oauth2/token |
| UserInfo endpoint | https://connect.czl.net/api/oauth2/userinfo |
| User emails endpoint | https://connect.czl.net/api/oauth2/user/emails |
Authorization Request
Basic Parameters
| Parameter | Required | Description |
|---|---|---|
response_type | Yes | Always code |
client_id | Yes | The client ID of your app |
redirect_uri | Yes | The redirect URI |
scope | No | The requested scope |
state | No | Client state parameter; always send it |
upstream_providers | No | Restricts the available upstream providers, comma-separated |
prompt | No | none requests silent authentication; see "Silent Authentication" |
code_challenge | Required with PKCE | The PKCE challenge |
code_challenge_method | Required with PKCE | Always S256 |
Traditional Client
https://connect.czl.net/oauth2/authorize?
response_type=code
&client_id=YOUR_CLIENT_ID
&redirect_uri=YOUR_REDIRECT_URI
&scope=read
&state=RANDOM_STATE_VALUE
Public Client + PKCE
https://connect.czl.net/oauth2/authorize?
response_type=code
&client_id=YOUR_CLIENT_ID
&redirect_uri=YOUR_REDIRECT_URI
&scope=read
&state=RANDOM_STATE_VALUE
&code_challenge=YOUR_CODE_CHALLENGE
&code_challenge_method=S256
Token Request
Exchange an Authorization Code for Tokens
secret client: client_secret_post
POST https://connect.czl.net/api/oauth2/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=AUTHORIZATION_CODE
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
&redirect_uri=YOUR_REDIRECT_URI
secret client: client_secret_basic
POST https://connect.czl.net/api/oauth2/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic BASE64(YOUR_CLIENT_ID:YOUR_CLIENT_SECRET)
grant_type=authorization_code
&code=AUTHORIZATION_CODE
&redirect_uri=YOUR_REDIRECT_URI
none + PKCE
POST https://connect.czl.net/api/oauth2/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=AUTHORIZATION_CODE
&client_id=YOUR_CLIENT_ID
&redirect_uri=YOUR_REDIRECT_URI
&code_verifier=YOUR_CODE_VERIFIER
Example Response
{
"access_token": "ACCESS_TOKEN",
"token_type": "bearer",
"refresh_token": "REFRESH_TOKEN",
"expires_in": 86400,
"scope": "read"
}
Refresh Token
secret client: client_secret_post
POST https://connect.czl.net/api/oauth2/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
&refresh_token=REFRESH_TOKEN
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
secret client: client_secret_basic
POST https://connect.czl.net/api/oauth2/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic BASE64(YOUR_CLIENT_ID:YOUR_CLIENT_SECRET)
grant_type=refresh_token
&refresh_token=REFRESH_TOKEN
none
POST https://connect.czl.net/api/oauth2/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
&refresh_token=REFRESH_TOKEN
&client_id=YOUR_CLIENT_ID
UserInfo
Request
GET https://connect.czl.net/api/oauth2/userinfo
Authorization: Bearer ACCESS_TOKEN
Response
{
"id": 1,
"username": "zhangsan",
"nickname": "Zhang San",
"email": "[email protected]",
"avatar": "https://example.com/avatar.png",
"groups": ["t0", "t1", "t2", "t3", "t4", "t5", "viewer", "admin"],
"upstreams": []
}
Fields
| Field | Description |
|---|---|
id | User ID |
username | Username |
nickname | User nickname |
email | User email |
avatar | User avatar URL |
groups | The user's groups as an array of strings, including the groups inherited from the user's level |
upstreams | Upstream accounts linked to the user |
User Emails Endpoint
Request
GET https://connect.czl.net/api/oauth2/user/emails
Authorization: Bearer ACCESS_TOKEN
Response
[
{
"email": "[email protected]",
"verified": true,
"primary": true,
"visibility": "public"
}
]
Restricting Upstream Providers
upstream_providers restricts which upstream providers are available during the authorization flow.
https://connect.czl.net/oauth2/authorize?
response_type=code
&client_id=YOUR_CLIENT_ID
&redirect_uri=YOUR_REDIRECT_URI
&scope=read
&state=RANDOM_STATE_VALUE
&upstream_providers=discourse,github
Common values:
discoursegithubgoogleqqwechat
Silent Authentication (prompt=none)
prompt=none restores the login automatically when a page opens. If the user is already signed in to CZL Connect and has previously authorized your app, the whole flow runs without interaction: the browser redirects briefly and returns to redirect_uri with a new authorization code. If silent authentication cannot complete, CZL Connect shows no login or consent page and redirects back immediately with error parameters. Your app then decides what to do next: stay signed out, or start a normal interactive authorization.
https://connect.czl.net/oauth2/authorize?
response_type=code
&client_id=YOUR_CLIENT_ID
&redirect_uri=YOUR_REDIRECT_URI
&scope=read
&state=RANDOM_STATE_VALUE
&prompt=none
On failure, the redirect to redirect_uri carries error, error_description, and the original state:
error | Meaning |
|---|---|
login_required | The user is not signed in to CZL Connect |
consent_required | The user has not yet authorized this app (the first authorization must go through the interactive flow) |
interaction_required | User interaction is required to continue (for example, the email is unverified or a required upstream account is not linked) |
access_denied | The user is not allowed to access this app, or the app's quota is full |
Requirements:
- Start the request with a top-level full-page navigation (
location.hrefor a server-side 302). Do not use a hidden iframe: the CZL Connect session cookie isSameSite=Lax, so it is not sent inside an iframe and silent authentication always returnslogin_required - Typical use: when your app's local session expires, redirect once with
prompt=none, and decide whether to show a "Sign in" button after you receivelogin_required - Do not start
prompt=noneagain right after an error redirect, to avoid a redirect loop prompt=nonecannot be combined with otherpromptvalues
Migrating to PKCE
To migrate from the traditional client_secret mode to PKCE, you usually only need to change the following:
- Change the client authentication method to
nonein the CZL Connect admin console - Add
code_challengeandcode_challenge_method=S256to the authorization request - Remove
client_secretfrom the token request - Add
code_verifierto the token request
The UserInfo endpoint, redirect URI, scope, and authorization endpoint stay the same.
Security Requirements
- Use
client_secretonly in server-side apps that can keep secrets confidential - Desktop apps, mobile apps, and browser frontends must not embed
client_secret - Public clients must use PKCE
- Always generate
stateon the client and verify it on return - Your app must still store
access_tokenandrefresh_tokensecurely - Only
S256is currently supported