Blame SOURCES/0020-prevent-open-redirect-on-refresh-token-requests-rele.patch

5f8edb
From e4d625b0f1116e50df2737e2738b4c77b35328bb Mon Sep 17 00:00:00 2001
5f8edb
From: Hans Zandbelt <hans.zandbelt@zmartzone.eu>
5f8edb
Date: Wed, 10 Jun 2020 18:14:24 +0200
5f8edb
Subject: [PATCH 1/4] prevent open redirect on refresh token requests; release
5f8edb
 2.4.3
5f8edb
5f8edb
add new OIDCRedirectURLsAllowed primitive to handle post logout and
5f8edb
refresh-return-to validation; addresses #453; closes #466
5f8edb
5f8edb
Signed-off-by: Hans Zandbelt <hans.zandbelt@zmartzone.eu>
5f8edb
(cherry picked from commit 8ea550f34ce51d8d41ba47843739c964407fa0ad)
5f8edb
---
5f8edb
 auth_openidc.conf      |   9 ++
5f8edb
 src/config.c           |  24 ++++-
5f8edb
 src/mod_auth_openidc.c | 205 ++++++++++++++++++++++++-----------------
5f8edb
 src/mod_auth_openidc.h |   1 +
5f8edb
 src/util.c             |  16 ++--
5f8edb
 5 files changed, 166 insertions(+), 89 deletions(-)
5f8edb
5f8edb
diff --git a/auth_openidc.conf b/auth_openidc.conf
5f8edb
index ce2fba7..87685f6 100644
5f8edb
--- a/auth_openidc.conf
5f8edb
+++ b/auth_openidc.conf
5f8edb
@@ -784,3 +784,12 @@
5f8edb
 # for calculating the fingerprint of the state during authentication.
5f8edb
 # When not defined the default "both" is used.
5f8edb
 #OIDCStateInputHeaders [none|user-agent|x-forwarded-for|both]
5f8edb
+
5f8edb
+# Define one or more regular expressions that specify URLs (or domains) allowed for post logout and
5f8edb
+# other redirects such as the "return_to" value on refresh token requests, e.g.:
5f8edb
+#   OIDCRedirectURLsAllowed ^https://www.example.com ^https://(\w+).example.org ^https://example.net/app
5f8edb
+# or:
5f8edb
+#   OIDCRedirectURLsAllowed ^https://www.example.com/logout$ ^https://www.example.com/app/return_to$ 
5f8edb
+# When not defined, the default is to match the hostname in the URL redirected to against
5f8edb
+# the hostname in the current request.
5f8edb
+#OIDCRedirectURLsAllowed [<regexp>]+
5f8edb
diff --git a/src/config.c b/src/config.c
5f8edb
index 588e1a3..b2c0da7 100644
5f8edb
--- a/src/config.c
5f8edb
+++ b/src/config.c
5f8edb
@@ -264,6 +264,7 @@
5f8edb
 #define OIDCBlackListedClaims                "OIDCBlackListedClaims"
5f8edb
 #define OIDCOAuthServerMetadataURL           "OIDCOAuthServerMetadataURL"
5f8edb
 #define OIDCStateInputHeaders                  "OIDCStateInputHeaders"
5f8edb
+#define OIDCRedirectURLsAllowed                "OIDCRedirectURLsAllowed"
5f8edb
 
5f8edb
 extern module AP_MODULE_DECLARE_DATA auth_openidc_module;
5f8edb
 
5f8edb
@@ -1044,6 +1045,16 @@ static const char * oidc_set_state_input_headers_as(cmd_parms *cmd, void *m,
5f8edb
 	return OIDC_CONFIG_DIR_RV(cmd, rv);
5f8edb
 }
5f8edb
 
5f8edb
+static const char * oidc_set_redirect_urls_allowed(cmd_parms *cmd, void *m,
5f8edb
+               const char *arg) {
5f8edb
+    oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
5f8edb
+        cmd->server->module_config, &auth_openidc_module);
5f8edb
+    if (cfg->redirect_urls_allowed == NULL)
5f8edb
+        cfg->redirect_urls_allowed = apr_hash_make(cmd->pool);
5f8edb
+    apr_hash_set(cfg->redirect_urls_allowed, arg, APR_HASH_KEY_STRING, arg);
5f8edb
+    return NULL;
5f8edb
+}
5f8edb
+
5f8edb
 /*
5f8edb
  * create a new server config record with defaults
5f8edb
  */
