Skip to content

Commit bd6cfa7

Browse files
committed
proto: pass the scope parameter as returned from the token endpoint
in the OIDC_scope header/environment variable and make it available for `Require claim scope:` purposes if not available as a claim returned in the id_token or userinfo endpoint Signed-off-by: Hans Zandbelt <[email protected]>
1 parent 8c4d518 commit bd6cfa7

File tree

13 files changed

+115
-43
lines changed

13 files changed

+115
-43
lines changed

ChangeLog

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
04/08/2025
2+
- proto: pass the scope parameter as returned from the token endpoint in the OIDC_scope
3+
header/environment variable and make it available for `Require claim scope:` purposes
4+
if not available as a claim returned in the id_token or userinfo endpoint
5+
16
04/07/2025
27
- allow for regular Apache processing (e.g. setting response/security headers)
38
by deferring HTML/HTTP output generation to the content handler (instead of user id check handler)

src/cache/shm.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ static apr_byte_t oidc_cache_shm_set(request_rec *r, const char *section, const
302302
if (value != NULL) {
303303

304304
/* fill out the entry with the provided data */
305-
_oidc_strncpy(t->section_key, section_key, OIDC_CACHE_SHM_KEY_MAX-1);
305+
_oidc_strncpy(t->section_key, section_key, OIDC_CACHE_SHM_KEY_MAX - 1);
306306
_oidc_strcpy(t->value, value);
307307
t->expires = expiry;
308308
t->access = current_time;

src/handle/authz.c

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,8 @@ static void oidc_authz_error_add(request_rec *r, const char *msg) {
384384
/*
385385
* get the claims and id_token from request state
386386
*/
387-
static void oidc_authz_get_claims_and_idtoken(request_rec *r, json_t **claims, json_t **id_token) {
387+
static void oidc_authz_get_claims_idtoken_scope(request_rec *r, json_t **claims, json_t **id_token,
388+
const char **scope) {
388389

389390
const char *s_claims = oidc_request_state_get(r, OIDC_REQUEST_STATE_KEY_CLAIMS);
390391
if (s_claims != NULL)
@@ -393,6 +394,8 @@ static void oidc_authz_get_claims_and_idtoken(request_rec *r, json_t **claims, j
393394
const char *s_id_token = oidc_request_state_get(r, OIDC_REQUEST_STATE_KEY_IDTOKEN);
394395
if (s_id_token != NULL)
395396
oidc_util_decode_json_object(r, s_id_token, id_token);
397+
398+
*scope = oidc_request_state_get(r, OIDC_REQUEST_STATE_KEY_SCOPE);
396399
}
397400

398401
#if HAVE_APACHE_24
@@ -537,6 +540,38 @@ static authz_status oidc_authz_24_unauthorized_user(request_rec *r) {
537540
return AUTHZ_DENIED;
538541
}
539542

543+
/*
544+
* merge the claims from the userinfo endpoint, the claims from the id_token, and the scope returned
545+
* from the userinfo endpoint into a single set of claims that can be used to authorize on
546+
*/
547+
static json_t *oidc_authz_merge_claims(request_rec *r) {
548+
json_t *claims = NULL, *id_token = NULL;
549+
const char *scope = NULL;
550+
551+
/* get the set of claims from the request state as they have been set in the authentication part earlier */
552+
oidc_authz_get_claims_idtoken_scope(r, &claims, &id_token, &scope);
553+
554+
json_t *result = json_object();
555+
556+
/* if scope was returned from the token endpoint, include it in the set of authorization claims */
557+
if (scope)
558+
json_object_set_new(result, OIDC_CLAIM_SCOPE, json_string(scope));
559+
560+
/* merge userinfo claims into the authorization claims (take precedence over scope) */
561+
if (claims)
562+
oidc_util_json_merge(r, claims, result);
563+
564+
/* merge id_token claims (e.g. "iss") into the authorization claims (take precedence over userinfo claims and
565+
* scope) */
566+
oidc_util_json_merge(r, id_token, result);
567+
568+
if (id_token)
569+
json_decref(id_token);
570+
if (claims)
571+
json_decref(claims);
572+
573+
return result;
574+
}
540575
/*
541576
* generic Apache >=2.4 authorization hook for this module
542577
* handles both OpenID Connect or OAuth 2.0 in the same way, based on the claims stored in the session
@@ -557,22 +592,14 @@ authz_status oidc_authz_24_checker(request_rec *r, const char *require_args, con
557592
}
558593

559594
/* get the set of claims from the request state (they've been set in the authentication part earlier */
560-
json_t *claims = NULL, *id_token = NULL;
561-
oidc_authz_get_claims_and_idtoken(r, &claims, &id_token);
562-
563-
/* merge id_token claims (e.g. "iss") in to claims json object */
564-
if (claims)
565-
oidc_util_json_merge(r, id_token, claims);
595+
json_t *claims = oidc_authz_merge_claims(r);
566596

567597
/* dispatch to the >=2.4 specific authz routine */
568-
authz_status rc =
569-
oidc_authz_24_worker(r, claims ? claims : id_token, require_args, parsed_require_args, match_claim_fn);
598+
authz_status rc = oidc_authz_24_worker(r, claims, require_args, parsed_require_args, match_claim_fn);
570599

571600
/* cleanup */
572601
if (claims)
573602
json_decref(claims);
574-
if (id_token)
575-
json_decref(id_token);
576603

577604
if ((rc == AUTHZ_DENIED) && ap_auth_type(r))
578605
rc = oidc_authz_24_unauthorized_user(r);
@@ -753,8 +780,7 @@ int oidc_authz_22_checker(request_rec *r) {
753780
}
754781

755782
/* get the set of claims from the request state (they've been set in the authentication part earlier */
756-
json_t *claims = NULL, *id_token = NULL;
757-
oidc_authz_get_claims_and_idtoken(r, &claims, &id_token);
783+
json_t *claims = oidc_authz_merge_claims(r);
758784

759785
/* get the Require statements */
760786
const apr_array_header_t *const reqs_arr = ap_requires(r);
@@ -766,18 +792,12 @@ int oidc_authz_22_checker(request_rec *r) {
766792
return DECLINED;
767793
}
768794

769-
/* merge id_token claims (e.g. "iss") in to claims json object */
770-
if (claims)
771-
oidc_util_json_merge(r, id_token, claims);
772-
773795
/* dispatch to the <2.4 specific authz routine */
774796
int rc = oidc_authz_22_worker(r, claims ? claims : id_token, reqs, reqs_arr->nelts);
775797

776798
/* cleanup */
777799
if (claims)
778800
json_decref(claims);
779-
if (id_token)
780-
json_decref(id_token);
781801

782802
if ((rc == HTTP_UNAUTHORIZED) && ap_auth_type(r))
783803
rc = oidc_authz_22_unauthorized_user(r);

src/handle/handle.h

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,12 +116,6 @@ apr_byte_t oidc_response_post_preserve_javascript(request_rec *r, const char *lo
116116
char *oidc_response_make_sid_iss_unique(request_rec *r, const char *sid, const char *issuer);
117117
int oidc_response_authorization_redirect(request_rec *r, oidc_cfg_t *c, oidc_session_t *session);
118118
int oidc_response_authorization_post(request_rec *r, oidc_cfg_t *c, oidc_session_t *session);
119-
apr_byte_t oidc_response_save_in_session(request_rec *r, oidc_cfg_t *c, oidc_session_t *session,
120-
oidc_provider_t *provider, const char *remoteUser, const char *id_token,
121-
oidc_jwt_t *id_token_jwt, const char *claims, const char *access_token,
122-
const char *access_token_type, const int expires_in, const char *refresh_token,
123-
const char *session_state, const char *state, const char *original_url,
124-
const char *userinfo_jwt);
125119

126120
// revoke.c
127121
int oidc_revoke_session(request_rec *r, oidc_cfg_t *c);

src/handle/refresh.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ apr_byte_t oidc_refresh_token_grant(request_rec *r, oidc_cfg_t *c, oidc_session_
202202
char *s_token_type = NULL;
203203
char *s_access_token = NULL;
204204
char *s_refresh_token = NULL;
205+
char *s_scope = NULL;
205206
oidc_jwt_t *id_token_jwt = NULL;
206207
oidc_jose_error_t err;
207208
const char *refresh_token = NULL;
@@ -227,7 +228,7 @@ apr_byte_t oidc_refresh_token_grant(request_rec *r, oidc_cfg_t *c, oidc_session_
227228

228229
/* refresh the tokens by calling the token endpoint */
229230
if (oidc_proto_token_refresh_request(r, c, provider, refresh_token, &s_id_token, &s_access_token, &s_token_type,
230-
&expires_in, &s_refresh_token) == FALSE) {
231+
&expires_in, &s_refresh_token, &s_scope) == FALSE) {
231232
OIDC_METRICS_COUNTER_INC(r, c, OM_PROVIDER_REFRESH_ERROR);
232233
oidc_error(r, "access_token could not be refreshed with refresh_token: %s", refresh_token);
233234
goto end;
@@ -259,6 +260,10 @@ apr_byte_t oidc_refresh_token_grant(request_rec *r, oidc_cfg_t *c, oidc_session_
259260
if (s_refresh_token != NULL)
260261
oidc_session_set_refresh_token(r, session, s_refresh_token);
261262

263+
/* see if a new scope was returned from the token endpoint */
264+
if (s_scope != NULL)
265+
oidc_session_set_scope(r, session, s_scope);
266+
262267
/* if we have a new id_token, store it in the session and update the session max lifetime if required */
263268
if (s_id_token != NULL) {
264269

src/handle/response.c

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -219,12 +219,12 @@ char *oidc_response_make_sid_iss_unique(request_rec *r, const char *sid, const c
219219
/*
220220
* store resolved information in the session
221221
*/
222-
apr_byte_t oidc_response_save_in_session(request_rec *r, oidc_cfg_t *c, oidc_session_t *session,
223-
oidc_provider_t *provider, const char *remoteUser, const char *id_token,
224-
oidc_jwt_t *id_token_jwt, const char *claims, const char *access_token,
225-
const char *access_token_type, const int expires_in, const char *refresh_token,
226-
const char *session_state, const char *state, const char *original_url,
227-
const char *userinfo_jwt) {
222+
static apr_byte_t oidc_response_save_in_session(request_rec *r, oidc_cfg_t *c, oidc_session_t *session,
223+
oidc_provider_t *provider, const char *remoteUser, const char *id_token,
224+
oidc_jwt_t *id_token_jwt, const char *claims, const char *access_token,
225+
const char *access_token_type, const int expires_in,
226+
const char *refresh_token, const char *scope, const char *session_state,
227+
const char *state, const char *original_url, const char *userinfo_jwt) {
228228

229229
/* store the user in the session */
230230
session->remote_user = apr_pstrdup(r->pool, remoteUser);
@@ -290,6 +290,12 @@ apr_byte_t oidc_response_save_in_session(request_rec *r, oidc_cfg_t *c, oidc_ses
290290
oidc_session_set_refresh_token(r, session, refresh_token);
291291
}
292292

293+
/* see if a scope was returned from the token endpoint */
294+
if (scope != NULL) {
295+
/* store the scope in the session context */
296+
oidc_session_set_scope(r, session, scope);
297+
}
298+
293299
/* store max session duration in the session as a hard cut-off expiry timestamp */
294300
apr_time_t session_expires =
295301
(oidc_cfg_provider_session_max_duration_get(provider) == 0)
@@ -637,8 +643,8 @@ static int oidc_response_process(request_rec *r, oidc_cfg_t *c, oidc_session_t *
637643
r, c, session, provider, r->user, apr_table_get(params, OIDC_PROTO_ID_TOKEN), jwt, claims,
638644
apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN), apr_table_get(params, OIDC_PROTO_TOKEN_TYPE),
639645
expires_in, apr_table_get(params, OIDC_PROTO_REFRESH_TOKEN),
640-
apr_table_get(params, OIDC_PROTO_SESSION_STATE), apr_table_get(params, OIDC_PROTO_STATE),
641-
original_url, userinfo_jwt) == FALSE) {
646+
apr_table_get(params, OIDC_PROTO_SCOPE), apr_table_get(params, OIDC_PROTO_SESSION_STATE),
647+
apr_table_get(params, OIDC_PROTO_STATE), original_url, userinfo_jwt) == FALSE) {
642648
oidc_proto_state_destroy(proto_state);
643649
oidc_jwt_destroy(jwt);
644650
return HTTP_INTERNAL_SERVER_ERROR;

src/mod_auth_openidc.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,7 @@ static void oidc_copy_tokens_to_request_state(request_rec *r, oidc_session_t *se
580580

581581
const char *id_token = oidc_session_get_idtoken_claims(r, session);
582582
const char *claims = oidc_session_get_userinfo_claims(r, session);
583+
const char *scope = oidc_session_get_scope(r, session);
583584

584585
oidc_debug(r, "id_token=%s claims=%s", id_token, claims);
585586

@@ -594,6 +595,9 @@ static void oidc_copy_tokens_to_request_state(request_rec *r, oidc_session_t *se
594595
if (s_claims != NULL)
595596
*s_claims = claims;
596597
}
598+
599+
if (scope != NULL)
600+
oidc_request_state_set(r, OIDC_REQUEST_STATE_KEY_SCOPE, scope);
597601
}
598602

599603
/*
@@ -637,6 +641,13 @@ apr_byte_t oidc_session_pass_tokens(request_rec *r, oidc_cfg_t *cfg, oidc_sessio
637641
OIDC_DEFAULT_HEADER_PREFIX, pass_in, encoding);
638642
}
639643

644+
/* set the scope in the app headers/variables alongside of the access token, if enabled */
645+
const char *scope = oidc_session_get_scope(r, session);
646+
if ((oidc_cfg_dir_pass_access_token_get(r) != 0) && scope != NULL) {
647+
/* pass it to the app in a header or environment variable */
648+
oidc_util_set_app_info(r, OIDC_APP_INFO_SCOPE, scope, OIDC_DEFAULT_HEADER_PREFIX, pass_in, encoding);
649+
}
650+
640651
if (extend_session) {
641652
/*
642653
* reset the session inactivity timer

src/mod_auth_openidc.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
#define OIDC_REQUEST_STATE_KEY_HTTP "hp"
6060
#define OIDC_REQUEST_STATE_KEY_HTML "hl"
6161
#define OIDC_REQUEST_STATE_KEY_IDTOKEN "i"
62+
#define OIDC_REQUEST_STATE_KEY_SCOPE "sc"
6263
#define OIDC_REQUEST_STATE_KEY_AUTHN_PRESERVE "p"
6364
#define OIDC_REQUEST_STATE_KEY_SAVE "s"
6465
#define OIDC_REQUEST_STATE_TRACE_ID "t"
@@ -124,8 +125,10 @@
124125
#define OIDC_CLAIM_HTM "htm"
125126
#define OIDC_CLAIM_HTU "htu"
126127
#define OIDC_CLAIM_ATH "ath"
128+
#define OIDC_CLAIM_SCOPE "scope"
127129

128130
#define OIDC_APP_INFO_REFRESH_TOKEN "refresh_token"
131+
#define OIDC_APP_INFO_SCOPE "scope"
129132
#define OIDC_APP_INFO_ACCESS_TOKEN "access_token"
130133
#define OIDC_APP_INFO_ACCESS_TOKEN_TYPE "access_token_type"
131134
#define OIDC_APP_INFO_ACCESS_TOKEN_EXP "access_token_expires"

src/proto/proto.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,10 +254,10 @@ void oidc_proto_state_set_timestamp_now(oidc_proto_state_t *proto_state);
254254
// token.c
255255
apr_byte_t oidc_proto_token_endpoint_request(request_rec *r, oidc_cfg_t *cfg, oidc_provider_t *provider,
256256
apr_table_t *params, char **id_token, char **access_token,
257-
char **token_type, int *expires_in, char **refresh_token);
257+
char **token_type, int *expires_in, char **refresh_token, char **scope);
258258
apr_byte_t oidc_proto_token_refresh_request(request_rec *r, oidc_cfg_t *cfg, oidc_provider_t *provider,
259259
const char *rtoken, char **id_token, char **access_token, char **token_type,
260-
int *expires_in, char **refresh_token);
260+
int *expires_in, char **refresh_token, char **scope);
261261

262262
// userinfo.c
263263
apr_byte_t oidc_proto_userinfo_request(request_rec *r, oidc_cfg_t *cfg, oidc_provider_t *provider,

src/proto/response.c

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,8 @@ static apr_byte_t oidc_proto_parse_idtoken_and_validate_code(request_rec *r, oid
292292
*/
293293
static apr_byte_t oidc_proto_resolve_code(request_rec *r, oidc_cfg_t *cfg, oidc_provider_t *provider, const char *code,
294294
const char *code_verifier, char **id_token, char **access_token,
295-
char **token_type, int *expires_in, char **refresh_token, const char *state) {
295+
char **token_type, int *expires_in, char **refresh_token, char **scope,
296+
const char *state) {
296297

297298
oidc_debug(r, "enter");
298299

@@ -310,7 +311,7 @@ static apr_byte_t oidc_proto_resolve_code(request_rec *r, oidc_cfg_t *cfg, oidc_
310311
apr_table_setn(params, OIDC_PROTO_STATE, state);
311312

312313
return oidc_proto_token_endpoint_request(r, cfg, provider, params, id_token, access_token, token_type,
313-
expires_in, refresh_token);
314+
expires_in, refresh_token, scope);
314315
}
315316

316317
/*
@@ -326,6 +327,7 @@ static apr_byte_t oidc_proto_resolve_code_and_validate_response(request_rec *r,
326327
int expires_in = -1;
327328
char *refresh_token = NULL;
328329
char *code_verifier = NULL;
330+
char *scope = NULL;
329331

330332
if (oidc_proto_profile_pkce_get(provider) != &oidc_pkce_none)
331333
oidc_proto_profile_pkce_get(provider)->verifier(r, oidc_proto_state_get_pkce_state(proto_state),
@@ -334,7 +336,7 @@ static apr_byte_t oidc_proto_resolve_code_and_validate_response(request_rec *r,
334336
const char *state = oidc_proto_state_get_state(proto_state);
335337

336338
if (oidc_proto_resolve_code(r, c, provider, apr_table_get(params, OIDC_PROTO_CODE), code_verifier, &id_token,
337-
&access_token, &token_type, &expires_in, &refresh_token, state) == FALSE) {
339+
&access_token, &token_type, &expires_in, &refresh_token, &scope, state) == FALSE) {
338340
oidc_error(r, "failed to resolve the code");
339341
OIDC_METRICS_COUNTER_INC(r, c, OM_PROVIDER_TOKEN_ERROR);
340342
return FALSE;
@@ -364,6 +366,10 @@ static apr_byte_t oidc_proto_resolve_code_and_validate_response(request_rec *r,
364366
apr_table_set(params, OIDC_PROTO_REFRESH_TOKEN, refresh_token);
365367
}
366368

369+
if (scope != NULL) {
370+
apr_table_set(params, OIDC_PROTO_SCOPE, scope);
371+
}
372+
367373
return TRUE;
368374
}
369375

@@ -393,6 +399,7 @@ apr_byte_t oidc_proto_response_code_idtoken(request_rec *r, oidc_cfg_t *c, oidc_
393399
apr_table_unset(params, OIDC_PROTO_TOKEN_TYPE);
394400
apr_table_unset(params, OIDC_PROTO_EXPIRES_IN);
395401
apr_table_unset(params, OIDC_PROTO_REFRESH_TOKEN);
402+
apr_table_unset(params, OIDC_PROTO_SCOPE);
396403

397404
if (oidc_proto_resolve_code_and_validate_response(r, c, provider, response_type, params, proto_state) == FALSE)
398405
return FALSE;
@@ -420,6 +427,7 @@ apr_byte_t oidc_proto_response_code_token(request_rec *r, oidc_cfg_t *c, oidc_pr
420427
/* clear parameters that should only be set from the token endpoint */
421428
apr_table_unset(params, OIDC_PROTO_ID_TOKEN);
422429
apr_table_unset(params, OIDC_PROTO_REFRESH_TOKEN);
430+
apr_table_unset(params, OIDC_PROTO_SCOPE);
423431

424432
if (oidc_proto_resolve_code_and_validate_response(r, c, provider, response_type, params, proto_state) == FALSE)
425433
return FALSE;
@@ -454,6 +462,7 @@ apr_byte_t oidc_proto_response_code(request_rec *r, oidc_cfg_t *c, oidc_proto_st
454462
apr_table_unset(params, OIDC_PROTO_EXPIRES_IN);
455463
apr_table_unset(params, OIDC_PROTO_ID_TOKEN);
456464
apr_table_unset(params, OIDC_PROTO_REFRESH_TOKEN);
465+
apr_table_unset(params, OIDC_PROTO_SCOPE);
457466

458467
if (oidc_proto_resolve_code_and_validate_response(r, c, provider, response_type, params, proto_state) == FALSE)
459468
return FALSE;
@@ -520,6 +529,7 @@ apr_byte_t oidc_proto_response_code_idtoken_token(request_rec *r, oidc_cfg_t *c,
520529

521530
/* clear parameters that should only be set from the token endpoint */
522531
apr_table_unset(params, OIDC_PROTO_REFRESH_TOKEN);
532+
apr_table_unset(params, OIDC_PROTO_SCOPE);
523533

524534
if (oidc_proto_resolve_code_and_validate_response(r, c, provider, response_type, params, proto_state) == FALSE)
525535
return FALSE;

0 commit comments

Comments
 (0)