diff --git a/SOURCES/0002-Backport-of-improve-validation-of-the-post-logout-UR.patch b/SOURCES/0002-Backport-of-improve-validation-of-the-post-logout-UR.patch
new file mode 100644
index 0000000..8b68923
--- /dev/null
+++ b/SOURCES/0002-Backport-of-improve-validation-of-the-post-logout-UR.patch
@@ -0,0 +1,127 @@
+From cb5560f016d4f8bbca40670c59898afafb8d0763 Mon Sep 17 00:00:00 2001
+From: Jakub Hrozek <jhrozek@redhat.com>
+Date: Sun, 10 May 2020 19:56:53 +0200
+Subject: [PATCH] Backport of improve validation of the post-logout URL
+
+---
+ src/mod_auth_openidc.c | 90 +++++++++++++++++++++++++-----------------
+ 1 file changed, 53 insertions(+), 37 deletions(-)
+
+diff --git a/src/mod_auth_openidc.c b/src/mod_auth_openidc.c
+index eaaec3c..e86c61e 100644
+--- a/src/mod_auth_openidc.c
++++ b/src/mod_auth_openidc.c
+@@ -2563,6 +2563,52 @@ static int oidc_handle_logout_request(request_rec *r, oidc_cfg *c,
+ 	return HTTP_MOVED_TEMPORARILY;
+ }
+ 
++static apr_byte_t oidc_validate_post_logout_url(request_rec *r, const char *url, char **err_str, char **err_desc) {
++       apr_uri_t uri;
++       const char *c_host = NULL;
++
++       if (apr_uri_parse(r->pool, url, &uri) != APR_SUCCESS) {
++               *err_str = apr_pstrdup(r->pool, "Malformed URL");
++               *err_desc = apr_psprintf(r->pool, "Logout URL malformed: %s", url);
++               oidc_error(r, "%s: %s", *err_str, *err_desc);
++               return FALSE;
++       }
++
++       c_host = oidc_get_current_url_host(r);
++       if ((uri.hostname != NULL)
++                       && ((strstr(c_host, uri.hostname) == NULL)
++                                       || (strstr(uri.hostname, c_host) == NULL))) {
++               *err_str = apr_pstrdup(r->pool, "Invalid Request");
++               *err_desc =
++                               apr_psprintf(r->pool,
++                                               "logout value \"%s\" does not match the hostname of the current request \"%s\"",
++                                               apr_uri_unparse(r->pool, &uri, 0), c_host);
++               oidc_error(r, "%s: %s", *err_str, *err_desc);
++               return FALSE;
++       } else if (strstr(url, "/") != url) {
++               *err_str = apr_pstrdup(r->pool, "Malformed URL");
++               *err_desc =
++                               apr_psprintf(r->pool,
++                                               "No hostname was parsed and it does not seem to be relative, i.e starting with '/': %s",
++                                               url);
++               oidc_error(r, "%s: %s", *err_str, *err_desc);
++               return FALSE;
++       }
++
++       /* validate the URL to prevent HTTP header splitting */
++       if (((strstr(url, "\n") != NULL) || strstr(url, "\r") != NULL)) {
++               *err_str = apr_pstrdup(r->pool, "Invalid Request");
++               *err_desc =
++                               apr_psprintf(r->pool,
++                                               "logout value \"%s\" contains illegal \"\n\" or \"\r\" character(s)",
++                                               url);
++               oidc_error(r, "%s: %s", *err_str, *err_desc);
++               return FALSE;
++       }
++
++       return TRUE;
++}
++
+ /*
+  * perform (single) logout
+  */
+@@ -2571,6 +2617,8 @@ static int oidc_handle_logout(request_rec *r, oidc_cfg *c,
+ 
+ 	/* pickup the command or URL where the user wants to go after logout */
+ 	char *url = NULL;
++	char *error_str = NULL;
++	char *error_description = NULL;
+ 	oidc_util_get_request_parameter(r, OIDC_REDIRECT_URI_REQUEST_LOGOUT, &url);
+ 
+ 	oidc_debug(r, "enter (url=%s)", url);
+@@ -2587,43 +2635,11 @@ static int oidc_handle_logout(request_rec *r, oidc_cfg *c,
+ 
+ 		/* do input validation on the logout parameter value */
+ 
+-		const char *error_description = NULL;
+-		apr_uri_t uri;
+-
+-		if (apr_uri_parse(r->pool, url, &uri) != APR_SUCCESS) {
+-			const char *error_description = apr_psprintf(r->pool,
+-					"Logout URL malformed: %s", url);
+-			oidc_error(r, "%s", error_description);
+-			return oidc_util_html_send_error(r, c->error_template,
+-					"Malformed URL", error_description,
+-					HTTP_INTERNAL_SERVER_ERROR);
+-
+-		}
+-
+-		const char *c_host = oidc_get_current_url_host(r);
+-		if ((uri.hostname != NULL)
+-				&& ((strstr(c_host, uri.hostname) == NULL)
+-						|| (strstr(uri.hostname, c_host) == NULL))) {
+-			error_description =
+-					apr_psprintf(r->pool,
+-							"logout value \"%s\" does not match the hostname of the current request \"%s\"",
+-							apr_uri_unparse(r->pool, &uri, 0), c_host);
+-			oidc_error(r, "%s", error_description);
+-			return oidc_util_html_send_error(r, c->error_template,
+-					"Invalid Request", error_description,
+-					HTTP_INTERNAL_SERVER_ERROR);
+-		}
+-
+-		/* validate the URL to prevent HTTP header splitting */
+-		if (((strstr(url, "\n") != NULL) || strstr(url, "\r") != NULL)) {
+-			error_description =
+-					apr_psprintf(r->pool,
+-							"logout value \"%s\" contains illegal \"\n\" or \"\r\" character(s)",
+-							url);
+-			oidc_error(r, "%s", error_description);
+-			return oidc_util_html_send_error(r, c->error_template,
+-					"Invalid Request", error_description,
+-					HTTP_INTERNAL_SERVER_ERROR);
++		if (oidc_validate_post_logout_url(r, url, &error_str,
++			&error_description) == FALSE) {
++		return oidc_util_html_send_error(r, c->error_template, error_str,
++			error_description,
++			HTTP_BAD_REQUEST); 
+ 		}
+ 	}
+ 
+-- 
+2.21.3
+
diff --git a/SOURCES/0003-Backport-of-Fix-open-redirect-starting-with-a-slash.patch b/SOURCES/0003-Backport-of-Fix-open-redirect-starting-with-a-slash.patch
new file mode 100644
index 0000000..9d3c3ee
--- /dev/null
+++ b/SOURCES/0003-Backport-of-Fix-open-redirect-starting-with-a-slash.patch
@@ -0,0 +1,31 @@
+From ed041f8b5df58c4e612a0d0cbb920dc0b399b921 Mon Sep 17 00:00:00 2001
+From: Jakub Hrozek <jhrozek@redhat.com>
+Date: Sun, 10 May 2020 20:00:49 +0200
+Subject: [PATCH 3/3] Backport of Fix open redirect starting with a slash
+
+---
+ src/mod_auth_openidc.c | 8 ++++++++
+ 1 file changed, 8 insertions(+)
+
+diff --git a/src/mod_auth_openidc.c b/src/mod_auth_openidc.c
+index e86c61e..3c6efb4 100644
+--- a/src/mod_auth_openidc.c
++++ b/src/mod_auth_openidc.c
+@@ -2604,6 +2604,14 @@ static apr_byte_t oidc_validate_post_logout_url(request_rec *r, const char *url,
+                                                url);
+                oidc_error(r, "%s: %s", *err_str, *err_desc);
+                return FALSE;
++        } else if ((uri.hostname == NULL) && (strstr(url, "//") == url)) {
++                *err_str = apr_pstrdup(r->pool, "Malformed URL");
++                *err_desc =
++                                apr_psprintf(r->pool,
++                                                "No hostname was parsed and starting with '//': %s",
++                                                url);
++                oidc_error(r, "%s: %s", *err_str, *err_desc);
++                return FALSE;
+        }
+ 
+        return TRUE;
+-- 
+2.21.3
+
diff --git a/SOURCES/0004-Backport-of-Fix-open-redirect-starting-with-a-slash-.patch b/SOURCES/0004-Backport-of-Fix-open-redirect-starting-with-a-slash-.patch
new file mode 100644
index 0000000..dbd64c7
--- /dev/null
+++ b/SOURCES/0004-Backport-of-Fix-open-redirect-starting-with-a-slash-.patch
@@ -0,0 +1,32 @@
+From c21228a0f170c025d79625207dc94759f480418f Mon Sep 17 00:00:00 2001
+From: Jakub Hrozek <jhrozek@redhat.com>
+Date: Sun, 10 May 2020 20:02:23 +0200
+Subject: [PATCH 4/4] Backport of Fix open redirect starting with a slash and a
+ backslash
+
+---
+ src/mod_auth_openidc.c | 8 ++++++++
+ 1 file changed, 8 insertions(+)
+
+diff --git a/src/mod_auth_openidc.c b/src/mod_auth_openidc.c
+index 3c6efb4..e16d500 100644
+--- a/src/mod_auth_openidc.c
++++ b/src/mod_auth_openidc.c
+@@ -2612,6 +2612,14 @@ static apr_byte_t oidc_validate_post_logout_url(request_rec *r, const char *url,
+                                                 url);
+                 oidc_error(r, "%s: %s", *err_str, *err_desc);
+                 return FALSE;
++        } else if ((uri.hostname == NULL) && (strstr(url, "/\\") == url)) {
++                *err_str = apr_pstrdup(r->pool, "Malformed URL");
++                *err_desc =
++                                apr_psprintf(r->pool,
++                                                "No hostname was parsed and starting with '/\\': %s",
++                                                url);
++                oidc_error(r, "%s: %s", *err_str, *err_desc);
++                return FALSE;
+        }
+ 
+        return TRUE;
+-- 
+2.21.3
+
diff --git a/SOURCES/0005-Fix-the-previous-backports.patch b/SOURCES/0005-Fix-the-previous-backports.patch
new file mode 100644
index 0000000..c3d0e2b
--- /dev/null
+++ b/SOURCES/0005-Fix-the-previous-backports.patch
@@ -0,0 +1,61 @@
+From a5c9f79516fd4097817ac75a37af3b191a3d1448 Mon Sep 17 00:00:00 2001
+From: Jakub Hrozek <jhrozek@redhat.com>
+Date: Mon, 1 Jun 2020 21:47:28 +0200
+Subject: [PATCH] Fix the previous backports
+
+---
+ src/mod_auth_openidc.c | 24 ++++++++++++------------
+ 1 file changed, 12 insertions(+), 12 deletions(-)
+
+diff --git a/src/mod_auth_openidc.c b/src/mod_auth_openidc.c
+index e16d500..74f206b 100644
+--- a/src/mod_auth_openidc.c
++++ b/src/mod_auth_openidc.c
+@@ -2585,7 +2585,7 @@ static apr_byte_t oidc_validate_post_logout_url(request_rec *r, const char *url,
+                                                apr_uri_unparse(r->pool, &uri, 0), c_host);
+                oidc_error(r, "%s: %s", *err_str, *err_desc);
+                return FALSE;
+-       } else if (strstr(url, "/") != url) {
++       } else if ((uri.hostname == NULL) && (strstr(url, "/") != url)) {
+                *err_str = apr_pstrdup(r->pool, "Malformed URL");
+                *err_desc =
+                                apr_psprintf(r->pool,
+@@ -2593,17 +2593,6 @@ static apr_byte_t oidc_validate_post_logout_url(request_rec *r, const char *url,
+                                                url);
+                oidc_error(r, "%s: %s", *err_str, *err_desc);
+                return FALSE;
+-       }
+-
+-       /* validate the URL to prevent HTTP header splitting */
+-       if (((strstr(url, "\n") != NULL) || strstr(url, "\r") != NULL)) {
+-               *err_str = apr_pstrdup(r->pool, "Invalid Request");
+-               *err_desc =
+-                               apr_psprintf(r->pool,
+-                                               "logout value \"%s\" contains illegal \"\n\" or \"\r\" character(s)",
+-                                               url);
+-               oidc_error(r, "%s: %s", *err_str, *err_desc);
+-               return FALSE;
+         } else if ((uri.hostname == NULL) && (strstr(url, "//") == url)) {
+                 *err_str = apr_pstrdup(r->pool, "Malformed URL");
+                 *err_desc =
+@@ -2622,6 +2611,17 @@ static apr_byte_t oidc_validate_post_logout_url(request_rec *r, const char *url,
+                 return FALSE;
+        }
+ 
++       /* validate the URL to prevent HTTP header splitting */
++       if (((strstr(url, "\n") != NULL) || strstr(url, "\r") != NULL)) {
++               *err_str = apr_pstrdup(r->pool, "Invalid Request");
++               *err_desc =
++                               apr_psprintf(r->pool,
++                                               "logout value \"%s\" contains illegal \"\n\" or \"\r\" character(s)",
++                                               url);
++               oidc_error(r, "%s: %s", *err_str, *err_desc);
++               return FALSE;
++       }
++
+        return TRUE;
+ }
+ 
+-- 
+2.21.3
+
diff --git a/SOURCES/0006-add-OIDCStateMaxNumberOfCookies-to-limit-nr-of-state.patch b/SOURCES/0006-add-OIDCStateMaxNumberOfCookies-to-limit-nr-of-state.patch
new file mode 100644
index 0000000..9918336
--- /dev/null
+++ b/SOURCES/0006-add-OIDCStateMaxNumberOfCookies-to-limit-nr-of-state.patch
@@ -0,0 +1,254 @@
+From 1bb55df94c0db8376f88a568c331802bd282f097 Mon Sep 17 00:00:00 2001
+From: Hans Zandbelt <hans.zandbelt@zmartzone.eu>
+Date: Wed, 1 Aug 2018 11:41:57 +0200
+Subject: [PATCH 06/11] add OIDCStateMaxNumberOfCookies to limit nr of state
+ cookies; see #331
+
+Signed-off-by: Hans Zandbelt <hans.zandbelt@zmartzone.eu>
+(cherry picked from commit 5041e0f679f03525f106eaf0edc7c7d245b1d89c)
+---
+ ChangeLog              |  3 +++
+ src/config.c           | 19 +++++++++++++
+ src/mod_auth_openidc.c | 61 ++++++++++++++++++++++++++++++++++--------
+ src/mod_auth_openidc.h |  2 ++
+ 4 files changed, 74 insertions(+), 11 deletions(-)
+
+diff --git a/ChangeLog b/ChangeLog
+index af9380e..b6ac513 100644
+--- a/ChangeLog
++++ b/ChangeLog
+@@ -1,3 +1,6 @@
++08/01/2018
++- add option to set an upper limit to the number of concurrent state cookies via OIDCStateMaxNumberOfCookies; see #331
++
+ 07/06/2018
+ - abort when string length for remote user name substitution is larger than 255 characters
+ - release 2.3.7
+diff --git a/src/config.c b/src/config.c
+index 0185cff..2fd63ea 100644
+--- a/src/config.c
++++ b/src/config.c
+@@ -104,6 +104,8 @@
+ #define OIDC_DEFAULT_SESSION_CLIENT_COOKIE_CHUNK_SIZE 4000
+ /* timeout in seconds after which state expires */
+ #define OIDC_DEFAULT_STATE_TIMEOUT 300
++/* maximum number of parallel state cookies; 0 means unlimited, until the browser or server gives up */
++#define OIDC_DEFAULT_MAX_NUMBER_OF_STATE_COOKIES 0
+ /* default session inactivity timeout */
+ #define OIDC_DEFAULT_SESSION_INACTIVITY_TIMEOUT 300
+ /* default session max duration */
+@@ -227,6 +229,7 @@
+ #define OIDCHTTPTimeoutLong                  "OIDCHTTPTimeoutLong"
+ #define OIDCHTTPTimeoutShort                 "OIDCHTTPTimeoutShort"
+ #define OIDCStateTimeout                     "OIDCStateTimeout"
++#define OIDCStateMaxNumberOfCookies          "OIDCStateMaxNumberOfCookies"
+ #define OIDCSessionInactivityTimeout         "OIDCSessionInactivityTimeout"
+ #define OIDCMetadataDir                      "OIDCMetadataDir"
+ #define OIDCSessionCacheFallbackToCookie     "OIDCSessionCacheFallbackToCookie"
+@@ -994,6 +997,12 @@ static const char *oidc_set_client_auth_bearer_token(cmd_parms *cmd,
+ 	return NULL;
+ }
+ 
++int oidc_cfg_max_number_of_state_cookies(oidc_cfg *cfg) {
++	if (cfg->max_number_of_state_cookies == OIDC_CONFIG_POS_INT_UNSET)
++		return OIDC_DEFAULT_MAX_NUMBER_OF_STATE_COOKIES;
++	return cfg->max_number_of_state_cookies;
++}
++
+ /*
+  * create a new server config record with defaults
+  */
+@@ -1102,6 +1111,7 @@ void *oidc_create_server_config(apr_pool_t *pool, server_rec *svr) {
+ 	c->http_timeout_long = OIDC_DEFAULT_HTTP_TIMEOUT_LONG;
+ 	c->http_timeout_short = OIDC_DEFAULT_HTTP_TIMEOUT_SHORT;
+ 	c->state_timeout = OIDC_DEFAULT_STATE_TIMEOUT;
++	c->max_number_of_state_cookies = OIDC_CONFIG_POS_INT_UNSET;
+ 	c->session_inactivity_timeout = OIDC_DEFAULT_SESSION_INACTIVITY_TIMEOUT;
+ 
+ 	c->cookie_domain = NULL;
+@@ -1416,6 +1426,10 @@ void *oidc_merge_server_config(apr_pool_t *pool, void *BASE, void *ADD) {
+ 	c->state_timeout =
+ 			add->state_timeout != OIDC_DEFAULT_STATE_TIMEOUT ?
+ 					add->state_timeout : base->state_timeout;
++	c->max_number_of_state_cookies =
++			add->max_number_of_state_cookies != OIDC_CONFIG_POS_INT_UNSET ?
++					add->max_number_of_state_cookies :
++					base->max_number_of_state_cookies;
+ 	c->session_inactivity_timeout =
+ 			add->session_inactivity_timeout
+ 			!= OIDC_DEFAULT_SESSION_INACTIVITY_TIMEOUT ?
+@@ -2627,6 +2641,11 @@ const command_rec oidc_config_cmds[] = {
+ 				(void*)APR_OFFSETOF(oidc_cfg, state_timeout),
+ 				RSRC_CONF,
+ 				"Time to live in seconds for state parameter (cq. interval in which the authorization request and the corresponding response need to be completed)."),
++		AP_INIT_TAKE1(OIDCStateMaxNumberOfCookies,
++				oidc_set_int_slot,
++				(void*)APR_OFFSETOF(oidc_cfg, max_number_of_state_cookies),
++				RSRC_CONF,
++				"Maximun number of parallel state cookies i.e. outstanding authorization requests."),
+ 		AP_INIT_TAKE1(OIDCSessionInactivityTimeout,
+ 				oidc_set_session_inactivity_timeout,
+ 				(void*)APR_OFFSETOF(oidc_cfg, session_inactivity_timeout),
+diff --git a/src/mod_auth_openidc.c b/src/mod_auth_openidc.c
+index 74f206b..c0f65c6 100644
+--- a/src/mod_auth_openidc.c
++++ b/src/mod_auth_openidc.c
+@@ -686,8 +686,14 @@ static apr_byte_t oidc_unsolicited_proto_state(request_rec *r, oidc_cfg *c,
+ 	return TRUE;
+ }
+ 
+-static void oidc_clean_expired_state_cookies(request_rec *r, oidc_cfg *c,
++/*
++ * clean state cookies that have expired i.e. for outstanding requests that will never return
++ * successfully and return the number of remaining valid cookies/outstanding-requests while
++ * doing so
++ */
++static int oidc_clean_expired_state_cookies(request_rec *r, oidc_cfg *c,
+ 		const char *currentCookieName) {
++	int number_of_valid_state_cookies = 0;
+ 	char *cookie, *tokenizerCtx;
+ 	char *cookies = apr_pstrdup(r->pool, oidc_util_hdr_in_cookie_get(r));
+ 	if (cookies != NULL) {
+@@ -715,6 +721,8 @@ static void oidc_clean_expired_state_cookies(request_rec *r, oidc_cfg *c,
+ 										cookieName);
+ 								oidc_util_set_cookie(r, cookieName, "", 0,
+ 										NULL);
++							} else {
++								number_of_valid_state_cookies++;
+ 							}
+ 							oidc_proto_state_destroy(proto_state);
+ 						}
+@@ -724,6 +732,7 @@ static void oidc_clean_expired_state_cookies(request_rec *r, oidc_cfg *c,
+ 			cookie = apr_strtok(NULL, OIDC_STR_SEMI_COLON, &tokenizerCtx);
+ 		}
+ 	}
++	return number_of_valid_state_cookies;
+ }
+ 
+ /*
+@@ -796,7 +805,7 @@ static apr_byte_t oidc_restore_proto_state(request_rec *r, oidc_cfg *c,
+  * set the state that is maintained between an authorization request and an authorization response
+  * in a cookie in the browser that is cryptographically bound to that state
+  */
+-static apr_byte_t oidc_authorization_request_set_cookie(request_rec *r,
++static int oidc_authorization_request_set_cookie(request_rec *r,
+ 		oidc_cfg *c, const char *state, oidc_proto_state_t *proto_state) {
+ 	/*
+ 	 * create a cookie consisting of 8 elements:
+@@ -805,10 +814,32 @@ static apr_byte_t oidc_authorization_request_set_cookie(request_rec *r,
+ 	 */
+ 	char *cookieValue = oidc_proto_state_to_cookie(r, c, proto_state);
+ 	if (cookieValue == NULL)
+-		return FALSE;
++		return HTTP_INTERNAL_SERVER_ERROR;
+ 
+-	/* clean expired state cookies to avoid pollution */
+-	oidc_clean_expired_state_cookies(r, c, NULL);
++	/*
++	 * clean expired state cookies to avoid pollution and optionally
++ 	 * try to avoid the number of state cookies exceeding a max
++	 */
++	int number_of_cookies = oidc_clean_expired_state_cookies(r, c, NULL);
++	int max_number_of_cookies = oidc_cfg_max_number_of_state_cookies(c);
++	if ((max_number_of_cookies > 0)
++			&& (number_of_cookies >= max_number_of_cookies)) {
++		oidc_warn(r,
++				"the number of existing, valid state cookies (%d) has exceeded the limit (%d), no additional authorization request + state cookie can be generated, aborting the request",
++				number_of_cookies, max_number_of_cookies);
++		/*
++		 * TODO: the html_send code below caters for the case that there's a user behind a
++		 * browser generating this request, rather than a piece of XHR code; how would an
++		 * XHR client handle this?
++		 */
++
++		return oidc_util_html_send_error(r, c->error_template,
++				"Too Many Outstanding Requests",
++				apr_psprintf(r->pool,
++						"No authentication request could be generated since there are too many outstanding authentication requests already; you may have to wait up to %d seconds to be able to create a new request",
++						c->state_timeout),
++						HTTP_SERVICE_UNAVAILABLE);
++	}
+ 
+ 	/* assemble the cookie name for the state cookie */
+ 	const char *cookieName = oidc_get_state_cookie_name(r, state);
+@@ -817,9 +848,7 @@ static apr_byte_t oidc_authorization_request_set_cookie(request_rec *r,
+ 	oidc_util_set_cookie(r, cookieName, cookieValue, -1,
+ 			c->cookie_same_site ? OIDC_COOKIE_EXT_SAME_SITE_LAX : NULL);
+ 
+-	//free(s_value);
+-
+-	return TRUE;
++	return HTTP_OK;
+ }
+ 
+ /*
+@@ -2245,12 +2274,19 @@ static int oidc_authenticate_user(request_rec *r, oidc_cfg *c,
+ 	/* get a hash value that fingerprints the browser concatenated with the random input */
+ 	char *state = oidc_get_browser_state_hash(r, nonce);
+ 
+-	/* create state that restores the context when the authorization response comes in; cryptographically bind it to the browser */
+-	if (oidc_authorization_request_set_cookie(r, c, state, proto_state) == FALSE)
+-		return HTTP_INTERNAL_SERVER_ERROR;
++	/*
++	 * create state that restores the context when the authorization response comes in
++	 * and cryptographically bind it to the browser
++	 */
++	int rc = oidc_authorization_request_set_cookie(r, c, state, proto_state);
++	if (rc != HTTP_OK) {
++		oidc_proto_state_destroy(proto_state);
++		return rc;
++	}
+ 
+ 	/*
+ 	 * printout errors if Cookie settings are not going to work
++	 * TODO: separate this code out into its own function
+ 	 */
+ 	apr_uri_t o_uri;
+ 	memset(&o_uri, 0, sizeof(apr_uri_t));
+@@ -2263,6 +2299,7 @@ static int oidc_authenticate_user(request_rec *r, oidc_cfg *c,
+ 		oidc_error(r,
+ 				"the URL scheme (%s) of the configured " OIDCRedirectURI " does not match the URL scheme of the URL being accessed (%s): the \"state\" and \"session\" cookies will not be shared between the two!",
+ 				r_uri.scheme, o_uri.scheme);
++		oidc_proto_state_destroy(proto_state);
+ 		return HTTP_INTERNAL_SERVER_ERROR;
+ 	}
+ 
+@@ -2273,6 +2310,7 @@ static int oidc_authenticate_user(request_rec *r, oidc_cfg *c,
+ 				oidc_error(r,
+ 						"the URL hostname (%s) of the configured " OIDCRedirectURI " does not match the URL hostname of the URL being accessed (%s): the \"state\" and \"session\" cookies will not be shared between the two!",
+ 						r_uri.hostname, o_uri.hostname);
++				oidc_proto_state_destroy(proto_state);
+ 				return HTTP_INTERNAL_SERVER_ERROR;
+ 			}
+ 		}
+@@ -2281,6 +2319,7 @@ static int oidc_authenticate_user(request_rec *r, oidc_cfg *c,
+ 			oidc_error(r,
+ 					"the domain (%s) configured in " OIDCCookieDomain " does not match the URL hostname (%s) of the URL being accessed (%s): setting \"state\" and \"session\" cookies will not work!!",
+ 					c->cookie_domain, o_uri.hostname, original_url);
++			oidc_proto_state_destroy(proto_state);
+ 			return HTTP_INTERNAL_SERVER_ERROR;
+ 		}
+ 	}
+diff --git a/src/mod_auth_openidc.h b/src/mod_auth_openidc.h
+index 48bb8fe..5162ed4 100644
+--- a/src/mod_auth_openidc.h
++++ b/src/mod_auth_openidc.h
+@@ -379,6 +379,7 @@ typedef struct oidc_cfg {
+ 	int http_timeout_long;
+ 	int http_timeout_short;
+ 	int state_timeout;
++	int max_number_of_state_cookies;
+ 	int session_inactivity_timeout;
+ 	int session_cache_fallback_to_cookie;
+ 
+@@ -686,6 +687,7 @@ int oidc_cfg_cache_encrypt(request_rec *r);
+ int oidc_cfg_session_cache_fallback_to_cookie(request_rec *r);
+ const char *oidc_parse_pkce_type(apr_pool_t *pool, const char *arg, oidc_proto_pkce_t **type);
+ const char *oidc_cfg_claim_prefix(request_rec *r);
++int oidc_cfg_max_number_of_state_cookies(oidc_cfg *cfg);
+ 
+ // oidc_util.c
+ int oidc_strnenvcmp(const char *a, const char *b, int len);
+-- 
+2.26.2
+
diff --git a/SOURCES/0007-set-boundaries-on-min-and-max-values-on-number-of-pa.patch b/SOURCES/0007-set-boundaries-on-min-and-max-values-on-number-of-pa.patch
new file mode 100644
index 0000000..e46448e
--- /dev/null
+++ b/SOURCES/0007-set-boundaries-on-min-and-max-values-on-number-of-pa.patch
@@ -0,0 +1,118 @@
+From 284537dfc0585e08cfc0702c89b241d8986c7236 Mon Sep 17 00:00:00 2001
+From: Hans Zandbelt <hans.zandbelt@zmartzone.eu>
+Date: Fri, 3 Aug 2018 12:22:45 +0200
+Subject: [PATCH 07/11] set boundaries on min and max values on number of
+ parallel state cookies
+
+Signed-off-by: Hans Zandbelt <hans.zandbelt@zmartzone.eu>
+(cherry picked from commit b8c53d7e0439f190afe0c6eeb2e2e12e881c65ac)
+---
+ src/config.c | 17 ++++++++++++++++-
+ src/parse.c  | 31 +++++++++++++++++++++++++++++++
+ src/parse.h  |  2 ++
+ 3 files changed, 49 insertions(+), 1 deletion(-)
+
+diff --git a/src/config.c b/src/config.c
+index 2fd63ea..c793818 100644
+--- a/src/config.c
++++ b/src/config.c
+@@ -997,6 +997,21 @@ static const char *oidc_set_client_auth_bearer_token(cmd_parms *cmd,
+ 	return NULL;
+ }
+ 
++/*
++ * set the maximun number of parallel state cookies
++ */
++static const char *oidc_set_max_number_of_state_cookies(cmd_parms *cmd,
++		void *struct_ptr, const char *arg) {
++	oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
++			cmd->server->module_config, &auth_openidc_module);
++	const char *rv = oidc_parse_max_number_of_state_cookies(cmd->pool, arg,
++			&cfg->max_number_of_state_cookies);
++	return OIDC_CONFIG_DIR_RV(cmd, rv);
++}
++
++/*
++ * return the maximun number of parallel state cookies
++ */
+ int oidc_cfg_max_number_of_state_cookies(oidc_cfg *cfg) {
+ 	if (cfg->max_number_of_state_cookies == OIDC_CONFIG_POS_INT_UNSET)
+ 		return OIDC_DEFAULT_MAX_NUMBER_OF_STATE_COOKIES;
+@@ -2642,7 +2657,7 @@ const command_rec oidc_config_cmds[] = {
+ 				RSRC_CONF,
+ 				"Time to live in seconds for state parameter (cq. interval in which the authorization request and the corresponding response need to be completed)."),
+ 		AP_INIT_TAKE1(OIDCStateMaxNumberOfCookies,
+-				oidc_set_int_slot,
++				oidc_set_max_number_of_state_cookies,
+ 				(void*)APR_OFFSETOF(oidc_cfg, max_number_of_state_cookies),
+ 				RSRC_CONF,
+ 				"Maximun number of parallel state cookies i.e. outstanding authorization requests."),
+diff --git a/src/parse.c b/src/parse.c
+index 9d3763c..0f986fd 100644
+--- a/src/parse.c
++++ b/src/parse.c
+@@ -530,6 +530,28 @@ const char *oidc_valid_session_max_duration(apr_pool_t *pool, int v) {
+ 	return NULL;
+ }
+ 
++#define OIDC_MAX_NUMBER_OF_STATE_COOKIES_MIN  0
++#define OIDC_MAX_NUMBER_OF_STATE_COOKIES_MAX  255
++
++/*
++ * check the maximum number of parallel state cookies
++ */
++const char *oidc_valid_max_number_of_state_cookies(apr_pool_t *pool, int v) {
++	if (v == 0) {
++		return NULL;
++	}
++	if (v < OIDC_MAX_NUMBER_OF_STATE_COOKIES_MIN) {
++		return apr_psprintf(pool, "maximum must not be less than %d",
++				OIDC_MAX_NUMBER_OF_STATE_COOKIES_MIN);
++	}
++	if (v > OIDC_MAX_NUMBER_OF_STATE_COOKIES_MAX) {
++		return apr_psprintf(pool, "maximum must not be greater than %d",
++				OIDC_MAX_NUMBER_OF_STATE_COOKIES_MAX);
++	}
++	return NULL;
++}
++
++
+ /*
+  * parse a session max duration value from the provided string
+  */
+@@ -1218,3 +1240,12 @@ const char *oidc_parse_auth_request_method(apr_pool_t *pool, const char *arg,
+ 
+ 	return NULL;
+ }
++
++/*
++ * parse the maximum number of parallel state cookies
++ */
++const char *oidc_parse_max_number_of_state_cookies(apr_pool_t *pool,
++		const char *arg, int *int_value) {
++	return oidc_parse_int_valid(pool, arg, int_value,
++			oidc_valid_max_number_of_state_cookies);
++}
+diff --git a/src/parse.h b/src/parse.h
+index 853e98f..6355db4 100644
+--- a/src/parse.h
++++ b/src/parse.h
+@@ -90,6 +90,7 @@ const char *oidc_valid_userinfo_refresh_interval(apr_pool_t *pool, int v);
+ const char *oidc_valid_userinfo_token_method(apr_pool_t *pool, const char *arg);
+ const char *oidc_valid_token_binding_policy(apr_pool_t *pool, const char *arg);
+ const char *oidc_valid_auth_request_method(apr_pool_t *pool, const char *arg);
++const char *oidc_valid_max_number_of_state_cookies(apr_pool_t *pool, int v);
+ 
+ const char *oidc_parse_int(apr_pool_t *pool, const char *arg, int *int_value);
+ const char *oidc_parse_boolean(apr_pool_t *pool, const char *arg, int *bool_value);
+@@ -116,6 +117,7 @@ const char *oidc_parse_info_hook_data(apr_pool_t *pool, const char *arg, apr_has
+ const char *oidc_parse_token_binding_policy(apr_pool_t *pool, const char *arg, int *int_value);
+ const char *oidc_token_binding_policy2str(apr_pool_t *pool, int v);
+ const char *oidc_parse_auth_request_method(apr_pool_t *pool, const char *arg, int *method);
++const char *oidc_parse_max_number_of_state_cookies(apr_pool_t *pool, const char *arg, int *int_value);
+ 
+ typedef const char *(*oidc_valid_int_function_t)(apr_pool_t *, int);
+ typedef const char *(*oidc_valid_function_t)(apr_pool_t *, const char *);
+-- 
+2.26.2
+
diff --git a/SOURCES/0008-make-the-default-max-number-of-state-cookies-7-inste.patch b/SOURCES/0008-make-the-default-max-number-of-state-cookies-7-inste.patch
new file mode 100644
index 0000000..e9a161d
--- /dev/null
+++ b/SOURCES/0008-make-the-default-max-number-of-state-cookies-7-inste.patch
@@ -0,0 +1,45 @@
+From 623163348f74fc1bd019a676fa24af69dde79654 Mon Sep 17 00:00:00 2001
+From: Hans Zandbelt <hans.zandbelt@zmartzone.eu>
+Date: Fri, 3 Aug 2018 21:41:34 +0200
+Subject: [PATCH 08/11] make the default max number of state cookies 7 instead
+ of unlimited
+
+bump to 2.3.8rc1
+
+Signed-off-by: Hans Zandbelt <hans.zandbelt@zmartzone.eu>
+(cherry picked from commit 6616372af77df04a9b0b197e759790ecf3f2399a)
+---
+ ChangeLog    | 5 ++++-
+ src/config.c | 2 +-
+ 2 files changed, 5 insertions(+), 2 deletions(-)
+
+diff --git a/ChangeLog b/ChangeLog
+index b6ac513..27f45be 100644
+--- a/ChangeLog
++++ b/ChangeLog
+@@ -1,5 +1,8 @@
+-08/01/2018
++
++08/03/2018
+ - add option to set an upper limit to the number of concurrent state cookies via OIDCStateMaxNumberOfCookies; see #331
++- make the default maximum number of parallel state cookies 7 instead of unlimited; see #331
++- bump o 2.3.8rc1
+ 
+ 07/06/2018
+ - abort when string length for remote user name substitution is larger than 255 characters
+diff --git a/src/config.c b/src/config.c
+index c793818..6fa6227 100644
+--- a/src/config.c
++++ b/src/config.c
+@@ -105,7 +105,7 @@
+ /* timeout in seconds after which state expires */
+ #define OIDC_DEFAULT_STATE_TIMEOUT 300
+ /* maximum number of parallel state cookies; 0 means unlimited, until the browser or server gives up */
+-#define OIDC_DEFAULT_MAX_NUMBER_OF_STATE_COOKIES 0
++#define OIDC_DEFAULT_MAX_NUMBER_OF_STATE_COOKIES 7
+ /* default session inactivity timeout */
+ #define OIDC_DEFAULT_SESSION_INACTIVITY_TIMEOUT 300
+ /* default session max duration */
+-- 
+2.26.2
+
diff --git a/SOURCES/0009-don-t-return-content-with-503-see-331.patch b/SOURCES/0009-don-t-return-content-with-503-see-331.patch
new file mode 100644
index 0000000..c80525c
--- /dev/null
+++ b/SOURCES/0009-don-t-return-content-with-503-see-331.patch
@@ -0,0 +1,59 @@
+From 7f5666375a3351e9c37589456b6fb3c92ef987c0 Mon Sep 17 00:00:00 2001
+From: Hans Zandbelt <hans.zandbelt@zmartzone.eu>
+Date: Sat, 4 Aug 2018 08:55:33 +0200
+Subject: [PATCH 09/11] don't return content with 503; see #331
+
+since it turns the HTTP 503 status code into a 200 which we don't prefer
+for XHR clients; users will see Apache specific readable text
+
+Signed-off-by: Hans Zandbelt <hans.zandbelt@zmartzone.eu>
+(cherry picked from commit 9e98f1a042fa14d6b0892638a0d87c2b951837b6)
+---
+ ChangeLog              | 4 +++-
+ src/mod_auth_openidc.c | 8 ++++++++
+ 2 files changed, 11 insertions(+), 1 deletion(-)
+
+diff --git a/ChangeLog b/ChangeLog
+index 27f45be..dfe4bd6 100644
+--- a/ChangeLog
++++ b/ChangeLog
+@@ -1,8 +1,10 @@
++08/04/2018
++- don't return content with 503 since it will turn the HTTP status code into a 200; see #331
+ 
+ 08/03/2018
+ - add option to set an upper limit to the number of concurrent state cookies via OIDCStateMaxNumberOfCookies; see #331
+ - make the default maximum number of parallel state cookies 7 instead of unlimited; see #331
+-- bump o 2.3.8rc1
++- bump to 2.3.8rc1
+ 
+ 07/06/2018
+ - abort when string length for remote user name substitution is larger than 255 characters
+diff --git a/src/mod_auth_openidc.c b/src/mod_auth_openidc.c
+index c0f65c6..e3817a9 100644
+--- a/src/mod_auth_openidc.c
++++ b/src/mod_auth_openidc.c
+@@ -833,12 +833,20 @@ static int oidc_authorization_request_set_cookie(request_rec *r,
+ 		 * XHR client handle this?
+ 		 */
+ 
++		/*
++		 * it appears that sending content with a 503 turns the HTTP status code
++		 * into a 200 so we'll avoid that for now: the user will see Apache specific
++		 * readable text anyway
++		 *
+ 		return oidc_util_html_send_error(r, c->error_template,
+ 				"Too Many Outstanding Requests",
+ 				apr_psprintf(r->pool,
+ 						"No authentication request could be generated since there are too many outstanding authentication requests already; you may have to wait up to %d seconds to be able to create a new request",
+ 						c->state_timeout),
+ 						HTTP_SERVICE_UNAVAILABLE);
++		*/
++
++		return HTTP_SERVICE_UNAVAILABLE;
+ 	}
+ 
+ 	/* assemble the cookie name for the state cookie */
+-- 
+2.26.2
+
diff --git a/SOURCES/0010-improve-auto-detection-of-XMLHttpRequests-via-Accept.patch b/SOURCES/0010-improve-auto-detection-of-XMLHttpRequests-via-Accept.patch
new file mode 100644
index 0000000..d0edbbc
--- /dev/null
+++ b/SOURCES/0010-improve-auto-detection-of-XMLHttpRequests-via-Accept.patch
@@ -0,0 +1,305 @@
+From 9a0827a18ec4d16cf9e79afede901194d6ef5cc6 Mon Sep 17 00:00:00 2001
+From: Hans Zandbelt <hans.zandbelt@zmartzone.eu>
+Date: Thu, 23 Aug 2018 00:04:38 +0200
+Subject: [PATCH 10/11] improve auto-detection of XMLHttpRequests via Accept
+ header; see #331
+
+version 2.3.8rc5
+
+Signed-off-by: Hans Zandbelt <hans.zandbelt@zmartzone.eu>
+(cherry picked from commit cff35bba8861ddb09d694ecfcd88d5fac1018453)
+---
+ src/mod_auth_openidc.c |  41 +++---
+ src/mod_auth_openidc.h |   6 +-
+ src/util.c             |  80 +++++++---
+
+diff --git a/src/mod_auth_openidc.c b/src/mod_auth_openidc.c
+index e3817a9..897a449 100644
+--- a/src/mod_auth_openidc.c
++++ b/src/mod_auth_openidc.c
+@@ -805,8 +805,8 @@ static apr_byte_t oidc_restore_proto_state(request_rec *r, oidc_cfg *c,
+  * set the state that is maintained between an authorization request and an authorization response
+  * in a cookie in the browser that is cryptographically bound to that state
+  */
+-static int oidc_authorization_request_set_cookie(request_rec *r,
+-		oidc_cfg *c, const char *state, oidc_proto_state_t *proto_state) {
++static int oidc_authorization_request_set_cookie(request_rec *r, oidc_cfg *c,
++		const char *state, oidc_proto_state_t *proto_state) {
+ 	/*
+ 	 * create a cookie consisting of 8 elements:
+ 	 * random value, original URL, original method, issuer, response_type, response_mod, prompt and timestamp
+@@ -818,7 +818,7 @@ static int oidc_authorization_request_set_cookie(request_rec *r,
+ 
+ 	/*
+ 	 * clean expired state cookies to avoid pollution and optionally
+- 	 * try to avoid the number of state cookies exceeding a max
++	 * try to avoid the number of state cookies exceeding a max
+ 	 */
+ 	int number_of_cookies = oidc_clean_expired_state_cookies(r, c, NULL);
+ 	int max_number_of_cookies = oidc_cfg_max_number_of_state_cookies(c);
+@@ -953,6 +953,26 @@ static void oidc_log_session_expires(request_rec *r, const char *msg,
+ 			apr_time_sec(session_expires - apr_time_now()));
+ }
+ 
++/*
++ * see if this is a non-browser request
++ */
++static apr_byte_t oidc_is_xml_http_request(request_rec *r) {
++
++	if ((oidc_util_hdr_in_x_requested_with_get(r) != NULL)
++			&& (apr_strnatcasecmp(oidc_util_hdr_in_x_requested_with_get(r),
++					OIDC_HTTP_HDR_VAL_XML_HTTP_REQUEST) == 0))
++		return TRUE;
++
++	if ((oidc_util_hdr_in_accept_contains(r, OIDC_CONTENT_TYPE_TEXT_HTML)
++			== FALSE) && (oidc_util_hdr_in_accept_contains(r,
++					OIDC_CONTENT_TYPE_APP_XHTML_XML) == FALSE)
++					&& (oidc_util_hdr_in_accept_contains(r,
++							OIDC_CONTENT_TYPE_ANY) == FALSE))
++		return TRUE;
++
++	return FALSE;
++}
++
+ /*
+  * find out which action we need to take when encountering an unauthenticated request
+  */
+@@ -982,9 +1002,7 @@ static int oidc_handle_unauthenticated_user(request_rec *r, oidc_cfg *c) {
+ 		 * won't redirect the user and thus avoid creating a state cookie
+ 		 * for a non-browser (= Javascript) call that will never return from the OP
+ 		 */
+-		if ((oidc_util_hdr_in_x_requested_with_get(r) != NULL)
+-				&& (apr_strnatcasecmp(oidc_util_hdr_in_x_requested_with_get(r),
+-						OIDC_HTTP_HDR_VAL_XML_HTTP_REQUEST) == 0))
++		if (oidc_is_xml_http_request(r) == TRUE)
+ 			return HTTP_UNAUTHORIZED;
+ 	}
+ 
+@@ -997,17 +1015,6 @@ static int oidc_handle_unauthenticated_user(request_rec *r, oidc_cfg *c) {
+ 			oidc_dir_cfg_path_scope(r));
+ }
+ 
+-/*
+- * see if this is a non-browser request
+- */
+-static apr_byte_t oidc_is_xml_http_request(request_rec *r) {
+-	if ((oidc_util_hdr_in_x_requested_with_get(r) != NULL)
+-			&& (apr_strnatcasecmp(oidc_util_hdr_in_x_requested_with_get(r),
+-					OIDC_HTTP_HDR_VAL_XML_HTTP_REQUEST) == 0))
+-		return TRUE;
+-	return FALSE;
+-}
+-
+ /*
+  * check if maximum session duration was exceeded
+  */
+diff --git a/src/mod_auth_openidc.h b/src/mod_auth_openidc.h
+index 5162ed4..f89f392 100644
+--- a/src/mod_auth_openidc.h
++++ b/src/mod_auth_openidc.h
+@@ -537,7 +537,9 @@ apr_byte_t oidc_oauth_get_bearer_token(request_rec *r, const char **access_token
+ #define OIDC_CONTENT_TYPE_JWT           "application/jwt"
+ #define OIDC_CONTENT_TYPE_FORM_ENCODED  "application/x-www-form-urlencoded"
+ #define OIDC_CONTENT_TYPE_IMAGE_PNG     "image/png"
+-#define OIDC_CONTENT_TYPE_HTML          "text/html"
++#define OIDC_CONTENT_TYPE_TEXT_HTML     "text/html"
++#define OIDC_CONTENT_TYPE_APP_XHTML_XML "application/xhtml+xml"
++#define OIDC_CONTENT_TYPE_ANY           "*/*"
+ 
+ #define OIDC_STR_SPACE         " "
+ #define OIDC_STR_EQUAL         "="
+@@ -560,6 +562,7 @@ apr_byte_t oidc_oauth_get_bearer_token(request_rec *r, const char **access_token
+ #define OIDC_CHAR_FORWARD_SLASH '/'
+ #define OIDC_CHAR_PIPE          '|'
+ #define OIDC_CHAR_AMP           '&'
++#define OIDC_CHAR_SEMI_COLON    ';'
+ 
+ #define OIDC_APP_INFO_REFRESH_TOKEN     "refresh_token"
+ #define OIDC_APP_INFO_ACCESS_TOKEN      "access_token"
+@@ -787,6 +790,7 @@ const char *oidc_util_hdr_in_host_get(const request_rec *r);
+ void oidc_util_hdr_out_location_set(const request_rec *r, const char *value);
+ const char *oidc_util_hdr_out_location_get(const request_rec *r);
+ void oidc_util_hdr_err_out_add(const request_rec *r, const char *name, const char *value);
++apr_byte_t oidc_util_hdr_in_accept_contains(const request_rec *r, const char *needle);
+ 
+ // oidc_metadata.c
+ apr_byte_t oidc_metadata_provider_retrieve(request_rec *r, oidc_cfg *cfg, const char *issuer, const char *url, json_t **j_metadata, char **response);
+diff --git a/src/util.c b/src/util.c
+index 2fd79ec..67b2fc3 100644
+--- a/src/util.c
++++ b/src/util.c
+@@ -97,7 +97,7 @@ int oidc_base64url_encode(request_rec *r, char **dst, const char *src,
+ 			enc_len--;
+ 		if ((enc_len > 0) && (enc[enc_len - 1] == ','))
+ 			enc_len--;
+-		if ((enc_len > 0) &&(enc[enc_len - 1] == ','))
++		if ((enc_len > 0) && (enc[enc_len - 1] == ','))
+ 			enc_len--;
+ 		enc[enc_len] = '\0';
+ 	}
+@@ -320,9 +320,9 @@ char *oidc_util_unescape_string(const request_rec *r, const char *str) {
+ 		return NULL;
+ 	}
+ 	int counter = 0;
+-	char *replaced = (char *)str;
+-	while(str[counter] != '\0') {
+-		if(str[counter] == '+') {
++	char *replaced = (char *) str;
++	while (str[counter] != '\0') {
++		if (str[counter] == '+') {
+ 			replaced[counter] = ' ';
+ 		}
+ 		counter++;
+@@ -353,7 +353,7 @@ char *oidc_util_html_escape(apr_pool_t *pool, const char *s) {
+ 	for (i = 0; i < strlen(s); i++) {
+ 		for (n = 0; n < len; n++) {
+ 			if (s[i] == chars[n]) {
+-				m = (unsigned int)strlen(replace[n]);
++				m = (unsigned int) strlen(replace[n]);
+ 				for (k = 0; k < m; k++)
+ 					r[j + k] = replace[n][k];
+ 				j += m;
+@@ -530,12 +530,13 @@ const char *oidc_get_redirect_uri_iss(request_rec *r, oidc_cfg *cfg,
+ 	const char *redirect_uri = oidc_get_redirect_uri(r, cfg);
+ 	if (provider->issuer_specific_redirect_uri != 0) {
+ 		redirect_uri = apr_psprintf(r->pool, "%s%s%s=%s", redirect_uri,
+-				strchr(redirect_uri ? redirect_uri : "", OIDC_CHAR_QUERY) != NULL ?
+-						OIDC_STR_AMP :
+-						OIDC_STR_QUERY,
+-						OIDC_PROTO_ISS, oidc_util_escape_string(r, provider->issuer));
+-//						OIDC_PROTO_CLIENT_ID,
+-//						oidc_util_escape_string(r, provider->client_id));
++				strchr(redirect_uri ? redirect_uri : "",
++						OIDC_CHAR_QUERY) != NULL ?
++								OIDC_STR_AMP :
++								OIDC_STR_QUERY,
++								OIDC_PROTO_ISS, oidc_util_escape_string(r, provider->issuer));
++		//						OIDC_PROTO_CLIENT_ID,
++		//						oidc_util_escape_string(r, provider->client_id));
+ 		oidc_debug(r, "determined issuer specific redirect uri: %s",
+ 				redirect_uri);
+ 	}
+@@ -1346,8 +1347,8 @@ int oidc_util_html_send(request_rec *r, const char *title,
+ 							on_load ? apr_psprintf(r->pool, " onload=\"%s()\"", on_load) : "",
+ 									html_body ? html_body : "<p></p>");
+ 
+-	return oidc_util_http_send(r, html, strlen(html), OIDC_CONTENT_TYPE_HTML,
+-			status_code);
++	return oidc_util_http_send(r, html, strlen(html),
++			OIDC_CONTENT_TYPE_TEXT_HTML, status_code);
+ }
+ 
+ static char *html_error_template_contents = NULL;
+@@ -1357,7 +1358,8 @@ static char *html_error_template_contents = NULL;
+  * that is relative to the Apache root directory
+  */
+ char *oidc_util_get_full_path(apr_pool_t *pool, const char *abs_or_rel_filename) {
+-	return (abs_or_rel_filename) ? ap_server_root_relative(pool, abs_or_rel_filename) : NULL;
++	return (abs_or_rel_filename) ?
++			ap_server_root_relative(pool, abs_or_rel_filename) : NULL;
+ }
+ 
+ /*
+@@ -1389,7 +1391,7 @@ int oidc_util_html_send_error(request_rec *r, const char *html_template,
+ 							description ? description : ""));
+ 
+ 			return oidc_util_http_send(r, html, strlen(html),
+-					OIDC_CONTENT_TYPE_HTML, status_code);
++					OIDC_CONTENT_TYPE_TEXT_HTML, status_code);
+ 		}
+ 	}
+ 
+@@ -1980,8 +1982,8 @@ void oidc_util_table_add_query_encoded_params(apr_pool_t *pool,
+  * create a symmetric key from a client_secret
+  */
+ apr_byte_t oidc_util_create_symmetric_key(request_rec *r,
+-		const char *client_secret, unsigned int r_key_len, const char *hash_algo,
+-		apr_byte_t set_kid, oidc_jwk_t **jwk) {
++		const char *client_secret, unsigned int r_key_len,
++		const char *hash_algo, apr_byte_t set_kid, oidc_jwk_t **jwk) {
+ 	oidc_jose_error_t err;
+ 	unsigned char *key = NULL;
+ 	unsigned int key_len;
+@@ -2216,7 +2218,8 @@ static const char *oidc_util_hdr_in_get(const request_rec *r, const char *name)
+ 	return value;
+ }
+ 
+-static const char *oidc_util_hdr_in_get_left_most_only(const request_rec *r, const char *name, const char *separator) {
++static const char *oidc_util_hdr_in_get_left_most_only(const request_rec *r,
++		const char *name, const char *separator) {
+ 	char *last = NULL;
+ 	const char *value = oidc_util_hdr_in_get(r, name);
+ 	if (value)
+@@ -2224,6 +2227,29 @@ static const char *oidc_util_hdr_in_get_left_most_only(const request_rec *r, con
+ 	return NULL;
+ }
+ 
++static apr_byte_t oidc_util_hdr_in_contains(const request_rec *r,
++		const char *name, const char *separator, const char postfix_separator,
++		const char *needle) {
++	char *ctx = NULL, *elem = NULL;
++	const char *value = oidc_util_hdr_in_get(r, name);
++	apr_byte_t rc = FALSE;
++	if (value) {
++		elem = apr_strtok(apr_pstrdup(r->pool, value), separator, &ctx);
++		while (elem != NULL) {
++			while (*elem == OIDC_CHAR_SPACE)
++				elem++;
++			if ((strncmp(elem, needle, strlen(needle)) == 0)
++					&& ((elem[strlen(needle)] == '\0')
++							|| (elem[strlen(needle)] == postfix_separator))) {
++				rc = TRUE;
++				break;
++			}
++			elem = apr_strtok(NULL, separator, &ctx);
++		}
++	}
++	return rc;
++}
++
+ static void oidc_util_hdr_table_set(const request_rec *r, apr_table_t *table,
+ 		const char *name, const char *value) {
+ 
+@@ -2288,7 +2314,8 @@ const char *oidc_util_hdr_in_user_agent_get(const request_rec *r) {
+ }
+ 
+ const char *oidc_util_hdr_in_x_forwarded_for_get(const request_rec *r) {
+-	return oidc_util_hdr_in_get_left_most_only(r, OIDC_HTTP_HDR_X_FORWARDED_FOR, OIDC_STR_COMMA);
++	return oidc_util_hdr_in_get_left_most_only(r, OIDC_HTTP_HDR_X_FORWARDED_FOR,
++			OIDC_STR_COMMA);
+ }
+ 
+ const char *oidc_util_hdr_in_content_type_get(const request_rec *r) {
+@@ -2303,20 +2330,29 @@ const char *oidc_util_hdr_in_accept_get(const request_rec *r) {
+ 	return oidc_util_hdr_in_get(r, OIDC_HTTP_HDR_ACCEPT);
+ }
+ 
++apr_byte_t oidc_util_hdr_in_accept_contains(const request_rec *r,
++		const char *needle) {
++	return oidc_util_hdr_in_contains(r, OIDC_HTTP_HDR_ACCEPT, OIDC_STR_COMMA,
++			OIDC_CHAR_SEMI_COLON, needle);
++}
++
+ const char *oidc_util_hdr_in_authorization_get(const request_rec *r) {
+ 	return oidc_util_hdr_in_get(r, OIDC_HTTP_HDR_AUTHORIZATION);
+ }
+ 
+ const char *oidc_util_hdr_in_x_forwarded_proto_get(const request_rec *r) {
+-	return oidc_util_hdr_in_get_left_most_only(r, OIDC_HTTP_HDR_X_FORWARDED_PROTO, OIDC_STR_COMMA);
++	return oidc_util_hdr_in_get_left_most_only(r,
++			OIDC_HTTP_HDR_X_FORWARDED_PROTO, OIDC_STR_COMMA);
+ }
+ 
+ const char *oidc_util_hdr_in_x_forwarded_port_get(const request_rec *r) {
+-	return oidc_util_hdr_in_get_left_most_only(r, OIDC_HTTP_HDR_X_FORWARDED_PORT, OIDC_STR_COMMA);
++	return oidc_util_hdr_in_get_left_most_only(r,
++			OIDC_HTTP_HDR_X_FORWARDED_PORT, OIDC_STR_COMMA);
+ }
+ 
+ const char *oidc_util_hdr_in_x_forwarded_host_get(const request_rec *r) {
+-	return oidc_util_hdr_in_get_left_most_only(r, OIDC_HTTP_HDR_X_FORWARDED_HOST, OIDC_STR_COMMA);
++	return oidc_util_hdr_in_get_left_most_only(r,
++			OIDC_HTTP_HDR_X_FORWARDED_HOST, OIDC_STR_COMMA);
+ }
+ 
+ const char *oidc_util_hdr_in_host_get(const request_rec *r) {
diff --git a/SOURCES/0011-oops-document-OIDCStateMaxNumberOfCookies-for-releas.patch b/SOURCES/0011-oops-document-OIDCStateMaxNumberOfCookies-for-releas.patch
new file mode 100644
index 0000000..9979909
--- /dev/null
+++ b/SOURCES/0011-oops-document-OIDCStateMaxNumberOfCookies-for-releas.patch
@@ -0,0 +1,33 @@
+From 7f81371f55a4a28000675023ad949e217d22bea3 Mon Sep 17 00:00:00 2001
+From: Hans Zandbelt <hans.zandbelt@zmartzone.eu>
+Date: Wed, 12 Sep 2018 20:05:34 +0200
+Subject: [PATCH 11/11] oops: document OIDCStateMaxNumberOfCookies for release
+ 2.3.8
+
+Signed-off-by: Hans Zandbelt <hans.zandbelt@zmartzone.eu>
+(cherry picked from commit 406a4915d3747d4cfdc2283f287a99ba1c7b446a)
+---
+ auth_openidc.conf | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+diff --git a/auth_openidc.conf b/auth_openidc.conf
+index eb83c24..48dd027 100644
+--- a/auth_openidc.conf
++++ b/auth_openidc.conf
+@@ -446,6 +446,13 @@
+ # When not defined, no cookies are stripped.
+ #OIDCStripCookies [<cookie-name>]+
+ 
++# Specify the maximum number of state cookies i.e. the maximum number of parallel outstanding
++# authentication requests. See: https://github.com/zmartzone/mod_auth_openidc/issues/331
++# Setting this to 0 means unlimited, until the browser or server gives up which is the
++# behavior of mod_auth_openidc < 2.3.8, which did not have this configuration option. 
++# When not defined, the default is 7.
++#OIDCStateMaxNumberOfCookies <number>
++
+ ########################################################################################
+ #
+ # Session Settings (only relevant in an OpenID Connect Relying Party setup)
+-- 
+2.26.2
+
diff --git a/SOURCES/0012-optionally-delete-the-oldest-state-cookie-s-see-399.patch b/SOURCES/0012-optionally-delete-the-oldest-state-cookie-s-see-399.patch
new file mode 100644
index 0000000..2da8779
--- /dev/null
+++ b/SOURCES/0012-optionally-delete-the-oldest-state-cookie-s-see-399.patch
@@ -0,0 +1,285 @@
+From 95baad3342aa7ef7172ad4922eb9f0d605dc469b Mon Sep 17 00:00:00 2001
+From: Hans Zandbelt <hans.zandbelt@zmartzone.eu>
+Date: Thu, 6 Dec 2018 14:55:44 +0100
+Subject: [PATCH 12/13] optionally delete the oldest state cookie(s); see #399
+
+bump to 2.3.10rc3
+
+Signed-off-by: Hans Zandbelt <hans.zandbelt@zmartzone.eu>
+(cherry picked from commit 46758a75eef8e3c91f2917fc7c6302136eb18809)
+---
+ auth_openidc.conf      |  8 +++--
+ src/config.c           | 27 +++++++++++++----
+ src/mod_auth_openidc.c | 66 +++++++++++++++++++++++++++++++++++++++---
+ src/mod_auth_openidc.h |  2 ++
+ src/parse.c            |  9 ++++--
+ src/parse.h            |  2 +-
+ 6 files changed, 100 insertions(+), 14 deletions(-)
+
+diff --git a/auth_openidc.conf b/auth_openidc.conf
+index 48dd027..33cea64 100644
+--- a/auth_openidc.conf
++++ b/auth_openidc.conf
+@@ -450,8 +450,12 @@
+ # authentication requests. See: https://github.com/zmartzone/mod_auth_openidc/issues/331
+ # Setting this to 0 means unlimited, until the browser or server gives up which is the
+ # behavior of mod_auth_openidc < 2.3.8, which did not have this configuration option. 
+-# When not defined, the default is 7.
+-#OIDCStateMaxNumberOfCookies <number>
++#
++# The optional second boolean parameter if the oldest state cookie(s) will be deleted, 
++# even if still valid; see #399.
++#
++# When not defined, the default is 7 and "false", thus the oldest cookie(s) will not be deleted.
++#OIDCStateMaxNumberOfCookies <number> [false|true]
+ 
+ ########################################################################################
+ #
+diff --git a/src/config.c b/src/config.c
+index 6fa6227..8e56716 100644
+--- a/src/config.c
++++ b/src/config.c
+@@ -106,6 +106,8 @@
+ #define OIDC_DEFAULT_STATE_TIMEOUT 300
+ /* maximum number of parallel state cookies; 0 means unlimited, until the browser or server gives up */
+ #define OIDC_DEFAULT_MAX_NUMBER_OF_STATE_COOKIES 7
++/* default setting for deleting the oldest state cookies */
++#define OIDC_DEFAULT_DELETE_OLDEST_STATE_COOKIES 0
+ /* default session inactivity timeout */
+ #define OIDC_DEFAULT_SESSION_INACTIVITY_TIMEOUT 300
+ /* default session max duration */
+@@ -1001,11 +1003,12 @@ static const char *oidc_set_client_auth_bearer_token(cmd_parms *cmd,
+  * set the maximun number of parallel state cookies
+  */
+ static const char *oidc_set_max_number_of_state_cookies(cmd_parms *cmd,
+-		void *struct_ptr, const char *arg) {
++		void *struct_ptr, const char *arg1, const char *arg2) {
+ 	oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
+ 			cmd->server->module_config, &auth_openidc_module);
+-	const char *rv = oidc_parse_max_number_of_state_cookies(cmd->pool, arg,
+-			&cfg->max_number_of_state_cookies);
++	const char *rv = oidc_parse_max_number_of_state_cookies(cmd->pool, arg1,
++			arg2, &cfg->max_number_of_state_cookies,
++			&cfg->delete_oldest_state_cookies);
+ 	return OIDC_CONFIG_DIR_RV(cmd, rv);
+ }
+ 
+@@ -1018,6 +1021,15 @@ int oidc_cfg_max_number_of_state_cookies(oidc_cfg *cfg) {
+ 	return cfg->max_number_of_state_cookies;
+ }
+ 
++/*
++ * return the number of oldest state cookies that need to be deleted
++ */
++int oidc_cfg_delete_oldest_state_cookies(oidc_cfg *cfg) {
++	if (cfg->delete_oldest_state_cookies == OIDC_CONFIG_POS_INT_UNSET)
++		return OIDC_DEFAULT_DELETE_OLDEST_STATE_COOKIES;
++	return cfg->delete_oldest_state_cookies;
++}
++
+ /*
+  * create a new server config record with defaults
+  */
+@@ -1127,6 +1139,7 @@ void *oidc_create_server_config(apr_pool_t *pool, server_rec *svr) {
+ 	c->http_timeout_short = OIDC_DEFAULT_HTTP_TIMEOUT_SHORT;
+ 	c->state_timeout = OIDC_DEFAULT_STATE_TIMEOUT;
+ 	c->max_number_of_state_cookies = OIDC_CONFIG_POS_INT_UNSET;
++	c->delete_oldest_state_cookies = OIDC_CONFIG_POS_INT_UNSET;
+ 	c->session_inactivity_timeout = OIDC_DEFAULT_SESSION_INACTIVITY_TIMEOUT;
+ 
+ 	c->cookie_domain = NULL;
+@@ -1445,6 +1458,10 @@ void *oidc_merge_server_config(apr_pool_t *pool, void *BASE, void *ADD) {
+ 			add->max_number_of_state_cookies != OIDC_CONFIG_POS_INT_UNSET ?
+ 					add->max_number_of_state_cookies :
+ 					base->max_number_of_state_cookies;
++	c->delete_oldest_state_cookies =
++			add->delete_oldest_state_cookies != OIDC_CONFIG_POS_INT_UNSET ?
++					add->delete_oldest_state_cookies :
++					base->delete_oldest_state_cookies;
+ 	c->session_inactivity_timeout =
+ 			add->session_inactivity_timeout
+ 			!= OIDC_DEFAULT_SESSION_INACTIVITY_TIMEOUT ?
+@@ -2656,11 +2673,11 @@ const command_rec oidc_config_cmds[] = {
+ 				(void*)APR_OFFSETOF(oidc_cfg, state_timeout),
+ 				RSRC_CONF,
+ 				"Time to live in seconds for state parameter (cq. interval in which the authorization request and the corresponding response need to be completed)."),
+-		AP_INIT_TAKE1(OIDCStateMaxNumberOfCookies,
++		AP_INIT_TAKE12(OIDCStateMaxNumberOfCookies,
+ 				oidc_set_max_number_of_state_cookies,
+ 				(void*)APR_OFFSETOF(oidc_cfg, max_number_of_state_cookies),
+ 				RSRC_CONF,
+-				"Maximun number of parallel state cookies i.e. outstanding authorization requests."),
++				"Maximun number of parallel state cookies i.e. outstanding authorization requests and whether to delete the oldest cookie(s)."),
+ 		AP_INIT_TAKE1(OIDCSessionInactivityTimeout,
+ 				oidc_set_session_inactivity_timeout,
+ 				(void*)APR_OFFSETOF(oidc_cfg, session_inactivity_timeout),
+diff --git a/src/mod_auth_openidc.c b/src/mod_auth_openidc.c
+index 897a449..8740e02 100644
+--- a/src/mod_auth_openidc.c
++++ b/src/mod_auth_openidc.c
+@@ -686,15 +686,53 @@ static apr_byte_t oidc_unsolicited_proto_state(request_rec *r, oidc_cfg *c,
+ 	return TRUE;
+ }
+ 
++typedef struct oidc_state_cookies_t {
++	char *name;
++	apr_time_t timestamp;
++	struct oidc_state_cookies_t *next;
++} oidc_state_cookies_t;
++
++static int oidc_delete_oldest_state_cookies(request_rec *r,
++		int number_of_valid_state_cookies, int max_number_of_state_cookies,
++		oidc_state_cookies_t *first) {
++	oidc_state_cookies_t *cur = NULL, *prev = NULL, *prev_oldest = NULL,
++			*oldest = NULL;
++	while (number_of_valid_state_cookies >= max_number_of_state_cookies) {
++		oldest = first;
++		prev_oldest = NULL;
++		prev = first;
++		cur = first->next;
++		while (cur) {
++			if ((cur->timestamp < oldest->timestamp)) {
++				oldest = cur;
++				prev_oldest = prev;
++			}
++			prev = cur;
++			cur = cur->next;
++		}
++		oidc_warn(r,
++				"deleting oldest state cookie: %s (time until expiry " APR_TIME_T_FMT " seconds)",
++				oldest->name, apr_time_sec(oldest->timestamp - apr_time_now()));
++		oidc_util_set_cookie(r, oldest->name, "", 0, NULL);
++		if (prev_oldest)
++			prev_oldest->next = oldest->next;
++		else
++			first = first->next;
++		number_of_valid_state_cookies--;
++	}
++	return number_of_valid_state_cookies;
++}
++
+ /*
+  * clean state cookies that have expired i.e. for outstanding requests that will never return
+  * successfully and return the number of remaining valid cookies/outstanding-requests while
+  * doing so
+  */
+ static int oidc_clean_expired_state_cookies(request_rec *r, oidc_cfg *c,
+-		const char *currentCookieName) {
++		const char *currentCookieName, int delete_oldest) {
+ 	int number_of_valid_state_cookies = 0;
+-	char *cookie, *tokenizerCtx;
++	oidc_state_cookies_t *first = NULL, *last = NULL;
++	char *cookie, *tokenizerCtx = NULL;
+ 	char *cookies = apr_pstrdup(r->pool, oidc_util_hdr_in_cookie_get(r));
+ 	if (cookies != NULL) {
+ 		cookie = apr_strtok(cookies, OIDC_STR_SEMI_COLON, &tokenizerCtx);
+@@ -722,6 +760,18 @@ static int oidc_clean_expired_state_cookies(request_rec *r, oidc_cfg *c,
+ 								oidc_util_set_cookie(r, cookieName, "", 0,
+ 										NULL);
+ 							} else {
++								if (first == NULL) {
++									first = apr_pcalloc(r->pool,
++											sizeof(oidc_state_cookies_t));
++									last = first;
++								} else {
++									last->next = apr_pcalloc(r->pool,
++											sizeof(oidc_state_cookies_t));
++									last = last->next;
++								}
++								last->name = cookieName;
++								last->timestamp = ts;
++								last->next = NULL;
+ 								number_of_valid_state_cookies++;
+ 							}
+ 							oidc_proto_state_destroy(proto_state);
+@@ -732,6 +782,12 @@ static int oidc_clean_expired_state_cookies(request_rec *r, oidc_cfg *c,
+ 			cookie = apr_strtok(NULL, OIDC_STR_SEMI_COLON, &tokenizerCtx);
+ 		}
+ 	}
++
++	if (delete_oldest > 0)
++		number_of_valid_state_cookies = oidc_delete_oldest_state_cookies(r,
++				number_of_valid_state_cookies, c->max_number_of_state_cookies,
++				first);
++
+ 	return number_of_valid_state_cookies;
+ }
+ 
+@@ -746,7 +802,7 @@ static apr_byte_t oidc_restore_proto_state(request_rec *r, oidc_cfg *c,
+ 	const char *cookieName = oidc_get_state_cookie_name(r, state);
+ 
+ 	/* clean expired state cookies to avoid pollution */
+-	oidc_clean_expired_state_cookies(r, c, cookieName);
++	oidc_clean_expired_state_cookies(r, c, cookieName, FALSE);
+ 
+ 	/* get the state cookie value first */
+ 	char *cookieValue = oidc_util_get_cookie(r, cookieName);
+@@ -820,10 +876,12 @@ static int oidc_authorization_request_set_cookie(request_rec *r, oidc_cfg *c,
+ 	 * clean expired state cookies to avoid pollution and optionally
+ 	 * try to avoid the number of state cookies exceeding a max
+ 	 */
+-	int number_of_cookies = oidc_clean_expired_state_cookies(r, c, NULL);
++	int number_of_cookies = oidc_clean_expired_state_cookies(r, c, NULL,
++			oidc_cfg_delete_oldest_state_cookies(c));
+ 	int max_number_of_cookies = oidc_cfg_max_number_of_state_cookies(c);
+ 	if ((max_number_of_cookies > 0)
+ 			&& (number_of_cookies >= max_number_of_cookies)) {
++
+ 		oidc_warn(r,
+ 				"the number of existing, valid state cookies (%d) has exceeded the limit (%d), no additional authorization request + state cookie can be generated, aborting the request",
+ 				number_of_cookies, max_number_of_cookies);
+diff --git a/src/mod_auth_openidc.h b/src/mod_auth_openidc.h
+index f89f392..c3a0a23 100644
+--- a/src/mod_auth_openidc.h
++++ b/src/mod_auth_openidc.h
+@@ -380,6 +380,7 @@ typedef struct oidc_cfg {
+ 	int http_timeout_short;
+ 	int state_timeout;
+ 	int max_number_of_state_cookies;
++	int delete_oldest_state_cookies;
+ 	int session_inactivity_timeout;
+ 	int session_cache_fallback_to_cookie;
+ 
+@@ -691,6 +692,7 @@ int oidc_cfg_session_cache_fallback_to_cookie(request_rec *r);
+ const char *oidc_parse_pkce_type(apr_pool_t *pool, const char *arg, oidc_proto_pkce_t **type);
+ const char *oidc_cfg_claim_prefix(request_rec *r);
+ int oidc_cfg_max_number_of_state_cookies(oidc_cfg *cfg);
++int oidc_cfg_delete_oldest_state_cookies(oidc_cfg *cfg);
+ 
+ // oidc_util.c
+ int oidc_strnenvcmp(const char *a, const char *b, int len);
+diff --git a/src/parse.c b/src/parse.c
+index 0f986fd..2d09584 100644
+--- a/src/parse.c
++++ b/src/parse.c
+@@ -1245,7 +1245,12 @@ const char *oidc_parse_auth_request_method(apr_pool_t *pool, const char *arg,
+  * parse the maximum number of parallel state cookies
+  */
+ const char *oidc_parse_max_number_of_state_cookies(apr_pool_t *pool,
+-		const char *arg, int *int_value) {
+-	return oidc_parse_int_valid(pool, arg, int_value,
++		const char *arg1, const char *arg2, int *int_value, int *bool_value) {
++	const char *rv = NULL;
++
++	rv = oidc_parse_int_valid(pool, arg1, int_value,
+ 			oidc_valid_max_number_of_state_cookies);
++	if ((rv == NULL) && (arg2 != NULL))
++		rv = oidc_parse_boolean(pool, arg2, bool_value);
++	return rv;
+ }
+diff --git a/src/parse.h b/src/parse.h
+index 6355db4..bdf5651 100644
+--- a/src/parse.h
++++ b/src/parse.h
+@@ -117,7 +117,7 @@ const char *oidc_parse_info_hook_data(apr_pool_t *pool, const char *arg, apr_has
+ const char *oidc_parse_token_binding_policy(apr_pool_t *pool, const char *arg, int *int_value);
+ const char *oidc_token_binding_policy2str(apr_pool_t *pool, int v);
+ const char *oidc_parse_auth_request_method(apr_pool_t *pool, const char *arg, int *method);
+-const char *oidc_parse_max_number_of_state_cookies(apr_pool_t *pool, const char *arg, int *int_value);
++const char *oidc_parse_max_number_of_state_cookies(apr_pool_t *pool, const char *arg1, const char *arg2, int *int_value, int *bool_value);
+ 
+ typedef const char *(*oidc_valid_int_function_t)(apr_pool_t *, int);
+ typedef const char *(*oidc_valid_function_t)(apr_pool_t *, const char *);
+-- 
+2.26.2
+
diff --git a/SOURCES/0013-Allow-configuring-which-header-value-is-used-to-calc.patch b/SOURCES/0013-Allow-configuring-which-header-value-is-used-to-calc.patch
new file mode 100644
index 0000000..2746022
--- /dev/null
+++ b/SOURCES/0013-Allow-configuring-which-header-value-is-used-to-calc.patch
@@ -0,0 +1,240 @@
+From 5a1f90847efce160631ee2a16d7b9d1da3496616 Mon Sep 17 00:00:00 2001
+From: Hiroyuki Wada <h2-wada@nri.co.jp>
+Date: Tue, 21 Apr 2020 20:29:25 +0900
+Subject: [PATCH 13/13] Allow configuring which header value is used to
+ calculate the fingerprint of the state during authentication
+
+(cherry picked from commit 2a97eced569ebbff82fc0370c4f741d04ba7cb13)
+---
+ auth_openidc.conf      |  4 ++++
+ src/config.c           | 25 +++++++++++++++++++++++++
+ src/mod_auth_openidc.c | 30 +++++++++++++++++-------------
+ src/mod_auth_openidc.h |  5 +++++
+ src/parse.c            | 33 +++++++++++++++++++++++++++++++++
+ src/parse.h            |  1 +
+ 6 files changed, 85 insertions(+), 13 deletions(-)
+
+diff --git a/auth_openidc.conf b/auth_openidc.conf
+index 33cea64..4012df3 100644
+--- a/auth_openidc.conf
++++ b/auth_openidc.conf
+@@ -774,3 +774,7 @@
+ # When not defined no claims are whitelisted and all claims are stored except when blacklisted with OIDCBlackListedClaims.
+ #OIDCWhiteListedClaims [<claim>]+
+ 
++# Defines whether the value of the User-Agent and X-Forwarded-For headers will be used as the input
++# for calculating the fingerprint of the state during authentication.
++# When not defined the default "both" is used.
++#OIDCStateInputHeaders [none|user-agent|x-forwarded-for|both]
+diff --git a/src/config.c b/src/config.c
+index 8e56716..588e1a3 100644
+--- a/src/config.c
++++ b/src/config.c
+@@ -166,6 +166,8 @@
+ #define OIDC_DEFAULT_AUTH_REQUEST_METHOD OIDC_AUTH_REQUEST_METHOD_GET
+ /* define whether the issuer will be added to the redirect uri by default to mitigate the IDP mixup attack */
+ #define OIDC_DEFAULT_PROVIDER_ISSUER_SPECIFIC_REDIRECT_URI 0
++/* default setting for calculating the fingerprint of the state from request headers during authentication */
++#define OIDC_DEFAULT_STATE_INPUT_HEADERS OIDC_STATE_INPUT_HEADERS_USER_AGENT | OIDC_STATE_INPUT_HEADERS_X_FORWARDED_FOR
+ 
+ #define OIDCProviderMetadataURL              "OIDCProviderMetadataURL"
+ #define OIDCProviderIssuer                   "OIDCProviderIssuer"
+@@ -261,6 +263,7 @@
+ #define OIDCProviderAuthRequestMethod        "OIDCProviderAuthRequestMethod"
+ #define OIDCBlackListedClaims                "OIDCBlackListedClaims"
+ #define OIDCOAuthServerMetadataURL           "OIDCOAuthServerMetadataURL"
++#define OIDCStateInputHeaders                  "OIDCStateInputHeaders"
+ 
+ extern module AP_MODULE_DECLARE_DATA auth_openidc_module;
+ 
+@@ -1030,6 +1033,17 @@ int oidc_cfg_delete_oldest_state_cookies(oidc_cfg *cfg) {
+ 	return cfg->delete_oldest_state_cookies;
+ }
+ 
++/*
++ * define which header we use for calculating the fingerprint of the state during authentication
++ */
++static const char * oidc_set_state_input_headers_as(cmd_parms *cmd, void *m,
++		const char *arg) {
++	oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
++			cmd->server->module_config, &auth_openidc_module);
++	const char *rv = oidc_parse_set_state_input_headers_as(cmd->pool, arg, &cfg->state_input_headers);
++	return OIDC_CONFIG_DIR_RV(cmd, rv);
++}
++
+ /*
+  * create a new server config record with defaults
+  */
+@@ -1176,6 +1190,8 @@ void *oidc_create_server_config(apr_pool_t *pool, server_rec *svr) {
+ 	c->provider.issuer_specific_redirect_uri =
+ 			OIDC_DEFAULT_PROVIDER_ISSUER_SPECIFIC_REDIRECT_URI;
+ 
++	c->state_input_headers = OIDC_DEFAULT_STATE_INPUT_HEADERS;
++
+ 	return c;
+ }
+ 
+@@ -1617,6 +1633,10 @@ void *oidc_merge_server_config(apr_pool_t *pool, void *BASE, void *ADD) {
+ 					add->provider.issuer_specific_redirect_uri :
+ 					base->provider.issuer_specific_redirect_uri;
+ 
++	c->state_input_headers =
++			add->state_input_headers != OIDC_DEFAULT_STATE_INPUT_HEADERS ?
++					add->state_input_headers : base->state_input_headers;
++
+ 	return c;
+ }
+ 
+@@ -2866,5 +2886,10 @@ const command_rec oidc_config_cmds[] = {
+ 				(void*)APR_OFFSETOF(oidc_cfg, oauth.metadata_url),
+ 				RSRC_CONF,
+ 				"Authorization Server metadata URL."),
++		AP_INIT_TAKE123(OIDCStateInputHeaders,
++				oidc_set_state_input_headers_as,
++				NULL,
++				RSRC_CONF,
++				"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)."),
+ 		{ NULL }
+ };
+diff --git a/src/mod_auth_openidc.c b/src/mod_auth_openidc.c
+index 8740e02..38558d2 100644
+--- a/src/mod_auth_openidc.c
++++ b/src/mod_auth_openidc.c
+@@ -226,7 +226,7 @@ void oidc_strip_cookies(request_rec *r) {
+ /*
+  * calculates a hash value based on request fingerprint plus a provided nonce string.
+  */
+-static char *oidc_get_browser_state_hash(request_rec *r, const char *nonce) {
++static char *oidc_get_browser_state_hash(request_rec *r, oidc_cfg *c, const char *nonce) {
+ 
+ 	oidc_debug(r, "enter");
+ 
+@@ -238,17 +238,21 @@ static char *oidc_get_browser_state_hash(request_rec *r, const char *nonce) {
+ 	/* Initialize the hash context */
+ 	apr_sha1_init(&sha1);
+ 
+-	/* get the X-FORWARDED-FOR header value  */
+-	value = oidc_util_hdr_in_x_forwarded_for_get(r);
+-	/* if we have a value for this header, concat it to the hash input */
+-	if (value != NULL)
+-		apr_sha1_update(&sha1, value, strlen(value));
++	if (c->state_input_headers & OIDC_STATE_INPUT_HEADERS_X_FORWARDED_FOR) {
++		/* get the X-FORWARDED-FOR header value  */
++		value = oidc_util_hdr_in_x_forwarded_for_get(r);
++		/* if we have a value for this header, concat it to the hash input */
++		if (value != NULL)
++			apr_sha1_update(&sha1, value, strlen(value));
++	}
+ 
+-	/* get the USER-AGENT header value  */
+-	value = oidc_util_hdr_in_user_agent_get(r);
+-	/* if we have a value for this header, concat it to the hash input */
+-	if (value != NULL)
+-		apr_sha1_update(&sha1, value, strlen(value));
++	if (c->state_input_headers & OIDC_STATE_INPUT_HEADERS_USER_AGENT) {
++		/* get the USER-AGENT header value  */
++		value = oidc_util_hdr_in_user_agent_get(r);
++		/* if we have a value for this header, concat it to the hash input */
++		if (value != NULL)
++			apr_sha1_update(&sha1, value, strlen(value));
++	}
+ 
+ 	/* get the remote client IP address or host name */
+ 	/*
+@@ -821,7 +825,7 @@ static apr_byte_t oidc_restore_proto_state(request_rec *r, oidc_cfg *c,
+ 	const char *nonce = oidc_proto_state_get_nonce(*proto_state);
+ 
+ 	/* calculate the hash of the browser fingerprint concatenated with the nonce */
+-	char *calc = oidc_get_browser_state_hash(r, nonce);
++	char *calc = oidc_get_browser_state_hash(r, c, nonce);
+ 	/* compare the calculated hash with the value provided in the authorization response */
+ 	if (apr_strnatcmp(calc, state) != 0) {
+ 		oidc_error(r,
+@@ -2345,7 +2349,7 @@ static int oidc_authenticate_user(request_rec *r, oidc_cfg *c,
+ 		oidc_proto_state_set_pkce_state(proto_state, pkce_state);
+ 
+ 	/* get a hash value that fingerprints the browser concatenated with the random input */
+-	char *state = oidc_get_browser_state_hash(r, nonce);
++	char *state = oidc_get_browser_state_hash(r, c, nonce);
+ 
+ 	/*
+ 	 * create state that restores the context when the authorization response comes in
+diff --git a/src/mod_auth_openidc.h b/src/mod_auth_openidc.h
+index c3a0a23..fada56d 100644
+--- a/src/mod_auth_openidc.h
++++ b/src/mod_auth_openidc.h
+@@ -222,6 +222,9 @@ APLOG_USE_MODULE(auth_openidc);
+ #define OIDC_TOKEN_BINDING_POLICY_REQUIRED  2
+ #define OIDC_TOKEN_BINDING_POLICY_ENFORCED  3
+ 
++#define OIDC_STATE_INPUT_HEADERS_USER_AGENT 1
++#define OIDC_STATE_INPUT_HEADERS_X_FORWARDED_FOR 2
++
+ typedef apr_byte_t (*oidc_proto_pkce_state)(request_rec *r, char **state);
+ typedef apr_byte_t (*oidc_proto_pkce_challenge)(request_rec *r, const char *state, char **code_challenge);
+ typedef apr_byte_t (*oidc_proto_pkce_verifier)(request_rec *r, const char *state, char **code_verifier);
+@@ -403,6 +406,8 @@ typedef struct oidc_cfg {
+ 	apr_hash_t *black_listed_claims;
+ 	apr_hash_t *white_listed_claims;
+ 
++	apr_byte_t state_input_headers;
++
+ } oidc_cfg;
+ 
+ int oidc_check_user_id(request_rec *r);
+diff --git a/src/parse.c b/src/parse.c
+index 2d09584..a0cedcb 100644
+--- a/src/parse.c
++++ b/src/parse.c
+@@ -1254,3 +1254,36 @@ const char *oidc_parse_max_number_of_state_cookies(apr_pool_t *pool,
+ 		rv = oidc_parse_boolean(pool, arg2, bool_value);
+ 	return rv;
+ }
++
++#define OIDC_STATE_INPUT_HEADERS_AS_BOTH            "both"
++#define OIDC_STATE_INPUT_HEADERS_AS_USER_AGENT      "user-agent"
++#define OIDC_STATE_INPUT_HEADERS_AS_X_FORWARDED_FOR "x-forwarded-for"
++#define OIDC_STATE_INPUT_HEADERS_AS_NONE            "none"
++
++/*
++ * parse a "set state input headers as" value from the provided string
++ */
++const char *oidc_parse_set_state_input_headers_as(apr_pool_t *pool, const char *arg,
++		apr_byte_t *state_input_headers) {
++	static char *options[] = {
++			OIDC_STATE_INPUT_HEADERS_AS_BOTH,
++			OIDC_STATE_INPUT_HEADERS_AS_USER_AGENT,
++			OIDC_STATE_INPUT_HEADERS_AS_X_FORWARDED_FOR,
++			OIDC_STATE_INPUT_HEADERS_AS_NONE,
++			NULL };
++	const char *rv = oidc_valid_string_option(pool, arg, options);
++	if (rv != NULL)
++		return rv;
++
++	if (apr_strnatcmp(arg, OIDC_STATE_INPUT_HEADERS_AS_BOTH) == 0) {
++		*state_input_headers = OIDC_STATE_INPUT_HEADERS_USER_AGENT | OIDC_STATE_INPUT_HEADERS_X_FORWARDED_FOR;
++	} else if (apr_strnatcmp(arg, OIDC_STATE_INPUT_HEADERS_AS_USER_AGENT) == 0) {
++		*state_input_headers = OIDC_STATE_INPUT_HEADERS_USER_AGENT;
++	} else if (apr_strnatcmp(arg, OIDC_STATE_INPUT_HEADERS_AS_X_FORWARDED_FOR) == 0) {
++		*state_input_headers = OIDC_STATE_INPUT_HEADERS_X_FORWARDED_FOR;
++	} else if (apr_strnatcmp(arg, OIDC_STATE_INPUT_HEADERS_AS_NONE) == 0) {
++		*state_input_headers = 0;
++	}
++
++	return NULL;
++}
+diff --git a/src/parse.h b/src/parse.h
+index bdf5651..c4301a3 100644
+--- a/src/parse.h
++++ b/src/parse.h
+@@ -118,6 +118,7 @@ const char *oidc_parse_token_binding_policy(apr_pool_t *pool, const char *arg, i
+ const char *oidc_token_binding_policy2str(apr_pool_t *pool, int v);
+ const char *oidc_parse_auth_request_method(apr_pool_t *pool, const char *arg, int *method);
+ const char *oidc_parse_max_number_of_state_cookies(apr_pool_t *pool, const char *arg1, const char *arg2, int *int_value, int *bool_value);
++const char *oidc_parse_set_state_input_headers_as(apr_pool_t *pool, const char *arg, apr_byte_t *state_input_headers);
+ 
+ typedef const char *(*oidc_valid_int_function_t)(apr_pool_t *, int);
+ typedef const char *(*oidc_valid_function_t)(apr_pool_t *, const char *);
+-- 
+2.26.2
+
diff --git a/SOURCES/0014-add-value-of-OIDC_SET_COOKIE_APPEND-env-var-to-Set-C.patch b/SOURCES/0014-add-value-of-OIDC_SET_COOKIE_APPEND-env-var-to-Set-C.patch
new file mode 100644
index 0000000..5aa503a
--- /dev/null
+++ b/SOURCES/0014-add-value-of-OIDC_SET_COOKIE_APPEND-env-var-to-Set-C.patch
@@ -0,0 +1,86 @@
+From 0bd084eb058361517b64a2c10a46c332adc9aeea Mon Sep 17 00:00:00 2001
+From: Hans Zandbelt <hans.zandbelt@zmartzone.eu>
+Date: Wed, 15 Jan 2020 17:58:53 +0100
+Subject: [PATCH 14/19] add value of OIDC_SET_COOKIE_APPEND env var to
+ Set-Cookie headers
+
+- useful for handling changing/upcoming SameSite behaviors across
+different browsers, e.g.:
+  SetEnvIf User-Agent ".*IOS.*" OIDC_SET_COOKIE_APPEND=SameSite=None
+- bump to 2.4.1rc4
+
+Signed-off-by: Hans Zandbelt <hans.zandbelt@zmartzone.eu>
+(cherry picked from commit a326dbe843a755124ecee883db52dcdc26284c26)
+---
+ ChangeLog  |  5 +++++
+ src/util.c | 27 +++++++++++++++++++++++++++
+ 2 files changed, 32 insertions(+)
+
+diff --git a/ChangeLog b/ChangeLog
+index dfe4bd6..fc7c5ae 100644
+--- a/ChangeLog
++++ b/ChangeLog
+@@ -1,3 +1,8 @@
++01/15/2020
++- add value of OIDC_SET_COOKIE_APPEND env var to Set-Cookie headers
++  useful for handling changing/upcoming SameSite behaviors across different browsers, e.g.:
++    SetEnvIf User-Agent ".*IOS.*" OIDC_SET_COOKIE_APPEND=SameSite=None
++
+ 08/04/2018
+ - don't return content with 503 since it will turn the HTTP status code into a 200; see #331
+ 
+diff --git a/src/util.c b/src/util.c
+index 67b2fc3..993718e 100644
+--- a/src/util.c
++++ b/src/util.c
+@@ -914,6 +914,27 @@ static char *oidc_util_get_cookie_path(request_rec *r) {
+ 
+ #define OIDC_COOKIE_MAX_SIZE            4093
+ 
++#define OIDC_SET_COOKIE_APPEND_ENV_VAR  "OIDC_SET_COOKIE_APPEND"
++
++const char *oidc_util_set_cookie_append_value(request_rec *r, oidc_cfg *c) {
++	const char *env_var_value = NULL;
++
++	if (r->subprocess_env != NULL)
++		env_var_value = apr_table_get(r->subprocess_env,
++				OIDC_SET_COOKIE_APPEND_ENV_VAR);
++
++	if (env_var_value == NULL) {
++		oidc_debug(r, "no cookie append environment variable %s found",
++				OIDC_SET_COOKIE_APPEND_ENV_VAR);
++		return NULL;
++	}
++
++	oidc_debug(r, "cookie append environment variable %s=%s found",
++			OIDC_SET_COOKIE_APPEND_ENV_VAR, env_var_value);
++
++	return env_var_value;
++}
++
+ /*
+  * set a cookie in the HTTP response headers
+  */
+@@ -923,6 +944,7 @@ void oidc_util_set_cookie(request_rec *r, const char *cookieName,
+ 	oidc_cfg *c = ap_get_module_config(r->server->module_config,
+ 			&auth_openidc_module);
+ 	char *headerString, *expiresString = NULL;
++	const char *appendString = NULL;
+ 
+ 	/* see if we need to clear the cookie */
+ 	if (apr_strnatcmp(cookieValue, "") == 0)
+@@ -961,6 +983,11 @@ void oidc_util_set_cookie(request_rec *r, const char *cookieName,
+ 	if (ext != NULL)
+ 		headerString = apr_psprintf(r->pool, "%s; %s", headerString, ext);
+ 
++	appendString = oidc_util_set_cookie_append_value(r, c);
++	if (appendString != NULL)
++		headerString = apr_psprintf(r->pool, "%s; %s", headerString,
++				appendString);
++
+ 	/* sanity check on overall cookie value size */
+ 	if (strlen(headerString) > OIDC_COOKIE_MAX_SIZE) {
+ 		oidc_warn(r,
+-- 
+2.26.2
+
diff --git a/SOURCES/0015-pick-OIDC_SET_COOKIE_APPEND-over-ext-passed-in-to-oi.patch b/SOURCES/0015-pick-OIDC_SET_COOKIE_APPEND-over-ext-passed-in-to-oi.patch
new file mode 100644
index 0000000..30f63c0
--- /dev/null
+++ b/SOURCES/0015-pick-OIDC_SET_COOKIE_APPEND-over-ext-passed-in-to-oi.patch
@@ -0,0 +1,35 @@
+From 914f700cd791d370cf363d408e938598023980dc Mon Sep 17 00:00:00 2001
+From: Hans Zandbelt <hans.zandbelt@zmartzone.eu>
+Date: Sun, 19 Jan 2020 16:00:31 +0100
+Subject: [PATCH 15/19] pick OIDC_SET_COOKIE_APPEND over ext passed in to
+ oidc_util_set_cookie
+
+Signed-off-by: Hans Zandbelt <hans.zandbelt@zmartzone.eu>
+(cherry picked from commit 5aa73817172acbb9e86287a54bc4532af7e394ee)
+---
+ src/util.c | 5 ++---
+ 1 file changed, 2 insertions(+), 3 deletions(-)
+
+diff --git a/src/util.c b/src/util.c
+index 993718e..c1fa5f3 100644
+--- a/src/util.c
++++ b/src/util.c
+@@ -980,13 +980,12 @@ void oidc_util_set_cookie(request_rec *r, const char *cookieName,
+ 		headerString = apr_psprintf(r->pool, "%s; %s", headerString,
+ 				OIDC_COOKIE_FLAG_HTTP_ONLY);
+ 
+-	if (ext != NULL)
+-		headerString = apr_psprintf(r->pool, "%s; %s", headerString, ext);
+-
+ 	appendString = oidc_util_set_cookie_append_value(r, c);
+ 	if (appendString != NULL)
+ 		headerString = apr_psprintf(r->pool, "%s; %s", headerString,
+ 				appendString);
++	else if (ext != NULL)
++		headerString = apr_psprintf(r->pool, "%s; %s", headerString, ext);
+ 
+ 	/* sanity check on overall cookie value size */
+ 	if (strlen(headerString) > OIDC_COOKIE_MAX_SIZE) {
+-- 
+2.26.2
+
diff --git a/SOURCES/0016-always-add-a-SameSite-value-to-the-Set-Cookie-header.patch b/SOURCES/0016-always-add-a-SameSite-value-to-the-Set-Cookie-header.patch
new file mode 100644
index 0000000..307a3c9
--- /dev/null
+++ b/SOURCES/0016-always-add-a-SameSite-value-to-the-Set-Cookie-header.patch
@@ -0,0 +1,95 @@
+From 2c999448c87b286744ac9802cb8e4277d5c38b71 Mon Sep 17 00:00:00 2001
+From: Hans Zandbelt <hans.zandbelt@zmartzone.eu>
+Date: Wed, 29 Jan 2020 13:27:44 +0100
+Subject: [PATCH 16/19] always add a SameSite value to the Set-Cookie header
+
+- to satisfy upcoming Chrome/Firefox changes
+  this can be overridden by using, e.g.:
+    SetEnvIf User-Agent ".*IOS.*" OIDC_SET_COOKIE_APPEND=;
+- release 2.4.1rc6
+
+Signed-off-by: Hans Zandbelt <hans.zandbelt@zmartzone.eu>
+(cherry picked from commit 3b4770f49cc67b9b0ae8732e9908895683ea556c)
+---
+ ChangeLog              |  5 +++++
+ src/mod_auth_openidc.c | 10 +++++++---
+ src/mod_auth_openidc.h |  1 +
+ src/session.c          |  2 +-
+ 4 files changed, 14 insertions(+), 4 deletions(-)
+
+diff --git a/ChangeLog b/ChangeLog
+index fc7c5ae..b67f764 100644
+--- a/ChangeLog
++++ b/ChangeLog
+@@ -1,3 +1,8 @@
++01/29/2020
++- always add a SameSite value to the Set-Cookie header to satisfy upcoming Chrome/Firefox changes
++  this can be overridden by using, e.g.:
++    SetEnvIf User-Agent ".*IOS.*" OIDC_SET_COOKIE_APPEND=;
++
+ 01/15/2020
+ - add value of OIDC_SET_COOKIE_APPEND env var to Set-Cookie headers
+   useful for handling changing/upcoming SameSite behaviors across different browsers, e.g.:
+diff --git a/src/mod_auth_openidc.c b/src/mod_auth_openidc.c
+index 38558d2..0d2b37c 100644
+--- a/src/mod_auth_openidc.c
++++ b/src/mod_auth_openidc.c
+@@ -916,7 +916,9 @@ static int oidc_authorization_request_set_cookie(request_rec *r, oidc_cfg *c,
+ 
+ 	/* set it as a cookie */
+ 	oidc_util_set_cookie(r, cookieName, cookieValue, -1,
+-			c->cookie_same_site ? OIDC_COOKIE_EXT_SAME_SITE_LAX : NULL);
++			c->cookie_same_site ?
++					OIDC_COOKIE_EXT_SAME_SITE_LAX :
++					OIDC_COOKIE_EXT_SAME_SITE_NONE);
+ 
+ 	return HTTP_OK;
+ }
+@@ -2183,7 +2185,7 @@ static int oidc_discovery(request_rec *r, oidc_cfg *cfg) {
+ 		oidc_util_set_cookie(r, OIDC_CSRF_NAME, csrf, -1,
+ 				cfg->cookie_same_site ?
+ 						OIDC_COOKIE_EXT_SAME_SITE_STRICT :
+-						NULL);
++						OIDC_COOKIE_EXT_SAME_SITE_NONE);
+ 
+ 		/* see if we need to preserve POST parameters through Javascript/HTML5 storage */
+ 		if (oidc_post_preserve_javascript(r, url, NULL, NULL) == TRUE)
+@@ -2276,7 +2278,9 @@ static int oidc_discovery(request_rec *r, oidc_cfg *cfg) {
+ 	s = apr_psprintf(r->pool, "%s</form>\n", s);
+ 
+ 	oidc_util_set_cookie(r, OIDC_CSRF_NAME, csrf, -1,
+-			cfg->cookie_same_site ? OIDC_COOKIE_EXT_SAME_SITE_STRICT : NULL);
++			cfg->cookie_same_site ?
++					OIDC_COOKIE_EXT_SAME_SITE_STRICT :
++					OIDC_COOKIE_EXT_SAME_SITE_NONE);
+ 
+ 	char *javascript = NULL, *javascript_method = NULL;
+ 	char *html_head =
+diff --git a/src/mod_auth_openidc.h b/src/mod_auth_openidc.h
+index fada56d..5f1a79a 100644
+--- a/src/mod_auth_openidc.h
++++ b/src/mod_auth_openidc.h
+@@ -213,6 +213,7 @@ APLOG_USE_MODULE(auth_openidc);
+ 
+ #define OIDC_COOKIE_EXT_SAME_SITE_LAX    "SameSite=Lax"
+ #define OIDC_COOKIE_EXT_SAME_SITE_STRICT "SameSite=Strict"
++#define OIDC_COOKIE_EXT_SAME_SITE_NONE   "SameSite=None"
+ 
+ /* https://tools.ietf.org/html/draft-ietf-tokbind-ttrp-01 */
+ #define OIDC_TB_CFG_PROVIDED_ENV_VAR     "Sec-Provided-Token-Binding-ID"
+diff --git a/src/session.c b/src/session.c
+index 1c6e118..cd9ccb8 100644
+--- a/src/session.c
++++ b/src/session.c
+@@ -204,7 +204,7 @@ static apr_byte_t oidc_session_save_cache(request_rec *r, oidc_session_t *z,
+ 									(first_time ?
+ 											OIDC_COOKIE_EXT_SAME_SITE_LAX :
+ 											OIDC_COOKIE_EXT_SAME_SITE_STRICT) :
+-											NULL);
++											OIDC_COOKIE_EXT_SAME_SITE_NONE);
+ 
+ 	} else {
+ 		/* clear the cookie */
+-- 
+2.26.2
+
diff --git a/SOURCES/0017-fix-also-add-SameSite-None-to-by-value-session-cooki.patch b/SOURCES/0017-fix-also-add-SameSite-None-to-by-value-session-cooki.patch
new file mode 100644
index 0000000..d89f0e2
--- /dev/null
+++ b/SOURCES/0017-fix-also-add-SameSite-None-to-by-value-session-cooki.patch
@@ -0,0 +1,42 @@
+From ca43d64e722f80ed91871c9ea31fbc7660aa9147 Mon Sep 17 00:00:00 2001
+From: Hans Zandbelt <hans.zandbelt@zmartzone.eu>
+Date: Mon, 3 Feb 2020 10:34:10 +0100
+Subject: [PATCH 17/19] fix: also add SameSite=None to by-value session cookies
+
+bump to 2.4.2rc0
+
+Signed-off-by: Hans Zandbelt <hans.zandbelt@zmartzone.eu>
+(cherry picked from commit f6798246abc8fd8f865db313439882ac9f5771f3)
+---
+ ChangeLog     | 4 ++++
+ src/session.c | 2 +-
+ 2 files changed, 5 insertions(+), 1 deletion(-)
+
+diff --git a/ChangeLog b/ChangeLog
+index b67f764..3db7110 100644
+--- a/ChangeLog
++++ b/ChangeLog
+@@ -1,3 +1,7 @@
++02/03/2020
++- fix: also add SameSite=None to by-value session cookies
++- bump to 2.4.2rc0
++
+ 01/29/2020
+ - always add a SameSite value to the Set-Cookie header to satisfy upcoming Chrome/Firefox changes
+   this can be overridden by using, e.g.:
+diff --git a/src/session.c b/src/session.c
+index cd9ccb8..e7194bd 100644
+--- a/src/session.c
++++ b/src/session.c
+@@ -249,7 +249,7 @@ static apr_byte_t oidc_session_save_cookie(request_rec *r, oidc_session_t *z,
+ 							(first_time ?
+ 									OIDC_COOKIE_EXT_SAME_SITE_LAX :
+ 									OIDC_COOKIE_EXT_SAME_SITE_STRICT) :
+-									NULL);
++									OIDC_COOKIE_EXT_SAME_SITE_NONE);
+ 
+ 	return TRUE;
+ }
+-- 
+2.26.2
+
diff --git a/SOURCES/0018-add-note-on-usage-of-OIDC_SET_COOKIE_APPEND-in-the-s.patch b/SOURCES/0018-add-note-on-usage-of-OIDC_SET_COOKIE_APPEND-in-the-s.patch
new file mode 100644
index 0000000..2bb92d6
--- /dev/null
+++ b/SOURCES/0018-add-note-on-usage-of-OIDC_SET_COOKIE_APPEND-in-the-s.patch
@@ -0,0 +1,32 @@
+From d2f6572e93446d611fc66cf68d0b71cd13366d55 Mon Sep 17 00:00:00 2001
+From: Hans Zandbelt <hans.zandbelt@zmartzone.eu>
+Date: Thu, 30 Jul 2020 10:10:04 +0200
+Subject: [PATCH 18/19] add note on usage of OIDC_SET_COOKIE_APPEND in the
+ sample config/doc
+
+Signed-off-by: Hans Zandbelt <hans.zandbelt@zmartzone.eu>
+(cherry picked from commit bcbdd1993e7449446cb34df696826bd8bc9d2977)
+---
+ auth_openidc.conf | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+diff --git a/auth_openidc.conf b/auth_openidc.conf
+index 4012df3..ce2fba7 100644
+--- a/auth_openidc.conf
++++ b/auth_openidc.conf
+@@ -431,6 +431,12 @@
+ #   state cookie: Lax
+ #   session cookie: first time set Lax, updates (e.g. after inactivity timeout) Strict
+ #   x_csrf discovery: Strict:
++#
++# The default `SameSite=None` cookie appendix on `Set-Cookie` response headers can be 
++# conditionally overridden using an environment variable in the Apache config as in:
++#   SetEnvIf User-Agent ".*IOS.*" OIDC_SET_COOKIE_APPEND=;
++# (since version 2.4.1)
++#
+ # When not defined the default is Off.
+ #OIDCCookieSameSite [On|Off]
+ 
+-- 
+2.26.2
+
diff --git a/SOURCES/0019-add-SameSite-attribute-on-cookie-clearance-logout.patch b/SOURCES/0019-add-SameSite-attribute-on-cookie-clearance-logout.patch
new file mode 100644
index 0000000..6950693
--- /dev/null
+++ b/SOURCES/0019-add-SameSite-attribute-on-cookie-clearance-logout.patch
@@ -0,0 +1,204 @@
+From b68ac577b465f54e7eb38e48a65e5cf4c88c74c5 Mon Sep 17 00:00:00 2001
+From: Hans Zandbelt <hans.zandbelt@zmartzone.eu>
+Date: Thu, 3 Sep 2020 16:52:30 +0200
+Subject: [PATCH 19/19] add SameSite attribute on cookie clearance / logout
+
+bump to 2.4.4.1; thanks @v0gler
+
+Signed-off-by: Hans Zandbelt <hans.zandbelt@zmartzone.eu>
+(cherry picked from commit 314000d179c2d08af6897725f37980e1f0891aa6)
+---
+ ChangeLog              |  4 ++++
+ src/mod_auth_openidc.c | 42 +++++++++++++++++++++++-------------------
+ src/mod_auth_openidc.h |  5 +++++
+ src/session.c          | 16 +++++++++-------
+ 4 files changed, 41 insertions(+), 26 deletions(-)
+
+diff --git a/ChangeLog b/ChangeLog
+index 3db7110..eba2ebc 100644
+--- a/ChangeLog
++++ b/ChangeLog
+@@ -1,3 +1,7 @@
++09/03/2020
++- add SameSite attribute on cookie clearance / logout; thanks @v0gler
++- bump to 2.4.4.1
++
+ 02/03/2020
+ - fix: also add SameSite=None to by-value session cookies
+ - bump to 2.4.2rc0
+diff --git a/src/mod_auth_openidc.c b/src/mod_auth_openidc.c
+index 0d2b37c..68fa2ce 100644
+--- a/src/mod_auth_openidc.c
++++ b/src/mod_auth_openidc.c
+@@ -717,7 +717,8 @@ static int oidc_delete_oldest_state_cookies(request_rec *r,
+ 		oidc_warn(r,
+ 				"deleting oldest state cookie: %s (time until expiry " APR_TIME_T_FMT " seconds)",
+ 				oldest->name, apr_time_sec(oldest->timestamp - apr_time_now()));
+-		oidc_util_set_cookie(r, oldest->name, "", 0, NULL);
++		oidc_util_set_cookie(r, oldest->name, "", 0,
++				OIDC_COOKIE_EXT_SAME_SITE_NONE);
+ 		if (prev_oldest)
+ 			prev_oldest->next = oldest->next;
+ 		else
+@@ -762,7 +763,7 @@ static int oidc_clean_expired_state_cookies(request_rec *r, oidc_cfg *c,
+ 								oidc_error(r, "state (%s) has expired",
+ 										cookieName);
+ 								oidc_util_set_cookie(r, cookieName, "", 0,
+-										NULL);
++										OIDC_COOKIE_EXT_SAME_SITE_NONE);
+ 							} else {
+ 								if (first == NULL) {
+ 									first = apr_pcalloc(r->pool,
+@@ -779,6 +780,12 @@ static int oidc_clean_expired_state_cookies(request_rec *r, oidc_cfg *c,
+ 								number_of_valid_state_cookies++;
+ 							}
+ 							oidc_proto_state_destroy(proto_state);
++						} else {
++							oidc_warn(r,
++									"state cookie could not be retrieved/decoded, deleting: %s",
++									cookieName);
++							oidc_util_set_cookie(r, cookieName, "", 0,
++									OIDC_COOKIE_EXT_SAME_SITE_NONE);
+ 						}
+ 					}
+ 				}
+@@ -816,7 +823,7 @@ static apr_byte_t oidc_restore_proto_state(request_rec *r, oidc_cfg *c,
+ 	}
+ 
+ 	/* clear state cookie because we don't need it anymore */
+-	oidc_util_set_cookie(r, cookieName, "", 0, NULL);
++	oidc_util_set_cookie(r, cookieName, "", 0, OIDC_COOKIE_EXT_SAME_SITE_NONE);
+ 
+ 	*proto_state = oidc_proto_state_from_cookie(r, c, cookieValue);
+ 	if (*proto_state == NULL)
+@@ -916,9 +923,7 @@ static int oidc_authorization_request_set_cookie(request_rec *r, oidc_cfg *c,
+ 
+ 	/* set it as a cookie */
+ 	oidc_util_set_cookie(r, cookieName, cookieValue, -1,
+-			c->cookie_same_site ?
+-					OIDC_COOKIE_EXT_SAME_SITE_LAX :
+-					OIDC_COOKIE_EXT_SAME_SITE_NONE);
++			OIDC_COOKIE_SAMESITE_LAX(c));
+ 
+ 	return HTTP_OK;
+ }
+@@ -2183,9 +2188,7 @@ static int oidc_discovery(request_rec *r, oidc_cfg *cfg) {
+ 
+ 		/* set CSRF cookie */
+ 		oidc_util_set_cookie(r, OIDC_CSRF_NAME, csrf, -1,
+-				cfg->cookie_same_site ?
+-						OIDC_COOKIE_EXT_SAME_SITE_STRICT :
+-						OIDC_COOKIE_EXT_SAME_SITE_NONE);
++				OIDC_COOKIE_SAMESITE_STRICT(cfg));
+ 
+ 		/* see if we need to preserve POST parameters through Javascript/HTML5 storage */
+ 		if (oidc_post_preserve_javascript(r, url, NULL, NULL) == TRUE)
+@@ -2278,9 +2281,7 @@ static int oidc_discovery(request_rec *r, oidc_cfg *cfg) {
+ 	s = apr_psprintf(r->pool, "%s</form>\n", s);
+ 
+ 	oidc_util_set_cookie(r, OIDC_CSRF_NAME, csrf, -1,
+-			cfg->cookie_same_site ?
+-					OIDC_COOKIE_EXT_SAME_SITE_STRICT :
+-					OIDC_COOKIE_EXT_SAME_SITE_NONE);
++			OIDC_COOKIE_SAMESITE_STRICT(cfg));
+ 
+ 	char *javascript = NULL, *javascript_method = NULL;
+ 	char *html_head =
+@@ -2501,7 +2502,8 @@ static int oidc_handle_discovery_response(request_rec *r, oidc_cfg *c) {
+ 	if (csrf_cookie) {
+ 
+ 		/* clean CSRF cookie */
+-		oidc_util_set_cookie(r, OIDC_CSRF_NAME, "", 0, NULL);
++		oidc_util_set_cookie(r, OIDC_CSRF_NAME, "", 0,
++				OIDC_COOKIE_EXT_SAME_SITE_NONE);
+ 
+ 		/* compare CSRF cookie value with query parameter value */
+ 		if ((csrf_query == NULL)
+@@ -2639,12 +2641,12 @@ static int oidc_handle_logout_request(request_rec *r, oidc_cfg *c,
+ 
+ 	oidc_debug(r, "enter (url=%s)", url);
+ 
+-	/* if there's no remote_user then there's no (stored) session to kill */
+-	if (session->remote_user != NULL) {
+-
+-		/* remove session state (cq. cache entry and cookie) */
+-		oidc_session_kill(r, session);
+-	}
++	/*
++	 * remove session state (cq. cache entry and cookie)
++	 * always clear the session cookie because the cookie may be not sent (but still in the browser)
++	 * due to SameSite policies
++	 */
++	oidc_session_kill(r, session);
+ 
+ 	/* see if this is the OP calling us */
+ 	if (oidc_is_front_channel_logout(url)) {
+@@ -2661,6 +2663,8 @@ static int oidc_handle_logout_request(request_rec *r, oidc_cfg *c,
+ 		const char *accept = oidc_util_hdr_in_accept_get(r);
+ 		if ((apr_strnatcmp(url, OIDC_IMG_STYLE_LOGOUT_PARAM_VALUE) == 0)
+ 				|| ((accept) && strstr(accept, OIDC_CONTENT_TYPE_IMAGE_PNG))) {
++			// terminate with DONE instead of OK
++			// to avoid Apache returning auth/authz error 401 for the redirect URI
+ 			return oidc_util_http_send(r,
+ 					(const char *) &oidc_transparent_pixel,
+ 					sizeof(oidc_transparent_pixel), OIDC_CONTENT_TYPE_IMAGE_PNG,
+diff --git a/src/mod_auth_openidc.h b/src/mod_auth_openidc.h
+index 5f1a79a..6821d0c 100644
+--- a/src/mod_auth_openidc.h
++++ b/src/mod_auth_openidc.h
+@@ -215,6 +215,11 @@ APLOG_USE_MODULE(auth_openidc);
+ #define OIDC_COOKIE_EXT_SAME_SITE_STRICT "SameSite=Strict"
+ #define OIDC_COOKIE_EXT_SAME_SITE_NONE   "SameSite=None"
+ 
++#define OIDC_COOKIE_SAMESITE_STRICT(c) \
++	c->cookie_same_site ? OIDC_COOKIE_EXT_SAME_SITE_STRICT : OIDC_COOKIE_EXT_SAME_SITE_NONE
++#define OIDC_COOKIE_SAMESITE_LAX(c) \
++	c->cookie_same_site ? OIDC_COOKIE_EXT_SAME_SITE_LAX : OIDC_COOKIE_EXT_SAME_SITE_NONE
++
+ /* https://tools.ietf.org/html/draft-ietf-tokbind-ttrp-01 */
+ #define OIDC_TB_CFG_PROVIDED_ENV_VAR     "Sec-Provided-Token-Binding-ID"
+ 
+diff --git a/src/session.c b/src/session.c
+index e7194bd..539ea21 100644
+--- a/src/session.c
++++ b/src/session.c
+@@ -155,7 +155,7 @@ static apr_byte_t oidc_session_load_cache(request_rec *r, oidc_session_t *z) {
+ 
+ 					/* delete the session cookie */
+ 					oidc_util_set_cookie(r, oidc_cfg_dir_cookie(r), "", 0,
+-							NULL);
++					                OIDC_COOKIE_EXT_SAME_SITE_NONE);
+ 					/* delete the cache entry */
+ 					rc = oidc_cache_set_session(r, z->uuid, NULL, 0);
+ 					/* clear the session */
+@@ -208,7 +208,8 @@ static apr_byte_t oidc_session_save_cache(request_rec *r, oidc_session_t *z,
+ 
+ 	} else {
+ 		/* clear the cookie */
+-		oidc_util_set_cookie(r, oidc_cfg_dir_cookie(r), "", 0, NULL);
++		oidc_util_set_cookie(r, oidc_cfg_dir_cookie(r), "", 0,
++				OIDC_COOKIE_EXT_SAME_SITE_NONE);
+ 
+ 		/* remove the session from the cache */
+ 		rc = oidc_cache_set_session(r, z->uuid, NULL, 0);
+@@ -245,11 +246,12 @@ static apr_byte_t oidc_session_save_cookie(request_rec *r, oidc_session_t *z,
+ 	oidc_util_set_chunked_cookie(r, oidc_cfg_dir_cookie(r), cookieValue,
+ 			c->persistent_session_cookie ? z->expiry : -1,
+ 					c->session_cookie_chunk_size,
+-					c->cookie_same_site ?
+-							(first_time ?
+-									OIDC_COOKIE_EXT_SAME_SITE_LAX :
+-									OIDC_COOKIE_EXT_SAME_SITE_STRICT) :
+-									OIDC_COOKIE_EXT_SAME_SITE_NONE);
++					(z->state == NULL) ? OIDC_COOKIE_EXT_SAME_SITE_NONE :
++							c->cookie_same_site ?
++									(first_time ?
++											OIDC_COOKIE_EXT_SAME_SITE_LAX :
++											OIDC_COOKIE_EXT_SAME_SITE_STRICT) :
++											OIDC_COOKIE_EXT_SAME_SITE_NONE);
+ 
+ 	return TRUE;
+ }
+-- 
+2.26.2
+
diff --git a/SPECS/mod_auth_openidc.spec b/SPECS/mod_auth_openidc.spec
index 43ab2f6..6d55854 100644
--- a/SPECS/mod_auth_openidc.spec
+++ b/SPECS/mod_auth_openidc.spec
@@ -15,7 +15,7 @@
 
 Name:		mod_auth_openidc
 Version:	2.3.7
-Release:	3%{?dist}
+Release:	8%{?dist}
 Summary:	OpenID Connect auth module for Apache HTTP Server
 
 Group:		System Environment/Daemons
@@ -24,6 +24,25 @@ URL:		https://github.com/zmartzone/mod_auth_openidc
 Source0:	https://github.com/zmartzone/mod_auth_openidc/releases/download/v%{version}/mod_auth_openidc-%{version}.tar.gz
 
 Patch1: test-segfault.patch
+Patch2: 0002-Backport-of-improve-validation-of-the-post-logout-UR.patch
+Patch3: 0003-Backport-of-Fix-open-redirect-starting-with-a-slash.patch
+Patch4: 0004-Backport-of-Fix-open-redirect-starting-with-a-slash-.patch
+Patch5: 0005-Fix-the-previous-backports.patch
+Patch6: 0006-add-OIDCStateMaxNumberOfCookies-to-limit-nr-of-state.patch
+Patch7: 0007-set-boundaries-on-min-and-max-values-on-number-of-pa.patch
+Patch8: 0008-make-the-default-max-number-of-state-cookies-7-inste.patch
+Patch9: 0009-don-t-return-content-with-503-see-331.patch
+Patch10: 0010-improve-auto-detection-of-XMLHttpRequests-via-Accept.patch
+Patch11: 0011-oops-document-OIDCStateMaxNumberOfCookies-for-releas.patch
+Patch12: 0012-optionally-delete-the-oldest-state-cookie-s-see-399.patch
+Patch13: 0013-Allow-configuring-which-header-value-is-used-to-calc.patch
+Patch14: 0014-add-value-of-OIDC_SET_COOKIE_APPEND-env-var-to-Set-C.patch
+Patch15: 0015-pick-OIDC_SET_COOKIE_APPEND-over-ext-passed-in-to-oi.patch
+Patch16: 0016-always-add-a-SameSite-value-to-the-Set-Cookie-header.patch
+Patch17: 0017-fix-also-add-SameSite-None-to-by-value-session-cooki.patch
+Patch18: 0018-add-note-on-usage-of-OIDC_SET_COOKIE_APPEND-in-the-s.patch
+Patch19: 0019-add-SameSite-attribute-on-cookie-clearance-logout.patch
+
 
 BuildRequires:  gcc
 BuildRequires:	httpd-devel
@@ -45,6 +64,24 @@ an OpenID Connect Relying Party and/or OAuth 2.0 Resource Server.
 %prep
 %setup -q
 %patch1 -p1
+%patch2 -p1
+%patch3 -p1
+%patch4 -p1
+%patch5 -p1
+%patch6 -p1
+%patch7 -p1
+%patch8 -p1
+%patch9 -p1
+%patch10 -p1
+%patch11 -p1
+%patch12 -p1
+%patch13 -p1
+%patch14 -p1
+%patch15 -p1
+%patch16 -p1
+%patch17 -p1
+%patch18 -p1
+%patch19 -p1
 
 %build
 # workaround rpm-buildroot-usage
@@ -97,6 +134,35 @@ install -m 700 -d $RPM_BUILD_ROOT%{httpd_pkg_cache_dir}/cache
 %dir %attr(0700, apache, apache) %{httpd_pkg_cache_dir}/cache
 
 %changelog
+* Tue Nov 17 2020 Jakub Hrozek <jhrozek@redhat.com> - 2.3.7-8
+- Resolves: rhbz#1823756 - Backport SameSite=None cookie from
+                           mod_auth_openidc upstream to support latest browsers
+
+* Tue Nov 17 2020 Jakub Hrozek <jhrozek@redhat.com> - 2.3.7-7
+- Resolves: rhbz#1897992 - OIDCStateInputHeaders &
+                           OIDCStateMaxNumberOfCookies in existing
+                           mod_auth_openidc version
+- Backport the OIDCStateMaxNumberOfCookies option
+- Configure which header value is used to calculate the fingerprint of
+  the auth state
+
+* Sun May 10 2020 Jakub Hrozek <jhrozek@redhat.com> - 2.3.7-6
+- Fix the previous backport
+- Related: rhbz#1805749 - CVE-2019-14857 mod_auth_openidc:2.3/mod_auth_openidc:
+                          Open redirect in logout url when using URLs with
+                          leading slashes
+- Related: rhbz#1805068 - CVE-2019-20479 mod_auth_openidc:2.3/mod_auth_openidc:
+                          open redirect issue exists in URLs with slash and
+                          backslash
+
+* Sun May 10 2020 Jakub Hrozek <jhrozek@redhat.com> - 2.3.7-5
+- Resolves: rhbz#1805749 - CVE-2019-14857 mod_auth_openidc:2.3/mod_auth_openidc:
+                           Open redirect in logout url when using URLs with
+                           leading slashes
+- Resolves: rhbz#1805068 - CVE-2019-20479 mod_auth_openidc:2.3/mod_auth_openidc:
+                           open redirect issue exists in URLs with slash and
+                           backslash
+
 * Thu Aug 16 2018  <jdennis@redhat.com> - 2.3.7-3
 - Resolves: rhbz# 1614977 - fix unit test segfault,
   the problem was not limited exclusively to s390x, but s390x provoked it.