OpenID Connect Protocol
StarEcho implements the standard OpenID Connect 1.0 protocol (based on OAuth 2.0 Authorization Code Flow), enabling third-party applications to integrate with the StarEcho account system for "Sign in with StarEcho" functionality.
Overview
| Item | Description |
|---|---|
| Protocol | OpenID Connect 1.0 (Authorization Code Flow) |
| Signing Algorithm | RS256 |
| Supported Scopes | openid, profile, email |
| Supported Grant Types | authorization_code, refresh_token |
| Client Authentication | client_secret_post, client_secret_basic, PKCE |
| Discovery Endpoint | https://api.starecho.net/.well-known/openid-configuration |
Authorization Code Flow Sequence Diagram
Integration Guide
1. Create an Application
Sign in to StarEcho, navigate to Profile → Developer → Developer Apps, and click "Create App":
| Field | Description |
|---|---|
| App Name | Name displayed to users on the authorization page |
| App Description | Brief description of your application |
| Account Usage | Explain how your app will use StarEcho accounts |
| Redirect URIs | OAuth redirect URI after authorization, must be https:// |
| Web Origins (CORS) | Exact origins allowed to call StarEcho APIs from a browser, for example https://app.example.com, without a path |
| Mobile URL Schemes | Optional, for native mobile app callbacks, e.g. starecho, full callback: starecho://callback |
| Homepage URL | Optional, your application's website |
| App Icon | Optional, JPG/PNG, 256×256 recommended |
Desktop Loopback:
http://127.0.0.1:{port}/callbackandhttp://localhost:{port}/callback(port 1024-65535) are automatically allowed without configuration. This follows RFC 8252 — loopback traffic never leaves the local machine.
After submission, you need to wait for the administrator's review. Only after the review is passed can it be used.
Application Web Origins
A Web Origin is separate from an OAuth Redirect URI. Redirect URIs control where authorization may return; Web Origins control which browser sites may call StarEcho APIs across origins. Application owners manage webOrigins while creating or editing an application, without asking a platform administrator to change server environment variables.
- Production origins must use HTTPS and contain only scheme, host, and port. Paths, queries, fragments, user information, and wildcards are rejected.
http://localhost:{port}andhttp://127.0.0.1:{port}are development-only and must include an explicit port from 1024 through 65535.- An application may register up to 32 origins. An empty list denies browser API access from every external site.
/cf/api/public/{clientId}/...and/cf/http/public/{clientId}/...bind the URLclientIdto that application's origins. Authorized browser APIs still validate the Access Token, Client ID, and permissions. CORS is not authentication.STARECHO_CORS_ORIGINis reserved for the platform's own fixed or operational origins; developer websites must not be added to it one by one.
Both POST /oauth/apps and PUT /oauth/apps/{clientId} accept:
{
"webOrigins": [
"https://app.example.com",
"http://localhost:5100"
]
}Before deploying this capability, run database_migrations/20260731-add-app-web-origins.sql, then deploy the new platform package. The migration does not trust any existing website automatically; the application owner must explicitly save its Web Origins.
2. Initiate Authorization
Redirect the user to the authorization page:
GET https://starecho.net/oauth/authorizeQuery Parameters:
| Parameter | Required | Description |
|---|---|---|
response_type | ✅ | Must be code |
client_id | ✅ | Your application's Client ID |
redirect_uri | ✅ | Redirect URI, must match the registered one |
scope | ✅ | Space-separated scopes. Example: openid profile email |
state | Recommended | Random string to prevent CSRF attacks |
nonce | Optional | Random string included in id_token to prevent replay attacks |
code_challenge | Optional | PKCE challenge code (required when using PKCE) |
code_challenge_method | Optional | PKCE method: S256 or plain |
Example:
https://starecho.net/oauth/authorize?
response_type=code&
client_id=YOUR_CLIENT_ID&
redirect_uri=https://your-app.com/callback&
scope=openid profile email&
state=random_state_stringThe user will see an authorization page showing your app's information and requested permissions. After the user approves, the browser will redirect to your callback URL:
https://your-app.com/callback?code=AUTHORIZATION_CODE&state=random_state_string3. Exchange Token
Use the authorization code to request tokens from the token endpoint. Both application/x-www-form-urlencoded (OAuth 2.0 standard) and application/json are supported.
POST https://api.starecho.net/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=AUTHORIZATION_CODE&redirect_uri=https%3A%2F%2Fyour-app.com%2Fcallback&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRETYou can also use JSON format (Content-Type: application/json):
{
"grant_type": "authorization_code",
"code": "AUTHORIZATION_CODE",
"redirect_uri": "https://your-app.com/callback",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET"
}TIP
Most OAuth 2.0 client libraries use application/x-www-form-urlencoded by default. If your framework handles the token exchange automatically, you usually don't need to specify the Content-Type manually.
HTTP Basic authentication (client_secret_basic) is also supported. When using Basic auth, client_id and client_secret are not needed in the request body:
POST https://api.starecho.net/oauth/token
Authorization: Basic base64(client_id:client_secret)
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=AUTHORIZATION_CODE&redirect_uri=https%3A%2F%2Fyour-app.com%2FcallbackSuccess Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"id_token": "eyJhbGciOiJSUzI1NiIs...",
"refresh_token": "dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4...",
"scope": "openid profile email"
}| Field | Description |
|---|---|
access_token | Access token (RS256 JWT, valid for 1 hour) |
token_type | Always Bearer |
expires_in | Token lifetime in seconds |
id_token | Identity token (only when scope includes openid) |
refresh_token | Refresh token (valid for 30 days, rotated on each use) |
scope | Actually granted scopes |
4. Get User Info
Use the access_token to request user information:
GET https://api.starecho.net/oauth/userinfo
Authorization: Bearer ACCESS_TOKENSuccess Response:
{
"sub": "100001",
"name": "StarUser",
"email": "user@example.com",
"gender": "male"
}| Field | Scope | Description |
|---|---|---|
sub | Always | User unique identifier (uid) |
name | profile | Display name |
gender | profile | Gender (male / female) |
email | email | Email address |
5. Refresh Token
When the access_token expires, you can use the refresh_token to obtain a new set of tokens without requiring the user to re-authorize. This follows the OIDC Core 1.0 §12 specification.
POST https://api.starecho.net/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=YOUR_REFRESH_TOKEN&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRETYou can also use JSON format:
{
"grant_type": "refresh_token",
"refresh_token": "YOUR_REFRESH_TOKEN",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET"
}PKCE Applications
If your application uses PKCE authentication (no client_secret), you only need to pass client_id — no client_secret is required.
Request Parameters:
| Parameter | Required | Description |
|---|---|---|
grant_type | ✅ | Must be refresh_token |
refresh_token | ✅ | The previously issued refresh token |
client_id | ✅ | Your application's Client ID |
client_secret | Conditional | Required for non-PKCE applications |
scope | Optional | Can request a subset of the original scopes (scope narrowing) |
Success Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"id_token": "eyJhbGciOiJSUzI1NiIs...",
"refresh_token": "NEW_REFRESH_TOKEN",
"scope": "openid profile email"
}Token Rotation
Each time a refresh_token is used, the server returns a new refresh_token and the previous one is immediately invalidated. Always save and use the latest refresh_token.
If an invalidated refresh_token is reused (suspected token leakage), the server will automatically revoke all refresh tokens for that user in the application to protect account security.
Refresh Token Properties:
| Property | Value |
|---|---|
| Lifetime | 30 days |
| Rotation | New token issued on each use, old token invalidated |
| Replay Detection | Reuse of old token revokes all tokens for the application |
| Scope Narrowing | Can request a subset of original scopes when refreshing |
PKCE Support
For clients that cannot securely store a client_secret (native apps, SPAs), PKCE (Proof Key for Code Exchange) can be used instead of client secret authentication.
Flow
- Generate code_verifier: A random string of 43-128 characters
- Compute code_challenge:
BASE64URL(SHA256(code_verifier)) - Initiate authorization with
code_challengeandcode_challenge_method=S256 - Exchange token with
code_verifier(noclient_secretneeded)
Token Request Example (PKCE):
{
"grant_type": "authorization_code",
"code": "AUTHORIZATION_CODE",
"redirect_uri": "https://your-app.com/callback",
"client_id": "YOUR_CLIENT_ID",
"code_verifier": "YOUR_CODE_VERIFIER"
}Native App Integration Examples
Desktop (Loopback + PKCE):
// 1. Start a local HTTP server on a random port
// 2. Build the authorization URL
https://starecho.net/oauth/authorize?
response_type=code&
client_id=YOUR_CLIENT_ID&
redirect_uri=http://127.0.0.1:12345/callback&
scope=openid profile email&
state=random_state&
code_challenge=CHALLENGE&
code_challenge_method=S256
// 3. Open the system browser for user authorization
// 4. Browser redirects to http://127.0.0.1:12345/callback?code=xxx
// 5. Local server receives the code and exchanges it for tokens via PKCEMobile (Custom URL Scheme + PKCE):
// Prerequisite: Register scheme "starecho" in app settings
https://starecho.net/oauth/authorize?
response_type=code&
client_id=YOUR_CLIENT_ID&
redirect_uri=starecho://callback&
scope=openid profile email&
state=random_state&
code_challenge=CHALLENGE&
code_challenge_method=S256
// After authorization, the system opens the app via Deep Link:
// starecho://callback?code=AUTHORIZATION_CODE&state=random_stateScopes
| Scope | Included Information |
|---|---|
openid | Returns id_token with sub (user ID) |
profile | Display name (name), gender (gender) |
email | Email address (email) |
ID Token
When the requested scope includes openid, the token response will include an id_token. This is an RS256-signed JWT containing the following claims:
| Claim | Description |
|---|---|
sub | User unique identifier (uid) |
aud | Your Client ID |
iss | Issuer URL |
iat | Issued at (Unix timestamp) |
exp | Expiration time (Unix timestamp) |
nonce | The nonce from the request (if provided) |
name | Display name (when scope includes profile) |
email | Email address (when scope includes email) |
gender | Gender (when scope includes profile) |
You can verify the id_token signature using the public key from the JWKS endpoint.
Endpoint Reference
| Endpoint | Method | URL | Description |
|---|---|---|---|
| Discovery | GET | /.well-known/openid-configuration | OIDC service discovery |
| JWKS | GET | /oauth/jwks | JSON Web Key Set public keys |
| Authorization | GET | /oauth/authorize | Authorization page (frontend) |
| Authorization Check | GET | /oauth/authorize/check | Validate authorization parameters |
| Token | POST | /oauth/token | Exchange authorization code for tokens / refresh tokens |
| UserInfo | GET | /oauth/userinfo | Get user information |
Base URLs
- Frontend pages:
https://starecho.net - API endpoints:
https://api.starecho.net
Error Handling
Authorization Errors
When authorization fails, the user will be redirected to the callback URL with error parameters:
https://your-app.com/callback?error=access_denied&state=random_state_string| Error Code | Description |
|---|---|
access_denied | User denied authorization |
invalid_client | Application not found |
client_not_approved | Application not yet approved |
invalid_redirect_uri | Redirect URI mismatch |
Token Errors
The token endpoint returns errors in JSON format:
{
"error": "invalid_grant",
"error_description": "Authorization code expired"
}| Error Code | Description |
|---|---|
unsupported_grant_type | Unsupported grant type |
invalid_grant | Authorization code is invalid, expired, or already used; or refresh token is invalid, expired, or revoked |
invalid_client | Client ID or secret is incorrect |
invalid_scope | Requested scope exceeds original authorization (refresh token only) |
Security Best Practices
- Always use HTTPS — Web application redirect URIs must use the
https://protocol - Validate state — Compare the
stateparameter in authorization request and callback to prevent CSRF - Protect client_secret — Never expose secrets in frontend code, version control, or logs
- Use PKCE — Native apps and SPAs that cannot securely store secrets should use PKCE. All native apps are strongly recommended to enable PKCE
- Verify id_token — Validate signature, issuer, audience, and expiration
- Principle of least privilege — Only request the scopes you need
Native App Redirect URI Reference
| Platform | Redirect URI Format | Notes |
|---|---|---|
| Web App | https://your-app.com/callback | Must be registered in app settings |
| Desktop | http://127.0.0.1:{port}/callback | Auto-allowed, port 1024-65535 |
| Desktop | http://localhost:{port}/callback | Auto-allowed, port 1024-65535 |
| Mobile | {scheme}://callback | scheme must be registered in app settings |
Code Examples
JavaScript / Node.js
// 1. Build authorization URL and redirect
const params = new URLSearchParams({
response_type: 'code',
client_id: 'YOUR_CLIENT_ID',
redirect_uri: 'https://your-app.com/callback',
scope: 'openid profile email',
state: crypto.randomUUID(),
})
window.location.href = `https://starecho.net/oauth/authorize?${params}`
// 2. Callback: exchange authorization code for tokens
const tokenRes = await fetch('https://api.starecho.net/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grant_type: 'authorization_code',
code: authorizationCode,
redirect_uri: 'https://your-app.com/callback',
client_id: 'YOUR_CLIENT_ID',
client_secret: 'YOUR_CLIENT_SECRET',
}),
})
const tokens = await tokenRes.json()
// 3. Get user info
const userRes = await fetch('https://api.starecho.net/oauth/userinfo', {
headers: { Authorization: `Bearer ${tokens.access_token}` },
})
const user = await userRes.json()
console.log(user.name, user.email)Python
import requests
# Exchange token
token_res = requests.post('https://api.starecho.net/oauth/token', json={
'grant_type': 'authorization_code',
'code': authorization_code,
'redirect_uri': 'https://your-app.com/callback',
'client_id': 'YOUR_CLIENT_ID',
'client_secret': 'YOUR_CLIENT_SECRET',
})
tokens = token_res.json()
# Get user info
user_res = requests.get('https://api.starecho.net/oauth/userinfo', headers={
'Authorization': f'Bearer {tokens["access_token"]}'
})
user = user_res.json()
print(user['name'], user['email'])