Skip to content

Commit 6eda9b0

Browse files
committed
store id_token/userinfo claims as JSON objects
and avoid parsing/serializing overhead which results in up to 7% performance increase and a decrease of 20-40 bytes in session/cookie storage size, depending on the number of claims stored NB: the internal session format has changed and is backwards incompatible: existing sessions and cookies will be invalid Signed-off-by: Hans Zandbelt <[email protected]>
1 parent 5a1a61a commit 6eda9b0

File tree

19 files changed

+315
-277
lines changed

19 files changed

+315
-277
lines changed

ChangeLog

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
11/17/2025
22
- metadata: avoid double-free when validation of provider metadata fails
3+
- perf: store id_token/userinfo claims as JSON objects and avoid parsing/serializing overhead
4+
which results in up to 7% performance increase, depending on the number of claims stored
5+
- session/cookie: save 20-40 bytes on the session and client-cookie size
6+
NB: the internal session format has changed and is backwards incompatible: existing sessions and cookies will be invalid
37
- drop support for Apache 2.2
48
- bump to 2.4.19dev
59

@@ -14,7 +18,7 @@
1418
- test: test/Makefile.am refactor check programs
1519

1620
11/07/2025
17-
- support individual SameSite cookie settings on the session cookie, state cookie and Discovery CSRF
21+
- cookie: support individual SameSite cookie settings on the session cookie, state cookie and Discovery CSRF
1822
cookie by adding 2 more arguments to OIDCCookieSameSite
1923
- code: avoid compiler warnings on `curl_easy_setopt` in http.c
2024
http.c:612:16: warning: call to '_curl_easy_setopt_err_long' declared with attribute warning: curl_easy_setopt expects a long argument [-Wattribute-warning]

