Summary
In simpleidserver/idserver:6.0.4, the OAuth 2.0 scope claim is serialized as a JSON array in both:
- the access-token JWT payload, and
- the RFC 7662 token introspection response.
Per the relevant specifications, scope is a space-delimited string, not an array. This causes spec-compliant resource servers and introspection clients to reject otherwise-valid tokens.
Environment
- Image:
simpleidserver/idserver:6.0.4 (Docker Hub), StorageConfiguration__Type=INMEMORY, plain HTTP, default master realm.
- Observed: June 2026.
Steps to reproduce
ISS=http://127.0.0.1:8080/master
# 1. Management token (seeded admin client)
MGMT=$(curl -s -X POST "$ISS/token" \
-d grant_type=client_credentials -d client_id=SIDS-manager -d client_secret=password \
-d "scope=clients scopes users" | jq -r .access_token)
# 2. Create an API scope + a confidential client
curl -s -X POST "$ISS/scopes" -H "Authorization: Bearer $MGMT" -H "Content-Type: application/json" \
-d '{"name":"mail_read","type":1,"protocol":0,"is_exposed":true}'
curl -s -X POST "$ISS/clients" -H "Authorization: Bearer $MGMT" -H "Content-Type: application/json" \
-d '{"id":"<uuid>","client_id":"demo","client_type":"MACHINE","is_public":false,
"grant_types":["client_credentials"],"token_endpoint_auth_method":"client_secret_post",
"access_token_type":"Jwt",
"client_secrets":[{"id":"<uuid>","value":"demosecret","alg":0,"is_active":true}],
"scopes":[{"name":"openid"},{"name":"mail_read"}]}'
# 3. Mint a token and decode the JWT payload
AT=$(curl -s -X POST "$ISS/token" \
-d grant_type=client_credentials -d client_id=demo -d client_secret=demosecret \
-d "scope=openid mail_read" | jq -r .access_token)
echo "$AT" | cut -d. -f2 | tr '_-' '/+' | base64 -d | jq '{scope, aud}'
# 4. Introspect it
curl -s -X POST "$ISS/token_info" -d client_id=demo -d client_secret=demosecret -d "token=$AT" | jq '{scope}'
Actual behaviour
JWT payload:
{
"scope": ["openid", "mail_read"],
"aud": ["demo"]
}
Introspection response:
{ "active": true, "scope": ["openid", "mail_read"], ... }
scope is a JSON array in both.
Expected behaviour
scope should be a single space-delimited string:
{ "scope": "openid mail_read" }
This is required by:
- RFC 9068 (JWT Profile for OAuth 2.0 Access Tokens) §2.2.3 — the
scope claim follows §3.3 of RFC 6749, i.e. a space-delimited list in a string.
- RFC 6749 §3.3 —
scope ABNF is a space-delimited string (scope = scope-token *( SP scope-token )).
- RFC 7662 (OAuth 2.0 Token Introspection) §2.2 —
scope: "A JSON string containing a space-separated list of scopes."
(aud correctly may be a string or array per JWT/RFC 9068; only scope is affected here.)
Impact
RFC-compliant consumers type scope as a string (as the specs mandate) and fail to deserialize SimpleIdServer's array form, rejecting valid tokens. We hit this with independent Go implementations of RFC 9068 and RFC 7662 — both error with the equivalent of "cannot unmarshal array into a string field" on the scope member, on both the JWT-validation path and the introspection path.
Suggested fix
Serialize scope as a space-delimited string in both the access-token JWT and the introspection response (ideally the spec-compliant default; a compatibility toggle would be a bonus). Happy to help test a fix.
Summary
In
simpleidserver/idserver:6.0.4, the OAuth 2.0scopeclaim is serialized as a JSON array in both:Per the relevant specifications,
scopeis a space-delimited string, not an array. This causes spec-compliant resource servers and introspection clients to reject otherwise-valid tokens.Environment
simpleidserver/idserver:6.0.4(Docker Hub),StorageConfiguration__Type=INMEMORY, plain HTTP, defaultmasterrealm.Steps to reproduce
Actual behaviour
JWT payload:
{ "scope": ["openid", "mail_read"], "aud": ["demo"] }Introspection response:
{ "active": true, "scope": ["openid", "mail_read"], ... }scopeis a JSON array in both.Expected behaviour
scopeshould be a single space-delimited string:{ "scope": "openid mail_read" }This is required by:
scopeclaim follows §3.3 of RFC 6749, i.e. a space-delimited list in a string.scopeABNF is a space-delimited string (scope = scope-token *( SP scope-token )).scope: "A JSON string containing a space-separated list of scopes."(
audcorrectly may be a string or array per JWT/RFC 9068; onlyscopeis affected here.)Impact
RFC-compliant consumers type
scopeas a string (as the specs mandate) and fail to deserialize SimpleIdServer's array form, rejecting valid tokens. We hit this with independent Go implementations of RFC 9068 and RFC 7662 — both error with the equivalent of "cannot unmarshal array into a string field" on thescopemember, on both the JWT-validation path and the introspection path.Suggested fix
Serialize
scopeas a space-delimited string in both the access-token JWT and the introspection response (ideally the spec-compliant default; a compatibility toggle would be a bonus). Happy to help test a fix.