Skip to content

Commit a7131e7

Browse files
committed
feat: support unsigned oidc request objects
1 parent b44dc75 commit a7131e7

3 files changed

Lines changed: 73 additions & 39 deletions

File tree

frontend/src/lib/hooks/oidc.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,25 @@ export const oidcParamsSchema = z.object({
1111
code_challenge_method: z.string().optional(),
1212
});
1313

14+
function decodeRequestObject(jwt: string): Record<string, string> {
15+
try {
16+
const parts = jwt.split(".");
17+
if (parts.length < 2) return {};
18+
// base64url decode the payload (second part)
19+
const payload = parts[1].replace(/-/g, "+").replace(/_/g, "/");
20+
const json = atob(payload.padEnd(payload.length + ((4 - (payload.length % 4)) % 4), "="));
21+
const parsed = JSON.parse(json);
22+
// Only return string-valued keys relevant to OIDC params
23+
const result: Record<string, string> = {};
24+
for (const [k, v] of Object.entries(parsed)) {
25+
if (typeof v === "string") result[k] = v;
26+
}
27+
return result;
28+
} catch {
29+
return {};
30+
}
31+
}
32+
1433
export const useOIDCParams = (
1534
params: URLSearchParams,
1635
): {
@@ -20,6 +39,15 @@ export const useOIDCParams = (
2039
compiled: string;
2140
} => {
2241
const obj = Object.fromEntries(params.entries());
42+
43+
// RFC 9101 / OIDC Core 6.1: if `request` param present, decode JWT payload
44+
// and merge claims over top-level params (JWT claims take precedence)
45+
const requestJwt = params.get("request");
46+
if (requestJwt) {
47+
const claims = decodeRequestObject(requestJwt);
48+
Object.assign(obj, claims);
49+
}
50+
2351
const parsed = oidcParamsSchema.safeParse(obj);
2452

2553
if (parsed.success) {

internal/controller/well_known_controller.go

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,21 @@ import (
99
)
1010

1111
type OpenIDConnectConfiguration struct {
12-
Issuer string `json:"issuer"`
13-
AuthorizationEndpoint string `json:"authorization_endpoint"`
14-
TokenEndpoint string `json:"token_endpoint"`
15-
UserinfoEndpoint string `json:"userinfo_endpoint"`
16-
JwksUri string `json:"jwks_uri"`
17-
ScopesSupported []string `json:"scopes_supported"`
18-
ResponseTypesSupported []string `json:"response_types_supported"`
19-
GrantTypesSupported []string `json:"grant_types_supported"`
20-
SubjectTypesSupported []string `json:"subject_types_supported"`
21-
IDTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported"`
22-
TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported"`
23-
ClaimsSupported []string `json:"claims_supported"`
24-
ServiceDocumentation string `json:"service_documentation"`
12+
Issuer string `json:"issuer"`
13+
AuthorizationEndpoint string `json:"authorization_endpoint"`
14+
TokenEndpoint string `json:"token_endpoint"`
15+
UserinfoEndpoint string `json:"userinfo_endpoint"`
16+
JwksUri string `json:"jwks_uri"`
17+
ScopesSupported []string `json:"scopes_supported"`
18+
ResponseTypesSupported []string `json:"response_types_supported"`
19+
GrantTypesSupported []string `json:"grant_types_supported"`
20+
SubjectTypesSupported []string `json:"subject_types_supported"`
21+
IDTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported"`
22+
TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported"`
23+
ClaimsSupported []string `json:"claims_supported"`
24+
ServiceDocumentation string `json:"service_documentation"`
25+
RequestParameterSupported bool `json:"request_parameter_supported"`
26+
RequestObjectSigningAlgValuesSupported []string `json:"request_object_signing_alg_values_supported"`
2527
}
2628

2729
type WellKnownControllerConfig struct{}
@@ -48,19 +50,21 @@ func (controller *WellKnownController) SetupRoutes() {
4850
func (controller *WellKnownController) OpenIDConnectConfiguration(c *gin.Context) {
4951
issuer := controller.oidc.GetIssuer()
5052
c.JSON(200, OpenIDConnectConfiguration{
51-
Issuer: issuer,
52-
AuthorizationEndpoint: fmt.Sprintf("%s/authorize", issuer),
53-
TokenEndpoint: fmt.Sprintf("%s/api/oidc/token", issuer),
54-
UserinfoEndpoint: fmt.Sprintf("%s/api/oidc/userinfo", issuer),
55-
JwksUri: fmt.Sprintf("%s/.well-known/jwks.json", issuer),
56-
ScopesSupported: service.SupportedScopes,
57-
ResponseTypesSupported: service.SupportedResponseTypes,
58-
GrantTypesSupported: service.SupportedGrantTypes,
59-
SubjectTypesSupported: []string{"pairwise"},
60-
IDTokenSigningAlgValuesSupported: []string{"RS256"},
61-
TokenEndpointAuthMethodsSupported: []string{"client_secret_basic", "client_secret_post"},
62-
ClaimsSupported: []string{"sub", "updated_at", "name", "preferred_username", "email", "email_verified", "groups"},
63-
ServiceDocumentation: "https://tinyauth.app/docs/guides/oidc",
53+
Issuer: issuer,
54+
AuthorizationEndpoint: fmt.Sprintf("%s/authorize", issuer),
55+
TokenEndpoint: fmt.Sprintf("%s/api/oidc/token", issuer),
56+
UserinfoEndpoint: fmt.Sprintf("%s/api/oidc/userinfo", issuer),
57+
JwksUri: fmt.Sprintf("%s/.well-known/jwks.json", issuer),
58+
ScopesSupported: service.SupportedScopes,
59+
ResponseTypesSupported: service.SupportedResponseTypes,
60+
GrantTypesSupported: service.SupportedGrantTypes,
61+
SubjectTypesSupported: []string{"pairwise"},
62+
IDTokenSigningAlgValuesSupported: []string{"RS256"},
63+
TokenEndpointAuthMethodsSupported: []string{"client_secret_basic", "client_secret_post"},
64+
ClaimsSupported: []string{"sub", "updated_at", "name", "preferred_username", "email", "email_verified", "groups"},
65+
ServiceDocumentation: "https://tinyauth.app/docs/guides/oidc",
66+
RequestParameterSupported: true,
67+
RequestObjectSigningAlgValuesSupported: []string{"none"},
6468
})
6569
}
6670

internal/controller/well_known_controller_test.go

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -56,19 +56,21 @@ func TestWellKnownController(t *testing.T) {
5656
assert.NoError(t, err)
5757

5858
expected := controller.OpenIDConnectConfiguration{
59-
Issuer: oidcServiceCfg.Issuer,
60-
AuthorizationEndpoint: fmt.Sprintf("%s/authorize", oidcServiceCfg.Issuer),
61-
TokenEndpoint: fmt.Sprintf("%s/api/oidc/token", oidcServiceCfg.Issuer),
62-
UserinfoEndpoint: fmt.Sprintf("%s/api/oidc/userinfo", oidcServiceCfg.Issuer),
63-
JwksUri: fmt.Sprintf("%s/.well-known/jwks.json", oidcServiceCfg.Issuer),
64-
ScopesSupported: service.SupportedScopes,
65-
ResponseTypesSupported: service.SupportedResponseTypes,
66-
GrantTypesSupported: service.SupportedGrantTypes,
67-
SubjectTypesSupported: []string{"pairwise"},
68-
IDTokenSigningAlgValuesSupported: []string{"RS256"},
69-
TokenEndpointAuthMethodsSupported: []string{"client_secret_basic", "client_secret_post"},
70-
ClaimsSupported: []string{"sub", "updated_at", "name", "preferred_username", "email", "email_verified", "groups"},
71-
ServiceDocumentation: "https://tinyauth.app/docs/guides/oidc",
59+
Issuer: oidcServiceCfg.Issuer,
60+
AuthorizationEndpoint: fmt.Sprintf("%s/authorize", oidcServiceCfg.Issuer),
61+
TokenEndpoint: fmt.Sprintf("%s/api/oidc/token", oidcServiceCfg.Issuer),
62+
UserinfoEndpoint: fmt.Sprintf("%s/api/oidc/userinfo", oidcServiceCfg.Issuer),
63+
JwksUri: fmt.Sprintf("%s/.well-known/jwks.json", oidcServiceCfg.Issuer),
64+
ScopesSupported: service.SupportedScopes,
65+
ResponseTypesSupported: service.SupportedResponseTypes,
66+
GrantTypesSupported: service.SupportedGrantTypes,
67+
SubjectTypesSupported: []string{"pairwise"},
68+
IDTokenSigningAlgValuesSupported: []string{"RS256"},
69+
TokenEndpointAuthMethodsSupported: []string{"client_secret_basic", "client_secret_post"},
70+
ClaimsSupported: []string{"sub", "updated_at", "name", "preferred_username", "email", "email_verified", "groups"},
71+
ServiceDocumentation: "https://tinyauth.app/docs/guides/oidc",
72+
RequestParameterSupported: true,
73+
RequestObjectSigningAlgValuesSupported: []string{"none"},
7274
}
7375

7476
assert.Equal(t, expected, res)

0 commit comments

Comments
 (0)