OAuth2 Server
Let third-party applications sign customers in with their store account and read their data.
Overview
Light Store can act as an OAuth2 authorization server. Applications you register get a client ID and secret, send customers to your store to approve access, and receive an access token they can use on the /api/me endpoints.
Typical uses are a "Sign in with your store account" button on another site, a desktop app checking which products a customer owns, or a game server reading a customer's licenses.
Available from 27.2.0
The server implements the authorization code grant (with optional PKCE) and the refresh token grant. The password, implicit and device code grants are not available.
Enabling the server
Open Settings > OAuth2 Server in the admin panel and turn on Enable the OAuth2 server, then save the settings. While it is off, every endpoint on this page answers 404.
The same tab sets the token lifetimes:
| Setting | Default | Range |
|---|---|---|
| Access token lifetime | 60 minutes | 5 to 1440 minutes |
| Refresh token lifetime | 30 days | 1 to 365 days |
The key pair signing the tokens is generated on first use and kept encrypted in the database, so it moves with your database backups. Set PASSPORT_PRIVATE_KEY and PASSPORT_PUBLIC_KEY in .env only to sign with a key pair you manage yourself.
Registering an application
In the Applications section of the tab, choose Register application and fill in:
- Name - shown to customers on the approval screen.
- Redirect URLs - where customers may be sent back after deciding. A request naming any other URL is refused. Press Enter after each one.
- Confidential - leave on for applications with a server that can keep a secret. Turn it off for mobile, desktop or single-page apps: they get no secret and must use PKCE.
After saving, the client ID and client secret are shown once. Copy the secret right away: it is stored hashed and cannot be displayed again.
Each application row also offers:
- Edit - change the name or the redirect URLs.
- New secret - replaces the secret. The old one stops working immediately.
- Delete - removes the application and every token it was issued, signing customers out of it.
Endpoints
| Endpoint | Method | Purpose |
|---|---|---|
/oauth2/authorize | GET | Sends the customer to the store to sign in and approve. |
/oauth2/token | POST | Exchanges an authorization code or a refresh token. |
/api/me | GET | The customer's profile. |
/api/me/purchases | GET | The customer's paid purchases, newest first, 10 per page. |
/api/me/licenses | GET | The customer's licenses, newest first. |
Every parameter and response is listed in the API reference: authorize, token, profile, purchases and licenses.
Scopes
An application asks for scopes in the scope parameter, separated by spaces. The customer sees each requested scope on the approval screen. A request without scope gets profile.
| Scope | Grants |
|---|---|
profile | /api/me: ID, username, avatar and sign-up date. |
email | Adds email and email_verified to /api/me. |
purchases:read | /api/me/purchases. |
licenses:read | /api/me/licenses. |
Authorization code flow
Send the customer to the store. Redirect the browser to /oauth2/authorize with these query parameters:
| Parameter | Value |
|---|---|
client_id | The application's client ID. |
redirect_uri | One of the application's redirect URLs, exactly as entered. |
response_type | code |
scope | Space separated scopes, for example profile email. |
state | A random value your application checks on the way back. |
code_challenge | PKCE: base64url of the SHA-256 of your code verifier. |
code_challenge_method | PKCE: S256 |
https://store.example.com/oauth2/authorize?client_id=9f3c...&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback&response_type=code&scope=profile%20email&state=Qx81Customers who are not signed in sign in first, then see which application asks for what. Once they have approved an application, later requests for the same scopes skip the screen until their tokens expire.
Receive the code. The store redirects back to redirect_uri with code and your state. If the customer cancels, it carries error=access_denied instead. Check that state matches the value you sent.
https://app.example.com/callback?code=def502...&state=Qx81Exchange the code for tokens. The code is valid for 10 minutes and works once.
curl -X POST https://store.example.com/oauth2/token \
-H "Accept: application/json" \
-d grant_type=authorization_code \
-d client_id=9f3c... \
-d client_secret=YOUR_SECRET \
-d redirect_uri=https://app.example.com/callback \
-d code=def502... \
-d code_verifier=YOUR_PKCE_VERIFIERPublic applications leave out client_secret and must send code_verifier. The response holds the tokens:
{
"token_type": "Bearer",
"expires_in": 3600,
"access_token": "eyJ0eXAiOiJKV1Qi...",
"refresh_token": "def50200a1b2..."
}Call the API with the access token as a Bearer token:
curl https://store.example.com/api/me \
-H "Accept: application/json" \
-H "Authorization: Bearer eyJ0eXAiOiJKV1Qi..."{
"data": {
"id": 42,
"name": "jane",
"avatar_url": "https://store.example.com/storage/avatars/42.webp",
"created_at": "2026-03-14T09:26:53.000000Z",
"email": "jane@example.com",
"email_verified": true
}
}Refreshing tokens
When the access token expires, trade the refresh token for a new pair. Each refresh token works once: store the new one from the response.
curl -X POST https://store.example.com/oauth2/token \
-H "Accept: application/json" \
-d grant_type=refresh_token \
-d client_id=9f3c... \
-d client_secret=YOUR_SECRET \
-d refresh_token=def50200a1b2...Revoking access
Customers see every application they approved under Account > Connected Apps in their profile, with what it can read, and can Revoke access there. Revoking ends every access and refresh token the application holds for that customer at once: its next request answers 401, and its next sign-in shows the approval screen again.
Deleting an application in the admin panel revokes it for every customer.
API endpoints
The /api/me endpoints accept only OAuth2 access tokens; API keys from the admin panel do not work there, and access tokens do not work on the other API routes. Their responses are documented in the API reference.
Send Accept: application/json with every request so errors come back as JSON:
| Status | Meaning |
|---|---|
401 | The access token is missing, expired, revoked or invalid. |
403 | The token was not granted the scope this endpoint needs. |
404 | The OAuth2 server is turned off in the settings. |