src/handle/authz.c

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -360,8 +360,7 @@ static apr_byte_t oidc_authz_match_claims_expr(request_rec *r, const char *const
360360

361361
oidc_debug(r, "enter: '%s'", attr_spec);
362362

363-
str = oidc_util_jq_filter(r, oidc_util_json_encode(r->pool, claims, JSON_PRESERVE_ORDER | JSON_COMPACT),
364-
attr_spec);
363+
str = oidc_util_jq_filter(r, claims, attr_spec);
365364
rv = (_oidc_strcmp(str, "true") == 0);
366365

367366
return rv;
@@ -386,15 +385,8 @@ static void oidc_authz_error_add(request_rec *r, const char *msg) {
386385
*/
387386
static void oidc_authz_get_claims_idtoken_scope(request_rec *r, json_t **claims, json_t **id_token,
388387
const char **scope) {
389-
390-
const char *s_claims = oidc_request_state_get(r, OIDC_REQUEST_STATE_KEY_CLAIMS);
391-
if (s_claims != NULL)
392-
oidc_util_json_decode_object(r, s_claims, claims);
393-
394-
const char *s_id_token = oidc_request_state_get(r, OIDC_REQUEST_STATE_KEY_IDTOKEN);
395-
if (s_id_token != NULL)
396-
oidc_util_json_decode_object(r, s_id_token, id_token);
397-
388+
*claims = oidc_request_state_json_get(r, OIDC_REQUEST_STATE_KEY_CLAIMS);
389+
*id_token = oidc_request_state_json_get(r, OIDC_REQUEST_STATE_KEY_IDTOKEN);
398390
*scope = oidc_request_state_get(r, OIDC_REQUEST_STATE_KEY_SCOPE);
399391
}
400392

@@ -403,14 +395,13 @@ static void oidc_authz_get_claims_idtoken_scope(request_rec *r, json_t **claims,
403395
* from the userinfo endpoint into a single set of claims that can be used to authorize on
404396
*/
405397
static json_t *oidc_authz_merge_claims(request_rec *r) {
398+
json_t *result = json_object();
406399
json_t *claims = NULL, *id_token = NULL;
407400
const char *scope = NULL;
408401

409402
/* get the set of claims from the request state as they have been set in the authentication part earlier */
410403
oidc_authz_get_claims_idtoken_scope(r, &claims, &id_token, &scope);
411404

412-
json_t *result = json_object();
413-
414405
/* if scope was returned from the token endpoint, include it in the set of authorization claims */
415406
if (scope)
416407
json_object_set_new(result, OIDC_CLAIM_SCOPE, json_string(scope));
@@ -421,12 +412,8 @@ static json_t *oidc_authz_merge_claims(request_rec *r) {
421412

422413
/* merge id_token claims (e.g. "iss") into the authorization claims (take precedence over userinfo claims and
423414
* scope) */
424-
oidc_util_json_merge(r, id_token, result);
425-
426415
if (id_token)
427-
json_decref(id_token);
428-
if (claims)
429-
json_decref(claims);
416+
oidc_util_json_merge(r, id_token, result);
430417

431418
return result;
432419
}

src/handle/handle.h

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,8 @@ int oidc_response_authorization_redirect(request_rec *r, oidc_cfg_t *c, oidc_ses
114114
int oidc_response_authorization_post(request_rec *r, oidc_cfg_t *c, oidc_session_t *session);
115115
apr_byte_t oidc_response_save_in_session(request_rec *r, oidc_cfg_t *c, oidc_session_t *session,
116116
oidc_provider_t *provider, const char *remoteUser, const char *id_token,
117-
oidc_jwt_t *id_token_jwt, const char *claims, const char *access_token,
117+
oidc_jwt_t *id_token_jwt, const char *s_userinfo_claims,
118+
json_t *userinfo_claims, const char *access_token,
118119
const char *access_token_type, const int expires_in, const char *refresh_token,
119120
const char *scope, const char *session_state, const char *state,
120121
const char *original_url, const char *userinfo_jwt);
@@ -128,13 +129,14 @@ int oidc_session_management(request_rec *r, oidc_cfg_t *c, oidc_session_t *sessi
128129

129130
// userinfo.c
130131
void oidc_userinfo_store_claims(request_rec *r, oidc_cfg_t *c, oidc_session_t *session, oidc_provider_t *provider,
131-
const char *claims, const char *userinfo_jwt);
132+
json_t *userinfo_claims, const char *userinfo_jwt);
132133
const char *oidc_userinfo_retrieve_claims(request_rec *r, oidc_cfg_t *c, oidc_provider_t *provider,
133134
const char *access_token, const char *access_token_type,
134-
oidc_session_t *session, char *id_token_sub, char **userinfo_jwt);
135+
oidc_session_t *session, char *id_token_sub, json_t **userinfo_claims,
136+
char **userinfo_jwt);
135137
apr_byte_t oidc_userinfo_refresh_claims(request_rec *r, oidc_cfg_t *cfg, oidc_session_t *session,
136138
apr_byte_t *needs_save);
137-
void oidc_userinfo_pass_as(request_rec *r, oidc_cfg_t *cfg, oidc_session_t *session, const char *s_claims,
138-
oidc_appinfo_pass_in_t pass_in, oidc_appinfo_encoding_t encoding);
139+
void oidc_userinfo_pass_as(request_rec *r, oidc_cfg_t *cfg, oidc_session_t *session, oidc_appinfo_pass_in_t pass_in,
140+
oidc_appinfo_encoding_t encoding);
139141

140142
#endif // _MOD_AUTH_OPENIDC_HANDLE_H_

src/handle/info.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -165,16 +165,16 @@ int oidc_info_request(request_rec *r, oidc_cfg_t *c, oidc_session_t *session, ap
165165

166166
/* include the id_token claims in the session info */
167167
if (apr_hash_get(oidc_cfg_info_hook_data_get(c), OIDC_HOOK_INFO_ID_TOKEN, APR_HASH_KEY_STRING)) {
168-
json_t *id_token = oidc_session_get_idtoken_claims_json(r, session);
168+
json_t *id_token = oidc_session_get_idtoken_claims(r, session);
169169
if (id_token)
170-
json_object_set_new(json, OIDC_HOOK_INFO_ID_TOKEN, id_token);
170+
json_object_set(json, OIDC_HOOK_INFO_ID_TOKEN, id_token);
171171
}
172172

173173
if (apr_hash_get(oidc_cfg_info_hook_data_get(c), OIDC_HOOK_INFO_USER_INFO, APR_HASH_KEY_STRING)) {
174174
/* include the claims from the userinfo endpoint the session info */
175-
json_t *claims = oidc_session_get_userinfo_claims_json(r, session);
175+
json_t *claims = oidc_session_get_userinfo_claims(r, session);
176176
if (claims)
177-
json_object_set_new(json, OIDC_HOOK_INFO_USER_INFO, claims);
177+
json_object_set(json, OIDC_HOOK_INFO_USER_INFO, claims);
178178
}
179179

180180
/* include the maximum session lifetime in the session info */

src/handle/refresh.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ apr_byte_t oidc_refresh_token_grant(request_rec *r, oidc_cfg_t *c, oidc_session_
273273

274274
if (oidc_jwt_parse(r->pool, s_id_token, &id_token_jwt, NULL, FALSE, &err) == TRUE) {
275275
/* store the claims payload in the id_token for later reference */
276-
oidc_session_set_idtoken_claims(r, session, id_token_jwt->payload.value.str);
276+
oidc_session_set_idtoken_claims(r, session, id_token_jwt->payload.value.json);
277277

278278
if (oidc_cfg_provider_session_max_duration_get(provider) == 0) {
279279
/* update the session expiry to match the expiry of the id_token */

src/handle/response.c

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,8 @@ char *oidc_response_make_sid_iss_unique(request_rec *r, const char *sid, const c
223223
*/
224224
apr_byte_t oidc_response_save_in_session(request_rec *r, oidc_cfg_t *c, oidc_session_t *session,
225225
oidc_provider_t *provider, const char *remoteUser, const char *id_token,
226-
oidc_jwt_t *id_token_jwt, const char *claims, const char *access_token,
226+
oidc_jwt_t *id_token_jwt, const char *s_userinfo_claims,
227+
json_t *userinfo_claims, const char *access_token,
227228
const char *access_token_type, const int expires_in, const char *refresh_token,
228229
const char *scope, const char *session_state, const char *state,
229230
const char *original_url, const char *userinfo_jwt) {
@@ -235,7 +236,7 @@ apr_byte_t oidc_response_save_in_session(request_rec *r, oidc_cfg_t *c, oidc_ses
235236
session->expiry = apr_time_now() + apr_time_from_sec(oidc_cfg_session_inactivity_timeout_get(c));
236237

237238
/* store the claims payload in the id_token for later reference */
238-
oidc_session_set_idtoken_claims(r, session, id_token_jwt->payload.value.str);
239+
oidc_session_set_idtoken_claims(r, session, id_token_jwt->payload.value.json);
239240

240241
if (oidc_cfg_store_id_token_get(c)) {
241242
/* store the compact serialized representation of the id_token for later reference */
@@ -272,7 +273,7 @@ apr_byte_t oidc_response_save_in_session(request_rec *r, oidc_cfg_t *c, oidc_ses
272273
oidc_cfg_provider_userinfo_refresh_interval_get(provider));
273274

274275
/* store claims resolved from userinfo endpoint */
275-
oidc_userinfo_store_claims(r, c, session, provider, claims, userinfo_jwt);
276+
oidc_userinfo_store_claims(r, c, session, provider, userinfo_claims, userinfo_jwt);
276277

277278
/* see if we have an access_token */
278279
if (access_token != NULL) {
@@ -485,7 +486,7 @@ static apr_byte_t oidc_response_flows(request_rec *r, oidc_cfg_t *c, oidc_proto_
485486
* set the unique user identifier that will be propagated in the Apache r->user and REMOTE_USER variables
486487
*/
487488
static apr_byte_t oidc_response_set_request_user(request_rec *r, oidc_cfg_t *c, oidc_provider_t *provider,
488-
oidc_jwt_t *jwt, const char *s_claims) {
489+
oidc_jwt_t *jwt, json_t *userinfo_claims) {
489490

490491
const char *issuer = oidc_cfg_provider_issuer_get(provider);
491492
char *claim_name = apr_pstrdup(r->pool, oidc_cfg_remote_user_claim_name_get(c));
@@ -501,13 +502,12 @@ static apr_byte_t oidc_response_set_request_user(request_rec *r, oidc_cfg_t *c,
501502
/* extract the username claim (default: "sub") from the id_token payload or user claims */
502503
apr_byte_t rc = FALSE;
503504
char *remote_user = NULL;
504-
json_t *claims = NULL;
505-
oidc_util_json_decode_object(r, s_claims, &claims);
506-
if (claims == NULL) {
505+
if (userinfo_claims == NULL) {
507506
rc = oidc_get_remote_user(r, claim_name, oidc_cfg_remote_user_claim_get(c)->reg_exp,
508507
oidc_cfg_remote_user_claim_get(c)->replace, jwt->payload.value.json,
509508
&remote_user);
510509
} else {
510+
json_t *claims = json_copy(userinfo_claims);
511511
oidc_util_json_merge(r, jwt->payload.value.json, claims);
512512
rc = oidc_get_remote_user(r, claim_name, oidc_cfg_remote_user_claim_get(c)->reg_exp,
513513
oidc_cfg_remote_user_claim_get(c)->replace, claims, &remote_user);
@@ -550,7 +550,7 @@ static int oidc_response_process(request_rec *r, oidc_cfg_t *c, oidc_session_t *
550550

551551
oidc_provider_t *provider = NULL;
552552
oidc_proto_state_t *proto_state = NULL;
553-
oidc_jwt_t *jwt = NULL;
553+
oidc_jwt_t *id_token = NULL;
554554

555555
/* see if this response came from a browser-back event */
556556
if (oidc_response_browser_back(r, apr_table_get(params, OIDC_PROTO_STATE), session) == TRUE)
@@ -593,12 +593,12 @@ static int oidc_response_process(request_rec *r, oidc_cfg_t *c, oidc_session_t *
593593
}
594594

595595
/* handle the code, implicit or hybrid flow */
596-
if (oidc_response_flows(r, c, proto_state, provider, params, response_mode, &jwt) == FALSE) {
596+
if (oidc_response_flows(r, c, proto_state, provider, params, response_mode, &id_token) == FALSE) {
597597
OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHN_RESPONSE_ERROR_PROTOCOL);
598598
return oidc_response_authorization_error(r, c, proto_state, "Error in handling response type.", NULL);
599599
}
600600

601-
if (jwt == NULL) {
601+
if (id_token == NULL) {
602602
oidc_error(r, "no id_token was provided");
603603
return oidc_response_authorization_error(r, c, proto_state, "No id_token was provided.", NULL);
604604
}
@@ -610,9 +610,10 @@ static int oidc_response_process(request_rec *r, oidc_cfg_t *c, oidc_session_t *
610610
* optionally resolve additional claims against the userinfo endpoint
611611
* parsed claims are not actually used here but need to be parsed anyway for error checking purposes
612612
*/
613-
const char *claims = oidc_userinfo_retrieve_claims(
613+
json_t *userinfo_claims = NULL;
614+
const char *s_userinfo_claims = oidc_userinfo_retrieve_claims(
614615
r, c, provider, apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN),
615-
apr_table_get(params, OIDC_PROTO_TOKEN_TYPE), NULL, jwt->payload.sub, &userinfo_jwt);
616+
apr_table_get(params, OIDC_PROTO_TOKEN_TYPE), NULL, id_token->payload.sub, &userinfo_claims, &userinfo_jwt);
616617

617618
/* restore the original protected URL that the user was trying to access */
618619
const char *original_url = oidc_proto_state_get_original_url(proto_state);
@@ -624,7 +625,7 @@ static int oidc_response_process(request_rec *r, oidc_cfg_t *c, oidc_session_t *
624625
const char *prompt = oidc_proto_state_get_prompt(proto_state);
625626

626627
/* set the user */
627-
if (oidc_response_set_request_user(r, c, provider, jwt, claims) == TRUE) {
628+
if (oidc_response_set_request_user(r, c, provider, id_token, userinfo_claims) == TRUE) {
628629

629630
/* session management: if the user in the new response is not equal to the old one, error out */
630631
if ((prompt != NULL) && (_oidc_strcmp(prompt, OIDC_PROTO_PROMPT_NONE) == 0)) {
@@ -634,36 +635,41 @@ static int oidc_response_process(request_rec *r, oidc_cfg_t *c, oidc_session_t *
634635
// if (_oidc_strcmp(sub, jwt->payload.sub) != 0) {
635636
if (_oidc_strcmp(session->remote_user, r->user) != 0) {
636637
oidc_warn(r, "user set from new id_token is different from current one");
637-
oidc_jwt_destroy(jwt);
638+
oidc_jwt_destroy(id_token);
639+
json_decref(userinfo_claims);
638640
return oidc_response_authorization_error(r, c, proto_state, "User changed!", NULL);
639641
}
640642
}
641643

642644
/* store resolved information in the session */
643645
if (oidc_response_save_in_session(
644-
r, c, session, provider, r->user, apr_table_get(params, OIDC_PROTO_ID_TOKEN), jwt, claims,
645-
apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN), apr_table_get(params, OIDC_PROTO_TOKEN_TYPE),
646-
expires_in, apr_table_get(params, OIDC_PROTO_REFRESH_TOKEN),
647-
apr_table_get(params, OIDC_PROTO_SCOPE), apr_table_get(params, OIDC_PROTO_SESSION_STATE),
648-
apr_table_get(params, OIDC_PROTO_STATE), original_url, userinfo_jwt) == FALSE) {
646+
r, c, session, provider, r->user, apr_table_get(params, OIDC_PROTO_ID_TOKEN), id_token,
647+
s_userinfo_claims, userinfo_claims, apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN),
648+
apr_table_get(params, OIDC_PROTO_TOKEN_TYPE), expires_in,
649+
apr_table_get(params, OIDC_PROTO_REFRESH_TOKEN), apr_table_get(params, OIDC_PROTO_SCOPE),
650+
apr_table_get(params, OIDC_PROTO_SESSION_STATE), apr_table_get(params, OIDC_PROTO_STATE),
651+
original_url, userinfo_jwt) == FALSE) {
649652
oidc_proto_state_destroy(proto_state);
650-
oidc_jwt_destroy(jwt);
653+
oidc_jwt_destroy(id_token);
654+
json_decref(userinfo_claims);
651655
return HTTP_INTERNAL_SERVER_ERROR;
652656
}
653657

654658
oidc_debug(r, "set remote_user to \"%s\" in new session \"%s\"", r->user, session->uuid);
655659

656660
} else {
657661
oidc_error(r, "remote user could not be set");
658-
oidc_jwt_destroy(jwt);
662+
oidc_jwt_destroy(id_token);
663+
json_decref(userinfo_claims);
659664
OIDC_METRICS_COUNTER_INC(r, c, OM_AUTHN_RESPONSE_ERROR_REMOTE_USER);
660665
return oidc_response_authorization_error(
661666
r, c, proto_state, "Remote user could not be set: contact the website administrator", NULL);
662667
}
663668

664669
/* cleanup */
665670
oidc_proto_state_destroy(proto_state);
666-
oidc_jwt_destroy(jwt);
671+
oidc_jwt_destroy(id_token);
672+
json_decref(userinfo_claims);
667673

668674
/* check that we've actually authenticated a user; functions as error handling for oidc_get_remote_user */
669675
if (r->user == NULL) {

0 commit comments

Comments
 (0)