5f8edb
@@ -1192,6 +1203,8 @@ void *oidc_create_server_config(apr_pool_t *pool, server_rec *svr) {
5f8edb
 
5f8edb
 	c->state_input_headers = OIDC_DEFAULT_STATE_INPUT_HEADERS;
5f8edb
 
5f8edb
+	c->redirect_urls_allowed = NULL;
5f8edb
+
5f8edb
 	return c;
5f8edb
 }
5f8edb
 
5f8edb
@@ -1637,6 +1650,10 @@ void *oidc_merge_server_config(apr_pool_t *pool, void *BASE, void *ADD) {
5f8edb
 			add->state_input_headers != OIDC_DEFAULT_STATE_INPUT_HEADERS ?
5f8edb
 					add->state_input_headers : base->state_input_headers;
5f8edb
 
5f8edb
+	c->redirect_urls_allowed =
5f8edb
+			add->redirect_urls_allowed != NULL ?
5f8edb
+					add->redirect_urls_allowed : base->redirect_urls_allowed;
5f8edb
+
5f8edb
 	return c;
5f8edb
 }
5f8edb
 
5f8edb
@@ -2310,7 +2327,7 @@ void oidc_register_hooks(apr_pool_t *pool) {
5f8edb
 	ap_register_auth_provider(pool, AUTHZ_PROVIDER_GROUP,
5f8edb
 			OIDC_REQUIRE_CLAIM_NAME, "0", &oidc_authz_claim_provider,
5f8edb
 			AP_AUTH_INTERNAL_PER_CONF);
5f8edb
-#ifdef USE_LIBJQ			
5f8edb
+#ifdef USE_LIBJQ
5f8edb
 	ap_register_auth_provider(pool, AUTHZ_PROVIDER_GROUP,
5f8edb
 			OIDC_REQUIRE_CLAIMS_EXPR_NAME, "0",
5f8edb
 			&oidc_authz_claims_expr_provider, AP_AUTH_INTERNAL_PER_CONF);
5f8edb
@@ -2891,5 +2908,10 @@ const command_rec oidc_config_cmds[] = {
5f8edb
 				NULL,
5f8edb
 				RSRC_CONF,
5f8edb
 				"Specify header name which is used as the input for calculating the fingerprint of the state during authentication; must be one of \"none\", \"user-agent\", \"x-forwarded-for\" or \"both\" (default)."),
5f8edb
+		AP_INIT_ITERATE(OIDCRedirectURLsAllowed,
5f8edb
+				oidc_set_redirect_urls_allowed,
5f8edb
+				(void *) APR_OFFSETOF(oidc_cfg, redirect_urls_allowed),
5f8edb
+				RSRC_CONF|ACCESS_CONF|OR_AUTHCFG,
5f8edb
+				"Specify one or more regular expressions that define URLs allowed for post logout and other redirects."),
5f8edb
 		{ NULL }
5f8edb
 };
