Skip to content

Commit 0345102

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

3 files changed

Lines changed: 81 additions & 39 deletions

File tree

frontend/src/lib/hooks/oidc.ts

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

14+
function b64urlDecode(s: string): string {
15+
const base64 = s.replace(/-/g, "+").replace(/_/g, "/");
16+
return atob(base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), "="));
17+
}
18+
19+
function decodeRequestObject(jwt: string): Record<string, string> {
20+
try {
21+
// Must have exactly 3 parts: header, payload, signature
22+
const parts = jwt.split(".");
23+
if (parts.length !== 3) return {};
24+
25+
// Header must specify "alg": "none" and signature must be empty string
26+
const header = JSON.parse(b64urlDecode(parts[0]));
27+
if (!header || typeof header !== "object" || header.alg !== "none" || parts[2] !== "") return {};
28+
29+
const payload = JSON.parse(b64urlDecode(parts[1]));
30+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return {};
31+
const result: Record<string, string> = {};
32+
for (const [k, v] of Object.entries(payload)) {
33+
if (typeof v === "string") result[k] = v;
34+
}
35+
return result;
36+
} catch {
37+
return {};
38+
}
39+
}
40+
1441
export const useOIDCParams = (
1542
params: URLSearchParams,
1643
): {
@@ -20,6 +47,15 @@ export const useOIDCParams = (
2047
compiled: string;
2148
} => {
2249
const obj = Object.fromEntries(params.entries());
50+
51+
// RFC 9101 / OIDC Core 6.1: if `request` param present, decode JWT payload
52+
// and merge claims over top-level params (JWT claims take precedence)
53+
const requestJwt = params.get("request");
54+
if (requestJwt) {
55+
const claims = decodeRequestObject(requestJwt);
56+
Object.assign(obj, claims);
57+
}
58+
2359
const parsed = oidcParamsSchema.safeParse(obj);
2460

2561
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)