Introduction

"Sign in with 4S" is an OAuth 2.0 / OpenID Connect (OIDC) compliant authentication feature. External applications can let users sign in with their 4S account and access the 4S read (GET) APIs on their behalf.

Because the implementation is standards-compliant, common OIDC libraries such as NextAuth (Auth.js) and openid-client work out of the box. The only supported flow is Authorization Code with PKCE (S256).

Register your app from the Developer menu on 4S (https://4s.link/developer). You will receive a client_id and a client_secret. The client_secret is shown only once at registration — store it securely.

Basics

ItemValue
Issuerhttps://api.4s.link
Supported flowAuthorization Code + PKCE (S256)
Client authenticationclient_secret_basic / client_secret_post / none (public + PKCE)
ID Token signatureRS256
Access token lifetime1 hour
Refresh token lifetime60 days (rotating)
Endpoints
# OIDC Discovery
https://api.4s.link/.well-known/openid-configuration
# JWKS (公開鍵)
https://api.4s.link/.well-known/jwks.json
# 認可エンドポイント
https://4s.link/oauth/authorize
# トークンエンドポイント
https://api.4s.link/oauth/token
# UserInfo
https://api.4s.link/oauth/userinfo
# トークン失効
https://api.4s.link/oauth/revoke

Authentication Flow

The Authorization Code + PKCE flow works as follows: (1) your app generates a code_verifier and redirects the user to the authorization endpoint with its S256 hash (code_challenge); (2) the user signs in to 4S and grants consent, and an authorization code is returned to your redirect_uri; (3) your app exchanges the code together with the code_verifier for tokens at the token endpoint.

GEThttps://4s.link/oauth/authorize

The authorization endpoint is a URL you redirect the user's browser to (it is not an API). Attach the following query parameters.

Query parameters

response_typestringrequired

Must be code

client_idstringrequired

Client ID issued in the Developer menu

redirect_uristringrequired

Must exactly match a registered redirect URI

statestring

Random value for CSRF protection, returned unchanged in the callback (strongly recommended)

noncestring

Random value returned as the nonce claim in the ID Token (recommended)

code_challengestringrequired

base64url-encoded SHA-256 hash of the code_verifier

code_challenge_methodstringrequired

Must be S256

promptstring

Set to consent to force the consent screen even if already granted. none is not supported and returns interaction_required

After the user approves, the browser returns to your redirect_uri with ?code=...&state=.... If the user denies or validation fails, ?error=access_denied (or similar) is returned instead. Authorization codes are single-use and expire after 5 minutes.

Example authorization request
const crypto = require("crypto");
const codeVerifier = crypto.randomBytes(32).toString("base64url");
const codeChallenge = crypto
.createHash("sha256")
.update(codeVerifier)
.digest("base64url");
console.log({ codeVerifier, codeChallenge });

Discovery / JWKS

GET/.well-known/openid-configuration

The OIDC Discovery document. It returns metadata such as endpoint URLs and signing algorithms. Configure your OIDC library with this origin as the issuer and everything else resolves automatically.

GET/.well-known/jwks.json

The public keys (JWK Set) for verifying ID Token signatures. Verify the RS256 signature using the key whose kid matches the ID Token header. Keys may be rotated, so fetch the JWKS as needed or implement cache expiry (Cache-Control: max-age=300).

Fetch discovery document
curl https://api.4s.link/.well-known/openid-configuration

Token Endpoint

POST/oauth/token

Exchanges an authorization code for tokens, or refreshes tokens with a refresh token. Send the request body as application/x-www-form-urlencoded. Confidential clients authenticate with client_secret_basic (Authorization: Basic) or client_secret_post (in the body).

Parameters for grant_type=authorization_code

grant_typestringrequired

authorization_code

codestringrequired

The authorization code received in the callback

redirect_uristringrequired

Same value as in the authorization request

code_verifierstringrequired

The PKCE verifier the code_challenge was derived from

Parameters for grant_type=refresh_token

grant_typestringrequired

refresh_token

refresh_tokenstringrequired

A valid refresh token

Refresh tokens rotate: every refresh_token grant issues a new refresh token and invalidates the old one. If a revoked refresh token is reused, all tokens for that user-client pair are revoked as a safety measure. Always persist the latest refresh token.

Code exchange
curl -X POST https://api.4s.link/oauth/token \
-u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=AUTHORIZATION_CODE" \
-d "redirect_uri=https://example.com/callback" \
-d "code_verifier=CODE_VERIFIER"

ID Token

The token response always contains an ID Token (an RS256-signed JWT). To validate it, verify (1) the signature with the JWKS key matching the kid, (2) iss equals the issuer, (3) aud equals your client_id, (4) exp is in the future, and (5) the nonce claim matches if you sent one. OIDC libraries handle all of this automatically.

Main claims

ClaimDescription
subUnique user ID (the 4S user ID)
issIssuer
audYour client ID
auth_timeWhen the user authorized (Unix seconds)
nonceThe nonce from the authorization request (if sent)
name / given_name / family_name / picture / profile / preferred_username / localeProfile info (always included)
email / email_verifiedEmail address (always included)
Example ID Token payload
{
"sub": "a1b2c3d4-....",
"iss": "https://api.4s.link",
"aud": "YOUR_CLIENT_ID",
"iat": 1783900000,
"exp": 1783903600,
"auth_time": 1783899990,
"nonce": "RANDOM_NONCE",
"name": "太郎 山田",
"given_name": "太郎",
"family_name": "山田",
"picture": "https://.../avatar.png",
"profile": "https://4s.link/taro",
"preferred_username": "taro",
"locale": "ja",
"email": "taro@example.com",
"email_verified": true
}

Token Revocation

POST/oauth/revoke

RFC 7009 compliant token revocation endpoint. Revoke tokens that are no longer needed, e.g. when the user signs out of your app. Revoking a refresh token also revokes the access tokens issued from it. Per RFC 7009, unknown tokens still return 200.

Body parameters

tokenstringrequired

The access token or refresh token to revoke

Revoke a token
curl -X POST https://api.4s.link/oauth/revoke \
-u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "token=4s_rt_..."

Calling the APIs

Call the 4S APIs by sending your access token in the Authorization: Bearer header. OAuth access tokens can only call the read (GET) endpoints listed in the categories below.

Conventions

ItemDetail
Base URLhttps://api.4s.link
AuthAuthorization: Bearer <access_token>
Allowed scopeOnly the GET endpoints listed in each category
PaginationList endpoints take page / perPage query params; responses include pageInfo (totalCount / totalPages / page / perPage)
Error (401)Token invalid, revoked or expired
Error (403)Access to an endpoint outside the allowed GET list

Representative endpoints for each category are listed below. For detailed request/response fields, refer to the OpenAPI specification (openapi.yaml).

Example request
curl https://api.4s.link/users/me \
-H "Authorization: Bearer 4s_at_..."

Error Reference

The token endpoint, UserInfo and events APIs return RFC 6749 / RFC 6750 style errors ({ error, error_description }). Authorization endpoint errors are returned as query parameters on the redirect_uri (unless the redirect_uri itself is invalid).

ErrorWhereDescription
invalid_clienttoken / revokeClient authentication failed, or the app is suspended
invalid_granttokenInvalid, expired or already-used code / refresh token, PKCE verification failure, redirect_uri mismatch, or consent revoked
invalid_requesttoken / authorizeMissing or malformed required parameters
access_deniedauthorizeThe user denied the authorization request
interaction_requiredauthorizeprompt=none was requested but interaction is required (silent auth is not supported)
unsupported_grant_typetokenA grant_type other than authorization_code / refresh_token
401 UnauthorizedResource APIsThe access token is invalid, revoked or expired, or the app is suspended
403 ForbiddenResource APIsAccess to an endpoint outside the allowed GET list (only the endpoints listed in each category are usable)
Example error response
{
"error": "invalid_grant",
"error_description": "PKCE verification failed"
}

Integration Examples

With generic OIDC libraries, setting the issuer is enough — endpoints resolve automatically via Discovery. A NextAuth (Auth.js) v5 configuration example is shown on the right. Don't forget to register your redirect_uri (e.g. https://your-app.com/api/auth/callback/4s) in the Developer menu.

Known limitations: prompt=none (silent authentication) and Dynamic Client Registration are not supported. ID Tokens do not include the at_hash claim.

NextAuth (Auth.js) v5
import NextAuth from "next-auth";
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
{
id: "4s",
name: "4S",
type: "oidc",
issuer: "https://api.4s.link",
clientId: process.env.FOURS_CLIENT_ID,
clientSecret: process.env.FOURS_CLIENT_SECRET,
authorization: {
params: { scope: "openid profile email" },
},
// PKCE と state は NextAuth がデフォルトで有効化します
},
],
});

Demo App

A working "Sign in with 4S" sample built with Next.js + TypeScript. See how the login flow (Authorization Code + PKCE) and profile retrieval are implemented.

Users / Profile

Organizations

Communities

Events

Bookmarks

Playlists

Misc