5f8edb
diff --git a/src/mod_auth_openidc.c b/src/mod_auth_openidc.c
5f8edb
index 68fa2ce..215ed5e 100644
5f8edb
--- a/src/mod_auth_openidc.c
5f8edb
+++ b/src/mod_auth_openidc.c
5f8edb
@@ -226,7 +226,8 @@ void oidc_strip_cookies(request_rec *r) {
5f8edb
 /*
5f8edb
  * calculates a hash value based on request fingerprint plus a provided nonce string.
5f8edb
  */
5f8edb
-static char *oidc_get_browser_state_hash(request_rec *r, oidc_cfg *c, const char *nonce) {
5f8edb
+static char *oidc_get_browser_state_hash(request_rec *r, oidc_cfg *c,
5f8edb
+		const char *nonce) {
5f8edb
 
5f8edb
 	oidc_debug(r, "enter");
5f8edb
 
5f8edb
@@ -543,14 +544,13 @@ static apr_byte_t oidc_unsolicited_proto_state(request_rec *r, oidc_cfg *c,
5f8edb
 	oidc_jose_error_t err;
5f8edb
 	oidc_jwk_t *jwk = NULL;
5f8edb
 	if (oidc_util_create_symmetric_key(r, c->provider.client_secret,
5f8edb
-			oidc_alg2keysize(alg), OIDC_JOSE_ALG_SHA256,
5f8edb
-			TRUE, &jwk) == FALSE)
5f8edb
+			oidc_alg2keysize(alg), OIDC_JOSE_ALG_SHA256, TRUE, &jwk) == FALSE)
5f8edb
 		return FALSE;
5f8edb
 
5f8edb
 	oidc_jwt_t *jwt = NULL;
5f8edb
 	if (oidc_jwt_parse(r->pool, state, &jwt,
5f8edb
-			oidc_util_merge_symmetric_key(r->pool, c->private_keys, jwk),
5f8edb
-			&err) == FALSE) {
5f8edb
+			oidc_util_merge_symmetric_key(r->pool, c->private_keys, jwk), &err)
5f8edb
+			== FALSE) {
5f8edb
 		oidc_error(r,
5f8edb
 				"could not parse JWT from state: invalid unsolicited response: %s",
5f8edb
 				oidc_jose_e2s(r->pool, err));
5f8edb
@@ -600,8 +600,7 @@ static apr_byte_t oidc_unsolicited_proto_state(request_rec *r, oidc_cfg *c,
5f8edb
 
5f8edb
 	char *target_link_uri = NULL;
5f8edb
 	oidc_jose_get_string(r->pool, jwt->payload.value.json,
5f8edb
-			OIDC_CLAIM_TARGET_LINK_URI,
5f8edb
-			FALSE, &target_link_uri, NULL);
5f8edb
+			OIDC_CLAIM_TARGET_LINK_URI, FALSE, &target_link_uri, NULL);
5f8edb
 	if (target_link_uri == NULL) {
5f8edb
 		if (c->default_sso_url == NULL) {
5f8edb
 			oidc_error(r,
5f8edb
@@ -1224,8 +1223,8 @@ static apr_byte_t oidc_refresh_access_token(request_rec *r, oidc_cfg *c,
5f8edb
 
5f8edb
 	/* refresh the tokens by calling the token endpoint */
5f8edb
 	if (oidc_proto_refresh_request(r, c, provider, refresh_token, &s_id_token,
5f8edb
-			&s_access_token, &s_token_type, &expires_in,
5f8edb
-			&s_refresh_token) == FALSE) {
5f8edb
+			&s_access_token, &s_token_type, &expires_in, &s_refresh_token)
5f8edb
+			== FALSE) {
5f8edb
 		oidc_error(r, "access_token could not be refreshed");
5f8edb
 		return FALSE;
5f8edb
 	}
5f8edb
@@ -2286,8 +2285,8 @@ static int oidc_discovery(request_rec *r, oidc_cfg *cfg) {
5f8edb
 	char *javascript = NULL, *javascript_method = NULL;
5f8edb
 	char *html_head =
5f8edb
 			"<style type=\"text/css\">body {text-align: center}</style>";
5f8edb
-	if (oidc_post_preserve_javascript(r, NULL, &javascript,
5f8edb
-			&javascript_method) == TRUE)
5f8edb
+	if (oidc_post_preserve_javascript(r, NULL, &javascript, &javascript_method)
5f8edb
+			== TRUE)
5f8edb
 		html_head = apr_psprintf(r->pool, "%s%s", html_head, javascript);
5f8edb
 
5f8edb
 	/* now send the HTML contents to the user agent */
5f8edb
@@ -2531,8 +2530,8 @@ static int oidc_handle_discovery_response(request_rec *r, oidc_cfg *c) {
5f8edb
 	}
5f8edb
 
5f8edb
 	/* do open redirect prevention */
5f8edb
-	if (oidc_target_link_uri_matches_configuration(r, c,
5f8edb
-			target_link_uri) == FALSE) {
5f8edb
+	if (oidc_target_link_uri_matches_configuration(r, c, target_link_uri)
5f8edb
+			== FALSE) {
5f8edb
 		return oidc_util_html_send_error(r, c->error_template,
5f8edb
 				"Invalid Request",
5f8edb
 				"\"target_link_uri\" parameter does not match configuration settings, aborting to prevent an open redirect.",
5f8edb
@@ -2587,7 +2586,8 @@ static int oidc_handle_discovery_response(request_rec *r, oidc_cfg *c) {
5f8edb
 		}
5f8edb
 
5f8edb
 		/* got an account name as input, perform OP discovery with that */
5f8edb
-		if (oidc_proto_account_based_discovery(r, c, issuer, &issuer) == FALSE) {
5f8edb
+		if (oidc_proto_account_based_discovery(r, c, issuer, &issuer)
5f8edb
+				== FALSE) {
5f8edb
 
5f8edb
 			/* something did not work out, show a user facing error */
5f8edb
 			return oidc_util_html_send_error(r, c->error_template,
5f8edb
@@ -2687,66 +2687,84 @@ static int oidc_handle_logout_request(request_rec *r, oidc_cfg *c,
5f8edb
 	return HTTP_MOVED_TEMPORARILY;
5f8edb
 }
5f8edb
 
5f8edb
-static apr_byte_t oidc_validate_post_logout_url(request_rec *r, const char *url, char **err_str, char **err_desc) {
5f8edb
-       apr_uri_t uri;
5f8edb
-       const char *c_host = NULL;
5f8edb
-
5f8edb
-       if (apr_uri_parse(r->pool, url, &uri) != APR_SUCCESS) {
5f8edb
-               *err_str = apr_pstrdup(r->pool, "Malformed URL");
5f8edb
-               *err_desc = apr_psprintf(r->pool, "Logout URL malformed: %s", url);
5f8edb
-               oidc_error(r, "%s: %s", *err_str, *err_desc);
5f8edb
-               return FALSE;
5f8edb
-       }
5f8edb
-
5f8edb
-       c_host = oidc_get_current_url_host(r);
5f8edb
-       if ((uri.hostname != NULL)
5f8edb
-                       && ((strstr(c_host, uri.hostname) == NULL)
5f8edb
-                                       || (strstr(uri.hostname, c_host) == NULL))) {
5f8edb
-               *err_str = apr_pstrdup(r->pool, "Invalid Request");
5f8edb
-               *err_desc =
5f8edb
-                               apr_psprintf(r->pool,
5f8edb
-                                               "logout value \"%s\" does not match the hostname of the current request \"%s\"",
5f8edb
-                                               apr_uri_unparse(r->pool, &uri, 0), c_host);
5f8edb
-               oidc_error(r, "%s: %s", *err_str, *err_desc);
5f8edb
-               return FALSE;
5f8edb
-       } else if ((uri.hostname == NULL) && (strstr(url, "/") != url)) {
5f8edb
-               *err_str = apr_pstrdup(r->pool, "Malformed URL");
5f8edb
-               *err_desc =
5f8edb
-                               apr_psprintf(r->pool,
5f8edb
-                                               "No hostname was parsed and it does not seem to be relative, i.e starting with '/': %s",
5f8edb
-                                               url);
5f8edb
-               oidc_error(r, "%s: %s", *err_str, *err_desc);
5f8edb
-               return FALSE;
5f8edb
-        } else if ((uri.hostname == NULL) && (strstr(url, "//") == url)) {
5f8edb
-                *err_str = apr_pstrdup(r->pool, "Malformed URL");
5f8edb
-                *err_desc =
5f8edb
-                                apr_psprintf(r->pool,
5f8edb
-                                                "No hostname was parsed and starting with '//': %s",
5f8edb
-                                                url);
5f8edb
-                oidc_error(r, "%s: %s", *err_str, *err_desc);
5f8edb
-                return FALSE;
5f8edb
-        } else if ((uri.hostname == NULL) && (strstr(url, "/\\") == url)) {
5f8edb
-                *err_str = apr_pstrdup(r->pool, "Malformed URL");
5f8edb
-                *err_desc =
5f8edb
-                                apr_psprintf(r->pool,
5f8edb
-                                                "No hostname was parsed and starting with '/\\': %s",
5f8edb
-                                                url);
5f8edb
-                oidc_error(r, "%s: %s", *err_str, *err_desc);
5f8edb
-                return FALSE;
5f8edb
-       }
5f8edb
-
5f8edb
-       /* validate the URL to prevent HTTP header splitting */
5f8edb
-       if (((strstr(url, "\n") != NULL) || strstr(url, "\r") != NULL)) {
5f8edb
-               *err_str = apr_pstrdup(r->pool, "Invalid Request");
5f8edb
-               *err_desc =
5f8edb
-                               apr_psprintf(r->pool,
5f8edb
-                                               "logout value \"%s\" contains illegal \"\n\" or \"\r\" character(s)",
5f8edb
-                                               url);
5f8edb
-               oidc_error(r, "%s: %s", *err_str, *err_desc);
5f8edb
-               return FALSE;
5f8edb
-       }
5f8edb
-
5f8edb
-       return TRUE;
5f8edb
+static apr_byte_t oidc_validate_redirect_url(request_rec *r, oidc_cfg *c,
5f8edb
+		const char *url, char **err_str, char **err_desc) {
5f8edb
+	apr_uri_t uri;
5f8edb
+	const char *c_host = NULL;
5f8edb
+	apr_hash_index_t *hi = NULL;
5f8edb
+
5f8edb
+	if (apr_uri_parse(r->pool, url, &uri) != APR_SUCCESS) {
5f8edb
+		*err_str = apr_pstrdup(r->pool, "Malformed URL");
5f8edb
+		*err_desc = apr_psprintf(r->pool, "not a valid URL value: %s", url);
5f8edb
+		oidc_error(r, "%s: %s", *err_str, *err_desc);
5f8edb
+		return FALSE;
5f8edb
+	}
5f8edb
+
5f8edb
+	if (c->redirect_urls_allowed != NULL) {
5f8edb
+		for (hi = apr_hash_first(NULL, c->redirect_urls_allowed); hi; hi =
5f8edb
+				apr_hash_next(hi)) {
5f8edb
+			apr_hash_this(hi, (const void**) &c_host, NULL, NULL);
5f8edb
+			if (oidc_util_regexp_first_match(r->pool, url, c_host,
5f8edb
+					NULL, err_str) == TRUE)
5f8edb
+				break;
5f8edb
+		}
5f8edb
+		if (hi == NULL) {
5f8edb
+			*err_str = apr_pstrdup(r->pool, "URL not allowed");
5f8edb
+			*err_desc =
5f8edb
+					apr_psprintf(r->pool,
5f8edb
+							"value does not match the list of allowed redirect URLs: %s",
5f8edb
+							url);
5f8edb
+			oidc_error(r, "%s: %s", *err_str, *err_desc);
5f8edb
+			return FALSE;
5f8edb
+		}
5f8edb
+	} else if (uri.hostname != NULL) {
5f8edb
+		c_host = oidc_get_current_url_host(r);
5f8edb
+		if ((strstr(c_host, uri.hostname) == NULL)
5f8edb
+				|| (strstr(uri.hostname, c_host) == NULL)) {
5f8edb
+			*err_str = apr_pstrdup(r->pool, "Invalid Request");
5f8edb
+			*err_desc =
5f8edb
+					apr_psprintf(r->pool,
5f8edb
+							"URL value \"%s\" does not match the hostname of the current request \"%s\"",
5f8edb
+							apr_uri_unparse(r->pool, &uri, 0), c_host);
5f8edb
+			oidc_error(r, "%s: %s", *err_str, *err_desc);
5f8edb
+			return FALSE;
5f8edb
+		}
5f8edb
+	}
5f8edb
+
5f8edb
+	if ((uri.hostname == NULL) && (strstr(url, "/") != url)) {
5f8edb
+		*err_str = apr_pstrdup(r->pool, "Malformed URL");
5f8edb
+		*err_desc =
5f8edb
+				apr_psprintf(r->pool,
5f8edb
+						"No hostname was parsed and it does not seem to be relative, i.e starting with '/': %s",
5f8edb
+						url);
5f8edb
+		oidc_error(r, "%s: %s", *err_str, *err_desc);
5f8edb
+		return FALSE;
5f8edb
+	} else if ((uri.hostname == NULL) && (strstr(url, "//") == url)) {
5f8edb
+		*err_str = apr_pstrdup(r->pool, "Malformed URL");
5f8edb
+		*err_desc = apr_psprintf(r->pool,
5f8edb
+				"No hostname was parsed and starting with '//': %s", url);
5f8edb
+		oidc_error(r, "%s: %s", *err_str, *err_desc);
5f8edb
+		return FALSE;
5f8edb
+	} else if ((uri.hostname == NULL) && (strstr(url, "/\\") == url)) {
5f8edb
+		*err_str = apr_pstrdup(r->pool, "Malformed URL");
5f8edb
+		*err_desc = apr_psprintf(r->pool,
5f8edb
+				"No hostname was parsed and starting with '/\\': %s", url);
5f8edb
+		oidc_error(r, "%s: %s", *err_str, *err_desc);
5f8edb
+		return FALSE;
5f8edb
+	}
5f8edb
+
5f8edb
+	/* validate the URL to prevent HTTP header splitting */
5f8edb
+	if (((strstr(url, "\n") != NULL) || strstr(url, "\r") != NULL)) {
5f8edb
+		*err_str = apr_pstrdup(r->pool, "Invalid URL");
5f8edb
+		*err_desc =
5f8edb
+				apr_psprintf(r->pool,
5f8edb
+						"URL value \"%s\" contains illegal \"\n\" or \"\r\" character(s)",
5f8edb
+						url);
5f8edb
+		oidc_error(r, "%s: %s", *err_str, *err_desc);
5f8edb
+		return FALSE;
5f8edb
+	}
5f8edb
+
5f8edb
+    return TRUE;
5f8edb
 }
5f8edb
 
5f8edb
 /*
5f8edb
@@ -2774,12 +2792,11 @@ static int oidc_handle_logout(request_rec *r, oidc_cfg *c,
5f8edb
 	} else {
5f8edb
 
5f8edb
 		/* do input validation on the logout parameter value */
5f8edb
-
5f8edb
-		if (oidc_validate_post_logout_url(r, url, &error_str,
5f8edb
-			&error_description) == FALSE) {
5f8edb
-		return oidc_util_html_send_error(r, c->error_template, error_str,
5f8edb
-			error_description,
5f8edb
-			HTTP_BAD_REQUEST); 
5f8edb
+		if (oidc_validate_redirect_url(r, c, url, &error_str,
5f8edb
+				&error_description) == FALSE) {
5f8edb
+			return oidc_util_html_send_error(r, c->error_template, error_str,
5f8edb
+					error_description,
5f8edb
+					HTTP_BAD_REQUEST);
5f8edb
 		}
5f8edb
 	}
5f8edb
 
5f8edb
@@ -3027,6 +3044,8 @@ static int oidc_handle_refresh_token_request(request_rec *r, oidc_cfg *c,
5f8edb
 	char *return_to = NULL;
5f8edb
 	char *r_access_token = NULL;
5f8edb
 	char *error_code = NULL;
5f8edb
+	char *error_str = NULL;
5f8edb
+	char *error_description = NULL;
5f8edb
 
5f8edb
 	/* get the command passed to the session management handler */
5f8edb
 	oidc_util_get_request_parameter(r, OIDC_REDIRECT_URI_REQUEST_REFRESH,
5f8edb
@@ -3041,6 +3060,14 @@ static int oidc_handle_refresh_token_request(request_rec *r, oidc_cfg *c,
5f8edb
 		return HTTP_INTERNAL_SERVER_ERROR;
5f8edb
 	}
5f8edb
 
5f8edb
+	/* do input validation on the return to parameter value */
5f8edb
+	if (oidc_validate_redirect_url(r, c, return_to, &error_str,
5f8edb
+			&error_description) == FALSE) {
5f8edb
+		oidc_error(r, "return_to URL validation failed: %s: %s", error_str,
5f8edb
+				error_description);
5f8edb
+		return HTTP_INTERNAL_SERVER_ERROR;
5f8edb
+	}
5f8edb
+
5f8edb
 	if (r_access_token == NULL) {
5f8edb
 		oidc_error(r,
5f8edb
 				"refresh token request handler called with no access_token parameter");
5f8edb
@@ -3201,8 +3228,8 @@ static int oidc_handle_info_request(request_rec *r, oidc_cfg *c,
5f8edb
 
5f8edb
 				/* get the current provider info */
5f8edb
 				oidc_provider_t *provider = NULL;
5f8edb
-				if (oidc_get_provider_from_session(r, c, session,
5f8edb
-						&provider) == FALSE)
5f8edb
+				if (oidc_get_provider_from_session(r, c, session, &provider)
5f8edb
+						== FALSE)
5f8edb
 					return HTTP_INTERNAL_SERVER_ERROR;
5f8edb
 
5f8edb
 				/* execute the actual refresh grant */
5f8edb
@@ -3319,6 +3346,20 @@ int oidc_handle_redirect_uri_request(request_rec *r, oidc_cfg *c,
5f8edb
 		/* this is an authorization response from the OP using the Basic Client profile or a Hybrid flow*/
5f8edb
 		return oidc_handle_redirect_authorization_response(r, c, session);
5f8edb
 
5f8edb
+		/*
5f8edb
+		 *
5f8edb
+		 * Note that we are checking for logout *before* checking for a POST authorization response
5f8edb
+		 * to handle backchannel POST-based logout
5f8edb
+		 *
5f8edb
+		 * so any POST to the Redirect URI that does not have a logout query parameter will be handled
5f8edb
+		 * as an authorization response; alternatively we could assume that a POST response has no
5f8edb
+		 * parameters
5f8edb
+		 */
5f8edb
+	} else if (oidc_util_request_has_parameter(r,
5f8edb
+			OIDC_REDIRECT_URI_REQUEST_LOGOUT)) {
5f8edb
+		/* handle logout */
5f8edb
+		return oidc_handle_logout(r, c, session);
5f8edb
+
5f8edb
 	} else if (oidc_proto_is_post_authorization_response(r, c)) {
5f8edb
 
5f8edb
 		/* this is an authorization response using the fragment(+POST) response_mode with the Implicit Client profile */
5f8edb
diff --git a/src/mod_auth_openidc.h b/src/mod_auth_openidc.h
5f8edb
index 6821d0c..ea79e6b 100644
5f8edb
--- a/src/mod_auth_openidc.h
5f8edb
+++ b/src/mod_auth_openidc.h
5f8edb
@@ -414,6 +414,7 @@ typedef struct oidc_cfg {
5f8edb
 
5f8edb
 	apr_byte_t state_input_headers;
5f8edb
 
5f8edb
+	apr_hash_t *redirect_urls_allowed;
5f8edb
 } oidc_cfg;
5f8edb
 
5f8edb
 int oidc_check_user_id(request_rec *r);
5f8edb
diff --git a/src/util.c b/src/util.c
5f8edb
index c1fa5f3..4b4e16b 100644
5f8edb
--- a/src/util.c
5f8edb
+++ b/src/util.c
5f8edb
@@ -2201,14 +2201,18 @@ apr_byte_t oidc_util_regexp_first_match(apr_pool_t *pool, const char *input,
5f8edb
 		goto out;
5f8edb
 	}
5f8edb
 
5f8edb
-	if (pcre_get_substring(input, subStr, rc, OIDC_UTIL_REGEXP_MATCH_NR,
5f8edb
-			&(psubStrMatchStr)) <= 0) {
5f8edb
-		*error_str = apr_psprintf(pool, "pcre_get_substring failed (rc=%d)",
5f8edb
-				rc);
5f8edb
-		goto out;
5f8edb
+	if (output) {
5f8edb
+
5f8edb
+		if (pcre_get_substring(input, subStr, rc, OIDC_UTIL_REGEXP_MATCH_NR,
5f8edb
+				&(psubStrMatchStr)) <= 0) {
5f8edb
+			*error_str = apr_psprintf(pool, "pcre_get_substring failed (rc=%d)",
5f8edb
+					rc);
5f8edb
+			goto out;
5f8edb
+		}
5f8edb
+
5f8edb
+		*output = apr_pstrdup(pool, psubStrMatchStr);
5f8edb
 	}
5f8edb
 
5f8edb
-	*output = apr_pstrdup(pool, psubStrMatchStr);
5f8edb
 	rv = TRUE;
5f8edb
 
5f8edb
 out:
5f8edb
-- 
5f8edb
2.31.1
5f8edb