Developers
Add Sign in with DSTG to your website. Members approve a public profile, and your app receives an access token. Passwords stay on Dark Solar.
01
Sign in, open My apps, and register the site that will offer Sign in with DSTG. Admin accounts are approved immediately. Every other app waits for an admin.
02
Copy the client ID. Confidential server apps also get a client secret once. Put the secret in your server environment, never in frontend code.
03
Add the exact redirect URI your site will use. https is required. http is allowed only for localhost and 127.0.0.1 while you develop.
04
Redirect their browser to the authorize URL with response_type=code, your client ID, redirect URI, a random state, and a PKCE S256 challenge.
05
DSTG sends the member back with a one-time code. Your server posts that code to the token URL. Public apps send the PKCE verifier instead of a secret.
06
Call the userinfo URL with Authorization: Bearer. That JSON is the member record. Email is included only when they approved the email scope.
| Authorize | GET | https://www.darksolargaming.com/oauth/authorize |
|---|---|---|
| Token | POST | https://www.darksolargaming.com/api/oauth/token |
| Revoke | POST | https://www.darksolargaming.com/api/oauth/revoke |
| User info | GET | https://www.darksolargaming.com/api/oauth/userinfo |
| Me | GET | https://www.darksolargaming.com/api/v1/me |
| Public operator | GET | https://www.darksolargaming.com/api/v1/operators/{slug} |
| OpenAPI | GET | https://www.darksolargaming.com/api/v1/openapi |
openid
Stable member id in sub.
profile
Name, avatar, rank, level, and Armory link.
Account email. Ask only if you need it.
const state = crypto.randomUUID();
const verifier = crypto.randomBytes(32).toString("base64url");
const challenge = crypto.createHash("sha256").update(verifier).digest("base64url");
session.state = state;
session.verifier = verifier;
const authorize = new URL("https://www.darksolargaming.com/oauth/authorize");
authorize.searchParams.set("response_type", "code");
authorize.searchParams.set("client_id", process.env.DSTG_CLIENT_ID);
authorize.searchParams.set("redirect_uri", "https://your.site/auth/dstg/callback");
authorize.searchParams.set("scope", "openid profile");
authorize.searchParams.set("state", state);
authorize.searchParams.set("code_challenge", challenge);
authorize.searchParams.set("code_challenge_method", "S256");
res.redirect(authorize.toString());When the member returns, check that state matches, then POST grant_type=authorization_code to the token URL with the same redirect URI. Confidential apps also send client_secret. Public apps send code_verifier. Then GET userinfo with Authorization: Bearer. Access tokens last 1 hour. Refresh tokens last 30 days and rotate on every refresh.
Paste this into a Custom GPT, a ChatGPT project, or the system message of an OpenAI API request. It tells the model the exact DSTG flow, scopes, and endpoints so it can write the integration without guessing.
OpenAI instructions
You are integrating Dark Solar Tactical Gaming (DSTG) into a third-party website.
Base URL: https://www.darksolargaming.com
Never ask a person for their DSTG password, Discord token, or client secret in a browser.
Never invent endpoints. Use only the endpoints in this prompt.
What DSTG OAuth does
- Lets a website offer "Sign in with DSTG".
- Returns the member's public profile, and their email only if they approve the email scope.
- Does not grant admin, XP, forum posting, or the ability to change the member's account.
Before you write code
1. The developer creates an app at https://www.darksolargaming.com/developers/apps while logged into DSTG.
2. They copy the client_id. Confidential apps also get a client_secret shown once.
3. They register exact redirect URIs. https is required. http is allowed only for localhost and 127.0.0.1.
4. An admin must approve the app. Until status is APPROVED, /oauth/authorize will refuse the client.
5. Choose client type:
- CONFIDENTIAL: a server holds client_secret and exchanges the code.
- PUBLIC: a browser or mobile app uses PKCE (S256) and does not send a secret.
Scopes (space-separated)
- openid: stable member id in "sub"
- profile: name, picture, rank, level, military_tier, is_team_dstg, motto, status_text, looking_for_squad, primary_game, armory_slug, armory_url, profile_url
- email: email and email_verified
Default when scope is omitted: "openid profile"
Step 1. Send the member's browser to
GET https://www.darksolargaming.com/oauth/authorize
Required query parameters:
- response_type=code
- client_id=dstg_ plus 32 hex characters
- redirect_uri=one exact registered URI
- state=random unguessable string you store in the user's session
Always send PKCE for public apps, and send it for confidential apps too when you can:
- code_challenge=base64url(sha256(code_verifier)) with no padding
- code_challenge_method=S256
- code_verifier is 43 to 128 characters from [A-Za-z0-9._~-]
Optional: scope, nonce
DSTG shows a consent screen. On approval the browser returns to redirect_uri with ?code= and the same state.
On denial: ?error=access_denied&state=
You must reject the login if state does not match.
Step 2. Exchange the code from YOUR server (confidential) or from the browser (public PKCE)
POST https://www.darksolargaming.com/api/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
code=THE_CODE
redirect_uri=THE_SAME_URI
client_id=THE_CLIENT_ID
client_secret=THE_SECRET (confidential only)
code_verifier=THE_VERIFIER (required if a challenge was sent; always required for public apps)
Success JSON:
{
"access_token": "...",
"refresh_token": "...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "openid profile"
}
The code works once and expires in 10 minutes. Access tokens expire in 1 hour. Refresh tokens expire in 30 days.
Refresh:
POST https://www.darksolargaming.com/api/oauth/token
grant_type=refresh_token
refresh_token=THE_REFRESH_TOKEN
client_id=THE_CLIENT_ID
client_secret=THE_SECRET (confidential only)
DSTG rotates the refresh token. Store the new one and discard the old one. Reusing a revoked refresh token revokes that member's tokens for this app.
Revoke:
POST https://www.darksolargaming.com/api/oauth/revoke
token=ACCESS_OR_REFRESH_TOKEN
client_id=THE_CLIENT_ID
client_secret=THE_SECRET (confidential only)
Step 3. Read the member
GET https://www.darksolargaming.com/api/oauth/userinfo
Authorization: Bearer ACCESS_TOKEN
The same payload is at GET https://www.darksolargaming.com/api/v1/me
Use this response as the source of truth. Do not decode or trust any token as a user record.
Public profile without login
GET https://www.darksolargaming.com/api/v1/operators/{slug}
Replace {slug} with the member's Armory slug. No token. Returns only fields that are already public on the Armory page. Unknown slug returns 404.
Errors are JSON: { "error": "...", "error_description": "..." }
Common errors: invalid_request, invalid_client, invalid_grant, unauthorized_client, unsupported_grant_type, invalid_scope, access_denied.
OpenAPI document: https://www.darksolargaming.com/api/v1/openapi
Human docs: https://www.darksolargaming.com/developers
Rules
- Compare redirect_uri as an exact string. Do not allow wildcards.
- Keep client_secret on the server. Public apps must not have a secret in frontend code.
- Store state in the session before the redirect and check it after.
- Show the DSTG member name and profile_url in your UI only after userinfo succeeds.
- If userinfo returns 401, refresh once, then ask the member to sign in again.
- Do not request the email scope unless the website needs the email.
- Do not call any DSTG URL that is not listed above.