Repository: oauth2-proxy/oauth2-proxy
Pull request #1752 (2022-08-08 07:37:16)

Azure AD Doc: change permission to openid and add warning on consent
## Description

Azure AD Doc: change permission to "openid" and add warning on consent

## Motivation and Context

The Azure AD Doc mentioned a very broad and risky permission (Directory.AccessAsUser.All), which is not really required by the proxy, and some Admins won't even permit.
This change recommends using the much more restricted "openid", and also warns about the consent that could still be required in certain cases.

## How Has This Been Tested?

N/A; just checked in AAD with the latest UI.

## Checklist:

<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->

- [x] My change requires a change to the documentation or CHANGELOG.
- [x] I have updated the documentation/CHANGELOG accordingly.
- [ ] I have created a feature (non-master) branch for my PR.



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ca7e088a..52a6210d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,9 +7,16 @@
 ## Breaking Changes
 
 ## Changes since v7.3.0
+- [#1691](https://github.com/oauth2-proxy/oauth2-proxy/pull/1691) Fix Redis IdleTimeout when Redis timeout option is set to non-zero (@dimss)
+
+- [#1669](https://github.com/oauth2-proxy/oauth2-proxy/pull/1699) Fix method deprecated error in lint (@t-katsumura)
 
 - [#1709](https://github.com/oauth2-proxy/oauth2-proxy/pull/1709) Show an alert message when basic auth credentials are invalid (@aiciobanu)
 
+- [#1720](https://github.com/oauth2-proxy/oauth2-proxy/pull/1720) Extract roles from authToken, to allow using allowed roles with Keycloak.
+
+- [#1774](https://github.com/oauth2-proxy/oauth2-proxy/pull/1774) Fix vulnerabilities CVE-2022-27191, CVE-2021-44716 and CVE-2022-29526
+
 # V7.3.0
 
 ## Release Highlights
diff --git a/Dockerfile b/Dockerfile
index 7097b3ca..bf90a6c0 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -5,7 +5,7 @@ ARG RUNTIME_IMAGE=alpine:3.15
 #  cache sharing of the go mod download step.
 # Go cross compilation is also faster than emulation the go compilation across
 #  multiple platforms.
-FROM --platform=${BUILDPLATFORM} golang:1.17-buster AS builder
+FROM --platform=${BUILDPLATFORM} golang:1.18-buster AS builder
 
 # Copy sources
 WORKDIR $GOPATH/src/github.com/oauth2-proxy/oauth2-proxy
diff --git a/contrib/local-environment/kubernetes/values.yaml b/contrib/local-environment/kubernetes/values.yaml
index 7b69845f..9181266f 100644
--- a/contrib/local-environment/kubernetes/values.yaml
+++ b/contrib/local-environment/kubernetes/values.yaml
@@ -43,7 +43,7 @@ oauth2-proxy:
   config:
     configFile: |-
       cookie_secret="OQINaROshtE9TcZkNAm-5Zs2Pv3xaWytBmc5W7sPX7w="
-      cookie_domain=".localtest.me"
+      cookie_domains=".localtest.me"
       whitelist_domains=[".localtest.me"]
       # only users with this domain will be let in
       email_domains=["example.com"]
diff --git a/docs/docs/configuration/auth.md b/docs/docs/configuration/auth.md
index 2617e380..f05de494 100644
--- a/docs/docs/configuration/auth.md
+++ b/docs/docs/configuration/auth.md
@@ -75,7 +75,8 @@ Note: The user is checked against the group members list on initial authenticati
 1. Add an application: go to [https://portal.azure.com](https://portal.azure.com), choose **"Azure Active Directory"** in the left menu, select **"App registrations"** and then click on **"New app registration"**.
 2. Pick a name and choose **"Webapp / API"** as application type. Use `https://internal.yourcompany.com` as Sign-on URL. Click **"Create"**.
 3. On the **"Settings"** / **"Properties"** page of the app, pick a logo and select **"Multi-tenanted"** if you want to allow users from multiple organizations to access your app. Note down the application ID. Click **"Save"**.
-4. On the **"Settings"** / **"Required Permissions"** page of the app, click on **"Windows Azure Active Directory"** and then on **"Access the directory as the signed in user"**. Hit **"Save"** and then then on **"Grant permissions"** (you might need another admin to do this).
+4. On the **"Settings"** / **"API Permissions"** page of the app, click on **"Add a permission"**, then select **"Microsoft Graph"**, then **"Delegated permissions"** and finally check the **"openid (Sign users in)"** permission. Hit **"Save"** and then on **"Grant permissions"** (you might need another admin to do this).
+<br/>**IMPORTANT**: Even if this permission is listed with **"Admin consent required=No"** the consent might actually be required, due to AAD policies you won't be able to see. If you get a **"Need admin approval"** during login, most likely this is what you're missing!
 5. On the **"Settings"** / **"Reply URLs"** page of the app, add `https://internal.yourcompanycom/oauth2/callback` for each host that you want to protect by the oauth2 proxy. Click **"Save"**.
 6. On the **"Settings"** / **"Keys"** page of the app, add a new key and note down the value after hitting **"Save"**.
 7. Configure the proxy with
@@ -192,7 +193,7 @@ Make sure you set the following to the appropriate url:
     --client-id=<your client's id>
     --client-secret=<your client's secret>
     --redirect-url=https://myapp.com/oauth2/callback
-    --oidc-issuer-url=https://<keycloak host>/realms/<your realm>
+    --oidc-issuer-url=https://<keycloak host>/auth/realms/<your realm>
     --allowed-role=<realm role name> // Optional, required realm role
     --allowed-role=<client id>:<client role name> // Optional, required client role
 ```
diff --git a/docs/docs/configuration/overview.md b/docs/docs/configuration/overview.md
index 00c59ca5..a2772fa4 100644
--- a/docs/docs/configuration/overview.md
+++ b/docs/docs/configuration/overview.md
@@ -89,7 +89,7 @@ An example [oauth2-proxy.cfg](https://github.com/oauth2-proxy/oauth2-proxy/blob/
 | `--cookie-domain` | string \| list | Optional cookie domains to force cookies to (e.g. `.yourcompany.com`). The longest domain matching the request's host will be used (or the shortest cookie domain if there is no match). | |
 | `--cookie-expire` | duration | expire timeframe for cookie | 168h0m0s |
 | `--cookie-httponly` | bool | set HttpOnly cookie flag | true |
-| `--cookie-name` | string | the name of the cookie that the oauth_proxy creates | `"_oauth2_proxy"` |
+| `--cookie-name` | string | the name of the cookie that the oauth_proxy creates. Should be changed to use a [cookie prefix](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#cookie_prefixes) (`__Host-` or `__Secure-`) if `--cookie-secure` is set. | `"_oauth2_proxy"` |
 | `--cookie-path` | string | an optional cookie path to force cookies to (e.g. `/poc/`) | `"/"` |
 | `--cookie-refresh` | duration | refresh the cookie after this duration; `0` to disable; not supported by all providers&nbsp;\[[1](#footnote1)\] | |
 | `--cookie-secret` | string | the seed string for secure cookies (optionally base64 encoded) | |
@@ -167,6 +167,7 @@ An example [oauth2-proxy.cfg](https://github.com/oauth2-proxy/oauth2-proxy/blob/
 | `--redis-sentinel-connection-urls` | string \| list | List of Redis sentinel connection URLs (e.g. `redis://HOST[:PORT]`). Used in conjunction with `--redis-use-sentinel` | |
 | `--redis-use-cluster` | bool | Connect to redis cluster. Must set `--redis-cluster-connection-urls` to use this feature | false |
 | `--redis-use-sentinel` | bool | Connect to redis via sentinels. Must set `--redis-sentinel-master-name` and `--redis-sentinel-connection-urls` to use this feature | false |
+| `--redis-connection-idle-timeout` | int | Redis connection idle timeout seconds. If Redis [timeout](https://redis.io/docs/reference/clients/#client-timeouts) option is set to non-zero, the `--redis-connection-idle-timeout` must be less than Redis timeout option. Exmpale: if either redis.conf includes `timeout 15` or using `CONFIG SET timeout 15` the `--redis-connection-idle-timeout` must be at least `--redis-connection-idle-timeout=14` | 0 |
 | `--request-id-header` | string | Request header to use as the request ID in logging | X-Request-Id |
 | `--request-logging` | bool | Log requests | true |
 | `--request-logging-format` | string | Template for request log lines | see [Logging Configuration](#logging-configuration) |
@@ -594,4 +595,4 @@ http:
 
 :::note
 If you set up your OAuth2 provider to rotate your client secret, you can use the `client-secret-file` option to reload the secret when it is updated.
-:::
+:::
\ No newline at end of file
diff --git a/docs/docs/configuration/sessions.md b/docs/docs/configuration/sessions.md
index 174164ba..8a4b640c 100644
--- a/docs/docs/configuration/sessions.md
+++ b/docs/docs/configuration/sessions.md
@@ -65,3 +65,7 @@ Redis Cluster is available to be the backend store as well. To leverage it, you
 `--redis-use-cluster=true` flag, and configure the flags `--redis-cluster-connection-urls` appropriately.
 
 Note that flags `--redis-use-sentinel=true` and `--redis-use-cluster=true` are mutually exclusive.
+
+Note, if Redis timeout option is set to non-zero, the `--redis-connection-idle-timeout` 
+must be less than [Redis timeout option](https://redis.io/docs/reference/clients/#client-timeouts). For example: if either redis.conf includes 
+`timeout 15` or using `CONFIG SET timeout 15` the `--redis-connection-idle-timeout` must be at least `--redis-connection-idle-timeout=14`
\ No newline at end of file
diff --git a/docs/versioned_docs/version-6.1.x/configuration/overview.md b/docs/versioned_docs/version-6.1.x/configuration/overview.md
index 5c0cbf95..6810be75 100644
--- a/docs/versioned_docs/version-6.1.x/configuration/overview.md
+++ b/docs/versioned_docs/version-6.1.x/configuration/overview.md
@@ -88,7 +88,7 @@ An example [oauth2-proxy.cfg](https://github.com/oauth2-proxy/oauth2-proxy/blob/
 | `--cookie-domain` | string \| list | Optional cookie domains to force cookies to (e.g. `.yourcompany.com`). The longest domain matching the request's host will be used (or the shortest cookie domain if there is no match). | |
 | `--cookie-expire` | duration | expire timeframe for cookie | 168h0m0s |
 | `--cookie-httponly` | bool | set HttpOnly cookie flag | true |
-| `--cookie-name` | string | the name of the cookie that the oauth_proxy creates | `"_oauth2_proxy"` |
+| `--cookie-name` | string | the name of the cookie that the oauth_proxy creates. Should be changed to use a [cookie prefix](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#cookie_prefixes) (`__Host-` or `__Secure-`) if `--cookie-secure` is set. | `"_oauth2_proxy"` |
 | `--cookie-path` | string | an optional cookie path to force cookies to (e.g. `/poc/`) | `"/"` |
 | `--cookie-refresh` | duration | refresh the cookie after this duration; `0` to disable; not supported by all providers&nbsp;\[[1](#footnote1)\] | |
 | `--cookie-secret` | string | the seed string for secure cookies (optionally base64 encoded) | |
diff --git a/docs/versioned_docs/version-7.0.x/configuration/overview.md b/docs/versioned_docs/version-7.0.x/configuration/overview.md
index b77a5517..ed433edd 100644
--- a/docs/versioned_docs/version-7.0.x/configuration/overview.md
+++ b/docs/versioned_docs/version-7.0.x/configuration/overview.md
@@ -88,7 +88,7 @@ An example [oauth2-proxy.cfg](https://github.com/oauth2-proxy/oauth2-proxy/blob/
 | `--cookie-domain` | string \| list | Optional cookie domains to force cookies to (e.g. `.yourcompany.com`). The longest domain matching the request's host will be used (or the shortest cookie domain if there is no match). | |
 | `--cookie-expire` | duration | expire timeframe for cookie | 168h0m0s |
 | `--cookie-httponly` | bool | set HttpOnly cookie flag | true |
-| `--cookie-name` | string | the name of the cookie that the oauth_proxy creates | `"_oauth2_proxy"` |
+| `--cookie-name` | string | the name of the cookie that the oauth_proxy creates. Should be changed to use a [cookie prefix](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#cookie_prefixes) (`__Host-` or `__Secure-`) if `--cookie-secure` is set. | `"_oauth2_proxy"` |
 | `--cookie-path` | string | an optional cookie path to force cookies to (e.g. `/poc/`) | `"/"` |
 | `--cookie-refresh` | duration | refresh the cookie after this duration; `0` to disable; not supported by all providers&nbsp;\[[1](#footnote1)\] | |
 | `--cookie-secret` | string | the seed string for secure cookies (optionally base64 encoded) | |
diff --git a/docs/versioned_docs/version-7.1.x/configuration/overview.md b/docs/versioned_docs/version-7.1.x/configuration/overview.md
index 9a2113e7..3121e6b2 100644
--- a/docs/versioned_docs/version-7.1.x/configuration/overview.md
+++ b/docs/versioned_docs/version-7.1.x/configuration/overview.md
@@ -33,7 +33,7 @@ An example [oauth2-proxy.cfg](https://github.com/oauth2-proxy/oauth2-proxy/blob/
 | `--cookie-domain` | string \| list | Optional cookie domains to force cookies to (e.g. `.yourcompany.com`). The longest domain matching the request's host will be used (or the shortest cookie domain if there is no match). | |
 | `--cookie-expire` | duration | expire timeframe for cookie | 168h0m0s |
 | `--cookie-httponly` | bool | set HttpOnly cookie flag | true |
-| `--cookie-name` | string | the name of the cookie that the oauth_proxy creates | `"_oauth2_proxy"` |
+| `--cookie-name` | string | the name of the cookie that the oauth_proxy creates. Should be changed to use a [cookie prefix](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#cookie_prefixes) (`__Host-` or `__Secure-`) if `--cookie-secure` is set. | `"_oauth2_proxy"` |
 | `--cookie-path` | string | an optional cookie path to force cookies to (e.g. `/poc/`) | `"/"` |
 | `--cookie-refresh` | duration | refresh the cookie after this duration; `0` to disable; not supported by all providers&nbsp;\[[1](#footnote1)\] | |
 | `--cookie-secret` | string | the seed string for secure cookies (optionally base64 encoded) | |
diff --git a/docs/versioned_docs/version-7.2.x/configuration/overview.md b/docs/versioned_docs/version-7.2.x/configuration/overview.md
index 092fb7dc..9ca8f036 100644
--- a/docs/versioned_docs/version-7.2.x/configuration/overview.md
+++ b/docs/versioned_docs/version-7.2.x/configuration/overview.md
@@ -88,7 +88,7 @@ An example [oauth2-proxy.cfg](https://github.com/oauth2-proxy/oauth2-proxy/blob/
 | `--cookie-domain` | string \| list | Optional cookie domains to force cookies to (e.g. `.yourcompany.com`). The longest domain matching the request's host will be used (or the shortest cookie domain if there is no match). | |
 | `--cookie-expire` | duration | expire timeframe for cookie | 168h0m0s |
 | `--cookie-httponly` | bool | set HttpOnly cookie flag | true |
-| `--cookie-name` | string | the name of the cookie that the oauth_proxy creates | `"_oauth2_proxy"` |
+| `--cookie-name` | string | the name of the cookie that the oauth_proxy creates. Should be changed to use a [cookie prefix](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#cookie_prefixes) (`__Host-` or `__Secure-`) if `--cookie-secure` is set. | `"_oauth2_proxy"` |
 | `--cookie-path` | string | an optional cookie path to force cookies to (e.g. `/poc/`) | `"/"` |
 | `--cookie-refresh` | duration | refresh the cookie after this duration; `0` to disable; not supported by all providers&nbsp;\[[1](#footnote1)\] | |
 | `--cookie-secret` | string | the seed string for secure cookies (optionally base64 encoded) | |
diff --git a/docs/versioned_docs/version-7.3.x/configuration/overview.md b/docs/versioned_docs/version-7.3.x/configuration/overview.md
index 00c59ca5..d58a2d11 100644
--- a/docs/versioned_docs/version-7.3.x/configuration/overview.md
+++ b/docs/versioned_docs/version-7.3.x/configuration/overview.md
@@ -89,7 +89,7 @@ An example [oauth2-proxy.cfg](https://github.com/oauth2-proxy/oauth2-proxy/blob/
 | `--cookie-domain` | string \| list | Optional cookie domains to force cookies to (e.g. `.yourcompany.com`). The longest domain matching the request's host will be used (or the shortest cookie domain if there is no match). | |
 | `--cookie-expire` | duration | expire timeframe for cookie | 168h0m0s |
 | `--cookie-httponly` | bool | set HttpOnly cookie flag | true |
-| `--cookie-name` | string | the name of the cookie that the oauth_proxy creates | `"_oauth2_proxy"` |
+| `--cookie-name` | string | the name of the cookie that the oauth_proxy creates. Should be changed to use a [cookie prefix](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#cookie_prefixes) (`__Host-` or `__Secure-`) if `--cookie-secure` is set. | `"_oauth2_proxy"` |
 | `--cookie-path` | string | an optional cookie path to force cookies to (e.g. `/poc/`) | `"/"` |
 | `--cookie-refresh` | duration | refresh the cookie after this duration; `0` to disable; not supported by all providers&nbsp;\[[1](#footnote1)\] | |
 | `--cookie-secret` | string | the seed string for secure cookies (optionally base64 encoded) | |
diff --git a/go.mod b/go.mod
index c34e7ec1..356d689c 100644
--- a/go.mod
+++ b/go.mod
@@ -29,8 +29,8 @@ require (
 	github.com/spf13/viper v1.6.3
 	github.com/stretchr/testify v1.7.0
 	github.com/vmihailenco/msgpack/v4 v4.3.11
-	golang.org/x/crypto v0.0.0-20220214200702-86341886e292
-	golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2
+	golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b
+	golang.org/x/net v0.0.0-20211209124913-491a49abca63
 	golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
 	golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
 	google.golang.org/api v0.20.0
@@ -68,7 +68,7 @@ require (
 	github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb // indirect
 	go.opencensus.io v0.22.2 // indirect
 	go.opentelemetry.io/otel v0.11.0 // indirect
-	golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect
+	golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
 	golang.org/x/text v0.3.7 // indirect
 	golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect
 	google.golang.org/appengine v1.6.5 // indirect
diff --git a/go.sum b/go.sum
index bdfaeef4..de9607ab 100644
--- a/go.sum
+++ b/go.sum
@@ -312,8 +312,9 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE=
 golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b h1:Qwe1rC8PSniVfAFPFJeyUkB+zcysC3RgJBAGk7eqBEU=
+golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20200908183739-ae8ad444f925/go.mod h1:1phAWC201xIgDyaFpmDeZkgf70Q4Pd/CNqfRtVPtxNw=
@@ -350,8 +351,9 @@ golang.org/x/net v0.0.0-20200505041828-1ed23360d12c/go.mod h1:qpuaurCH72eLCgpAm/
 golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
-golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211209124913-491a49abca63 h1:iocB37TsdFuN6IBRZ+ry36wrkoV51/tl5vOWqkcPGvY=
+golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -391,8 +393,9 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
+golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
diff --git a/pkg/apis/options/options.go b/pkg/apis/options/options.go
index 8641a430..eac42ead 100644
--- a/pkg/apis/options/options.go
+++ b/pkg/apis/options/options.go
@@ -143,7 +143,7 @@ func NewFlagSet() *pflag.FlagSet {
 	flagSet.StringSlice("redis-sentinel-connection-urls", []string{}, "List of Redis sentinel connection URLs (eg redis://HOST[:PORT]). Used in conjunction with --redis-use-sentinel")
 	flagSet.Bool("redis-use-cluster", false, "Connect to redis cluster. Must set --redis-cluster-connection-urls to use this feature")
 	flagSet.StringSlice("redis-cluster-connection-urls", []string{}, "List of Redis cluster connection URLs (eg redis://HOST[:PORT]). Used in conjunction with --redis-use-cluster")
-
+	flagSet.Int("redis-connection-idle-timeout", 0, "Redis connection idle timeout seconds, if Redis timeout option is non-zero, the --redis-connection-idle-timeout must be less then Redis timeout option")
 	flagSet.String("signature-key", "", "GAP-Signature request signature key (algorithm:secretkey)")
 	flagSet.Bool("gcp-healthchecks", false, "Enable GCP/GKE healthcheck endpoints")
 
diff --git a/pkg/apis/options/sessions.go b/pkg/apis/options/sessions.go
index 9b36d79e..188f0e45 100644
--- a/pkg/apis/options/sessions.go
+++ b/pkg/apis/options/sessions.go
@@ -32,6 +32,7 @@ type RedisStoreOptions struct {
 	ClusterConnectionURLs  []string `flag:"redis-cluster-connection-urls" cfg:"redis_cluster_connection_urls"`
 	CAPath                 string   `flag:"redis-ca-path" cfg:"redis_ca_path"`
 	InsecureSkipTLSVerify  bool     `flag:"redis-insecure-skip-tls-verify" cfg:"redis_insecure_skip_tls_verify"`
+	IdleTimeout            int      `flag:"redis-connection-idle-timeout" cfg:"redis_connection_idle_timeout"`
 }
 
 func sessionOptionsDefaults() SessionOptions {
diff --git a/pkg/sessions/redis/redis_store.go b/pkg/sessions/redis/redis_store.go
index f1c798fa..1243c7c5 100644
--- a/pkg/sessions/redis/redis_store.go
+++ b/pkg/sessions/redis/redis_store.go
@@ -104,6 +104,7 @@ func buildSentinelClient(opts options.RedisStoreOptions) (Client, error) {
 		SentinelPassword: opts.SentinelPassword,
 		Password:         opts.Password,
 		TLSConfig:        opt.TLSConfig,
+		IdleTimeout:      time.Duration(opts.IdleTimeout) * time.Second,
 	})
 	return newClient(client), nil
 }
@@ -120,9 +121,10 @@ func buildClusterClient(opts options.RedisStoreOptions) (Client, error) {
 	}
 
 	client := redis.NewClusterClient(&redis.ClusterOptions{
-		Addrs:     addrs,
-		Password:  opts.Password,
-		TLSConfig: opt.TLSConfig,
+		Addrs:       addrs,
+		Password:    opts.Password,
+		TLSConfig:   opt.TLSConfig,
+		IdleTimeout: time.Duration(opts.IdleTimeout) * time.Second,
 	})
 	return newClusterClient(client), nil
 }
@@ -143,6 +145,8 @@ func buildStandaloneClient(opts options.RedisStoreOptions) (Client, error) {
 		return nil, err
 	}
 
+	opt.IdleTimeout = time.Duration(opts.IdleTimeout) * time.Second
+
 	client := redis.NewClient(opt)
 	return newClient(client), nil
 }
diff --git a/providers/keycloak_oidc.go b/providers/keycloak_oidc.go
index e16b4383..28abae3a 100644
--- a/providers/keycloak_oidc.go
+++ b/providers/keycloak_oidc.go
@@ -43,6 +43,21 @@ func (p *KeycloakOIDCProvider) addAllowedRoles(roles []string) {
 	}
 }
 
+// CreateSessionFromToken converts Bearer IDTokens into sessions
+func (p *KeycloakOIDCProvider) CreateSessionFromToken(ctx context.Context, token string) (*sessions.SessionState, error) {
+	ss, err := p.OIDCProvider.CreateSessionFromToken(ctx, token)
+	if err != nil {
+		return nil, fmt.Errorf("could not create session from token: %v", err)
+	}
+
+	// Extract custom keycloak roles and enrich session
+	if err := p.extractRoles(ctx, ss); err != nil {
+		return nil, err
+	}
+
+	return ss, nil
+}
+
 // EnrichSession is called after Redeem to allow providers to enrich session fields
 // such as User, Email, Groups with provider specific API calls.
 func (p *KeycloakOIDCProvider) EnrichSession(ctx context.Context, s *sessions.SessionState) error {


Test patch:
diff --git a/pkg/util/util_test.go b/pkg/util/util_test.go
index 347f41bb..d5f7860b 100644
--- a/pkg/util/util_test.go
+++ b/pkg/util/util_test.go
@@ -1,8 +1,8 @@
 package util
 
 import (
-	"crypto/x509/pkix"
-	"encoding/asn1"
+	"crypto/x509"
+	"encoding/pem"
 	"io/ioutil"
 	"os"
 	"testing"
@@ -11,45 +11,173 @@ import (
 )
 
 // Test certificate created with an OpenSSL command in the following form:
-// openssl req -x509 -newkey rsa:4096 -keyout key-unused.pem -out cert.pem -nodes -subj "/CN=oauth-proxy test ca"
+// # Create "root1" certification and "cert1" certification signed by "root1" certification.
+// openssl req -x509 -nodes -days 365000 -newkey rsa:4096 -keyout root1.key -out root1.pem -subj "/CN=oauth2-proxy test root1 ca"
+// openssl req -nodes -days 365000 -newkey rsa:4096 -keyout cert1.key -out cert1-req.pem -subj "/CN=oauth2-proxy test cert1 ca"
+// openssl x509 -req -in cert1-req.pem -days 365000 -CA root1.pem -CAkey root1.key -set_serial 01 -out cert1.pem
+// openssl verify -CAfile ./root1.pem cert1.pem
+// # Create "root2" certification and "cert2" certification signed by "root2" certification.
+// openssl req -x509 -nodes -days 365000 -newkey rsa:4096 -keyout root2.key -out root2.pem -subj "/CN=oauth2-proxy test root2 ca"
+// openssl req -nodes -days 365000 -newkey rsa:4096 -keyout cert2.key -out cert2-req.pem -subj "/CN=oauth2-proxy test cert2 ca"
+// openssl x509 -req -in cert2-req.pem -days 365000 -CA root2.pem -CAkey root2.key -set_serial 01 -out cert2.pem
+// openssl verify -CAfile ./root2.pem cert2.pem
+// # Create "root3" certification and "cert3" certification signed by "root3" certification.
+// openssl req -x509 -nodes -days 365000 -newkey rsa:4096 -keyout root3.key -out root3.pem -subj "/CN=oauth2-proxy test root3 ca"
+// openssl req -nodes -days 365000 -newkey rsa:4096 -keyout cert3.key -out cert3-req.pem -subj "/CN=oauth2-proxy test cert3 ca"
+// openssl x509 -req -in cert3-req.pem -days 365000 -CA root3.pem -CAkey root3.key -set_serial 01 -out cert3.pem
+// openssl verify -CAfile ./root3.pem cert3.pem
 
 var (
-	testCA1Subj = "CN=oauth-proxy test ca"
-	testCA1     = `-----BEGIN CERTIFICATE-----
-MIICuTCCAaGgAwIBAgIFAKuKEWowDQYJKoZIhvcNAQELBQAwHjEcMBoGA1UEAxMT
-b2F1dGgtcHJveHkgdGVzdCBjYTAeFw0xNzEwMjQyMDExMzJaFw0xOTEwMjQyMDEx
-MzJaMB4xHDAaBgNVBAMTE29hdXRoLXByb3h5IHRlc3QgY2EwggEiMA0GCSqGSIb3
-DQEBAQUAA4IBDwAwggEKAoIBAQC5/kmgKNiECuxlj27yTWBWOMVvIB0AaRhQrMA7
-3iSCk/SHhaTabUuXUGRwmCAewT/y9oX3rTdfnSPCn7praU/27lRFBgOGFrTzAZH6
-voisF54I3ZxWZgHDJ/ig/KFwd0Y8OATj9/k9uAJSCe6aT7BouJPZVWNGF2dF5BOJ
-EwFsJiN2s8HpF14DhxFOMMtlckdMHGxi3wj3E/hBCfGvGGU4Wezz48vEWWC1ajWM
-qVq2vVWi1bcNft8FjWa5wTGpdlDQJM7yvKYJPwRkEjgIXtF1ra3JM3WTTFZO9Yhd
-QXwO7IWRTdTaypKTNbTDKuWQZsm7xQM9sNcFkukGb3o+uBpLAgMBAAEwDQYJKoZI
-hvcNAQELBQADggEBAHJNrUfHhN7VOUF60pG8sOEkx0ztjbtbYMj2N9Kb0oSya+re
-Kmb2Z4JgyV7XHCZ03Jch6L7UBI3Y6/Lp1zdwU03LFayVUchLkvFonoXpRRP5UFYN
-+36xP3ZL1qBYFphARsCk6/tl36czH4oF5gTlhWCRy3upNzn+INk467hnCKt5xuse
-zhm+xQv/VN1poI0S/oCg9HLA9iKpoqGJByN32yoFr3QViLPqkmJ1v8EiH0Ns+1m3
-pP5YlVqdRCVrxgT80PIMsvQhfcuIrbbeiRDEUdEX7FqebuGCEa2757MTdW7UYQiB
-7kgECMnwAOlJME8aDKnmTBajaMy6xCSC87V7wps=
+	root1Cert = `-----BEGIN CERTIFICATE-----
+MIIFLTCCAxWgAwIBAgIUfVKJjmDCrxB6a4GfPxF7QjP67vwwDQYJKoZIhvcNAQEL
+BQAwJTEjMCEGA1UEAwwab2F1dGgyLXByb3h5IHRlc3Qgcm9vdDEgY2EwIBcNMjIw
+NzA3MjMyNzUwWhgPMzAyMTExMDcyMzI3NTBaMCUxIzAhBgNVBAMMGm9hdXRoMi1w
+cm94eSB0ZXN0IHJvb3QxIGNhMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC
+AgEA8a5ieYCOmxkKT5fnZbLe5zWun+zNVnIGJOrrayg4esJ9TXfnfzQJE6sC+7ty
+C5CGmueZqNAiYBriXzLoQwqtb+wVORH6TBzn2rNQonxMXB4rGaHX9Z1+bx3a3WKi
+nDY1oERDeAeDOS42QhJIoSnYMYVHWeKdYw+xveLPjtqiN7pj7bPB7vpyFRO0c3r1
+rN5Y3bB2fzb2bYUmt8A3y+4/pREXMiGN610SVFHHw8X6WtbeuM+w4JKcp2jzFaGU
+AocvbFcNNth6jiM9a4jERjZ+VV/rvvGAx1Ucp3a9kcHc9s2r0bQV82U3KqO2RNP4
+M5y9g+TKMttaDMp2PC20jpWwfJ/wfmFWk6QK0oJU6FlcaPOop6YzIaIdOP6qXVEv
+Irg5yheefafsH79cV09Srw4EEklretIm/p+qLWsi/eacF3yG67nznp5uOqqyeDg0
+Za1bOvOT14m4yJvM6uyykYLkNqXpbZ8HEux9jwPoWfAWRVJfFt8aPCUb4vT3S6ps
+zA1izlA+j3hbBHfrOT+zFdrFy7Urr9d6lAhur0mnog2e/QN/7cFLSNDZwLntatcj
+BTWJ3CZdKBKhfthlCJ4at6BS+Yk7HZudMaJCuRjk/0aqoeb7LPjtZwS8YnYgACwn
+Mq4J38BSyuTxkD3zffxnnaL41ID4mmMK15VhaETUZfYlVocCAwEAAaNTMFEwHQYD
+VR0OBBYEFCmVnz9yP/WllHiEZeE8Y63FQK3iMB8GA1UdIwQYMBaAFCmVnz9yP/Wl
+lHiEZeE8Y63FQK3iMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIB
+ABdSLp6If5CBv26LdMWcMXNq0t8FRRx8tSGZr6aWQWxYzb4L2yWQaZIYsEnlqTVI
+f6yQWevURx5m7yxnZovM6ovF1D/ivAdgYJDOIuT9eHIJ5DFS4ayDmUbmtzzWsH1u
+2qaMQnWSZjwP1ISgNcCVlICclW5rwIKGLeU9NRxNrNLoDCsPFneOR1GWYG5i7EES
+TvhynThBzhGYICPrIXKLpZEmYJb0sxOqw7URorwHLcAd4oACJ5UbN87QvHN9tc99
++H2zJwibaxbg0bxOhFCmfbPuSQ2sgn/Ff80utpv+yZ/WDmIaSuA7TUXT+qIovHSi
+z0ZbR2ytzflzXZrj6JEN1R4QJ2UTvzcYBNbT+UnNpMrqGk9HO5v0D/k0QoN4XtCv
+YX351kePLY+69mTzwjWVjdH1aZh5ROh4i3Zez1+aZZuZlOoIOWbm1EzlmAYwlQ1L
+OArpFJ7vc1Xx6/q9uoW9bkZk4tYNRdgighCDBDv8JnIHl+ZUPAJm1dnnv7reyfPy
+wgySqqf9K+LokNpEsVS3sQYa5HRgF4rGYPBiOxnHBRb+7PnGNK9p8wI/FAfbuXJN
+oV3sleVSP/U9sr/1q8ThUy7w9MW8NOT4EKbQh5Ph5L2aafsO+LNn9Ly9vZgxy5Dz
+HJcjgKpeXzKhwMtFToVEkkIx+VL0kcRxou4XAF2wltck
 -----END CERTIFICATE-----
 `
-	testCA2Subj = "CN=oauth-proxy second test ca"
-	testCA2     = `-----BEGIN CERTIFICATE-----
-MIICxzCCAa+gAwIBAgIFAKuMKewwDQYJKoZIhvcNAQELBQAwJTEjMCEGA1UEAxMa
-b2F1dGgtcHJveHkgc2Vjb25kIHRlc3QgY2EwHhcNMTcxMDI1MTYxMTQxWhcNMTkx
-MDI1MTYxMTQxWjAlMSMwIQYDVQQDExpvYXV0aC1wcm94eSBzZWNvbmQgdGVzdCBj
-YTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKdTkEOJ+QpOHy0PqGDR
-fu8NFyo7BJwAnI+P1G32UXMeecCwBgGJEyv6eHEFV6jH/U2K2H0hynaCFxRuIdTA
-EeS4s4BAbKqFhQ62I9lF3HVuqRPOe5FYdUl80eQynME22fWQ6/sZdQds0sFqaJBz
-R4KQQxVULT19Br/6zwQZZhC1NtzSwCqi4CoO2OM7ctUKRvtC87LNGWapz5I4eh0A
-/q4XJaSObsBCAJD7OVMa1LM3sSINUnvvGoSBKTuJ8MRk/BQRAO/PwXxsa+2h+k+w
-D6sLExrBgWzAAPQKRKF+nLYVhz9AKn4JBpZt9j4PvTKz1SDcJ5wVEzOfVmii7Ui3
-EFcCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAiy58XvhOka3drXv2bwl95FwNtodj
-L2MmIdF0pp01O0ryREcC1kogamdOS/UHQs4okuCjwinR/UgU+cFGCDYHfeENtUTw
-Ox2OikYD7bXUpNzbQ4QyF0+cKwAgxD4ai5xSV/NUvMkL1aE8tLyxGm6VkhhyvxU1
-U9kvLha6KBWOCNd2fBJxgg8RAxFV3vR+xLdEtXnBAeTURrHM19gwMtd16y6gUZTZ
-Xbl3Ix0t2+sqi0hpEF/iVFdCp5TXiicSnZCtePzCfHePAEfbh5hS0bq8Lbb9DZ6d
-+2jX3AVuYhQPuutxla+vNp2XRcMTbzwXyi/Ig4nHKmPLFXsEbv+4tSwxyQ==
+	root2Cert = `-----BEGIN CERTIFICATE-----
+MIIFLTCCAxWgAwIBAgIUf67dNKjgAWyO1n66bTtAS5eWnyIwDQYJKoZIhvcNAQEL
+BQAwJTEjMCEGA1UEAwwab2F1dGgyLXByb3h5IHRlc3Qgcm9vdDIgY2EwIBcNMjIw
+NzA3MjMyNzUxWhgPMzAyMTExMDcyMzI3NTFaMCUxIzAhBgNVBAMMGm9hdXRoMi1w
+cm94eSB0ZXN0IHJvb3QyIGNhMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC
+AgEA2NFh8OB0/sAt/bys5L2gqmOv9j45VleZNUifPR6CAyaEnGUjRAEYR+ncwc86
+9EgR+w/RguUncLKk9U1Fq/Og4/oXI2F595Ryf2Vqxl9wW55AYWIBgep39u0vmWFS
+MacQISkRs5zQNNu5D+wQDSfC5u8zkXXCAd1yUt8W5JLieg9PtT3yHWSVRLfZgs7+
+285B9xDE04z/XmaF1gP7oVtATswO+ssV68XiiKK5n7BfB6zQbDIQGrSLevGftDZ9
+R1BS6wtE2wzzuZoBQ6bZSWrudSf9DIzKZsuphsY42tE8q9Rpur1tFVL7mwC6rYBQ
+s4mw+qKj1Wto6O0NQTZ+E4r0dcwCZIa8MP8tYyJylozai0+7QQsdzuM7V1PuvmCQ
+8XiBdiLAN+vjMN+7cqvyzA0TtWPy7sE/5kc+L+FnrSGllXc3/YytY7t5bfr3CPwO
+tQuTYVPqr3lpAnbTz4jhplT9NXKdFGFwagk8V4si/GwnW2fCpMw1Kcuy+qVOI36x
+O1KJ8mBe6vxzfYIYHV22EniWvmA6GOEcQM5xUsYbSsN+IkC3OVIgn2MbBaVQ2KgP
+Xzrfja7b0T9mqAvWexxUKrlWrQ+igVQNfxrSizFRwj/WlKh/h4sSbmiMTgN6Dpj5
+8bOogWkH9AE0UfHYxN6GHHo9ZMRPrreNL3YTCzreSttHHcMCAwEAAaNTMFEwHQYD
+VR0OBBYEFJGlZ2LTzaDmI36BTXYW5IpZy6jZMB8GA1UdIwQYMBaAFJGlZ2LTzaDm
+I36BTXYW5IpZy6jZMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIB
+AKejioTBUlfJeqX5hUnN882xOR/gwitr1/MNVi8a0Y2fXvFWYrZjzYCcYqXPi0Do
+VELZaojqHCi9BvO6nQfxK6I/Rf7FZB/kEQMbYF4F5FEXD6DoSsV8Q5sFqwY/ry2O
+1Q7xCn1WF1lVUXzkd0aiT1p4N6pj+iTIVgiEzR00lmYYMP/UjThDyQ7KqK2TwVeX
+9EgSXNFGXz1D1uJkskhQ+3fiVWmwIfI3WZo/so3VXiNFB04s+le2/l49DQ6cmSd/
++OGsTur09m7GtsFSD00JV3N5znTRXpxgX6Rgt1WpDPj6GqGPuJK2KZ5fFwuiqXs9
+GUXXcJeadw2i0BgEJmiIputRLmsdNyBKHLxAxj7zcQVGJ5ZH7Vmmdo0oVWva/t4f
+dJ7dl6YVm/xt0+kuF3CsdtsroK+Yh/tp5wslOyzX5B4tOJo0HZpQhfJzYqeleZG4
+mmu20Q5sWqggb0WNk1GoHIlpiObgY38KfnNSZ5Ra/QuXLF3QM2NdK5bT1iNFfPG3
+fQklEouz/2lqZkRhsKSeXFKQ+h8GtNevKwFg6y3wWvH62WF5mTe9eyUE9VWl64y6
+js5ESoVXA+e+QfsMsJrI5XfLV1O8ZxXKAVrYxBnC+WQbrNOjI7VBkjcn/QDmDjBw
+sC1lo4YZwxEQ/bE0kEWI7PT/Skml4bTLw0jsgXNV9Nd8
+-----END CERTIFICATE-----		
+`
+	cert1CertSubj = "CN=oauth2-proxy test cert1 ca"
+	cert1Cert     = `-----BEGIN CERTIFICATE-----
+MIIEwDCCAqgCAQEwDQYJKoZIhvcNAQELBQAwJTEjMCEGA1UEAwwab2F1dGgyLXBy
+b3h5IHRlc3Qgcm9vdDEgY2EwIBcNMjIwNzA3MjMyNzUxWhgPMzAyMTExMDcyMzI3
+NTFaMCUxIzAhBgNVBAMMGm9hdXRoMi1wcm94eSB0ZXN0IGNlcnQxIGNhMIICIjAN
+BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyaGEqDJZjaQybLPbBTfgdl8RVksT
+FPdThWaL6vVU9Tsmjk6KC+TcQmV8OXTi70QfxciqFvtoCR5IwayAwztHresRJb5e
+5IrOC250wSai5A4t3dF/VWSkJ8qE59L8Km5rR7AcooT32iFXHr5dmXN4qkbsI9iI
+PfGo4e/zxC+bcBwQVScTDOrOamqUiLNVHE+kQZykwpshlShiqmpVq87FH+2ceZ0Q
+0gST3+AUi+E0pd97AU1a/SO9MSKr0XwZxhrGag/eowwB0S2LHobLJouvFsbqUYnS
+4E6Jby1AUIWBeJ2v4xMw7Y2Hhu42t0BRw+SiZBS59HRpjM2zDptGjxWJFjNsQ98/
+e583Gn7foCETaDSpoOzFvnP3qNp6dUkJIt4DJRzRPMBXVXzQ8kwc8EHISQFLb4t9
+reS3NQgEPFbSoxZRS2e5zpopuFOekUejT+57zib4XalOdVNZi7CFbF4OZuMGXYsj
+IgaIatcon8ujk/f0LqUfCSyNFXLpO10ZKpV5z0QGXyMYfLjImUyHHHs6VPPc3AL0
+cCspspx8ihnOHE9pD0ljfbQI/mdwk0wZ5Cg+AOWk2x1nlzXUV4aHtv9VLOaUb66u
+O5o6nSUu/nEBwsLo5xiXur63V0GEolj+Ia7DROEyofX/G8whlAxHT4IqAit2CVSA
+CmVWz+WrnHrSvx8CAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAjHLcBlx/6GarXwTw
++fgiDqs4CfOLxUKIfrO8+gHxFEBSYjr194lccK2AlzneCDs5E4qyspaxT1mBm2D0
+K4O8lhEoHxgcWsWEejNwfj+tcicV1G9P31mYK1XyexBa6oTQ12WkmE7QhEHQ0aW8
+ldU6ntf7soIbh3JXeoyNuDrth/M4QO4TcgZG4svaRUraoATov2J6D2SyJBqrzEK9
+s+JwGdY7PEHCx7NPcqZ66Kf1b593aJlQiBUKDAkrPXAYVoEJesLq5AD2j+GxOydt
+gBncgeV/3A8IKwkiWcVYkMhe+tW59Q19j2t9BFyDkRECpOeNTR9OcQ1mwpv/BfY3
+zLtkzYhls6NvY1OVD8yxKiqYBdv/PrtpcmLbnWO8q1a8UzxlzFLCAzjeqqs9LQQZ
+RafejyWax79chjby2DrvgILxgyNiR8NZu68ARziKdCsAQZR/R99rYtRYS/PvYEUy
+WA+NNQQzq8KOuyTUxx9OYtBq93WFpnOE8/jrOu+KCmrGLXBS5CYOviu4q6/wEp0O
+swFvtQb0ZESaZ65NxC0uSQLTc1E8j2PmQk7J+bcVHFD2nCEHgRtAx0NcdnUPPi4s
+Qht3aWEb8fxeWqDCFzn2G1yFCe3bv3T9OoBvFLVGu8al1puAM2fypkTlKb5s7HEl
+ZZUrUbOKkforJJnoiPXIlG/Lka4=
+-----END CERTIFICATE-----
+`
+	cert2CertSubj = "CN=oauth2-proxy test cert2 ca"
+	cert2Cert     = `-----BEGIN CERTIFICATE-----
+MIIEwDCCAqgCAQEwDQYJKoZIhvcNAQELBQAwJTEjMCEGA1UEAwwab2F1dGgyLXBy
+b3h5IHRlc3Qgcm9vdDIgY2EwIBcNMjIwNzA3MjMyNzUyWhgPMzAyMTExMDcyMzI3
+NTJaMCUxIzAhBgNVBAMMGm9hdXRoMi1wcm94eSB0ZXN0IGNlcnQyIGNhMIICIjAN
+BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAoyM+YiNabREpTjoZ+pl2QimTTwnQ
+AxzotBgRg0/y2pIw6mLYYLpfAlSQO8340D1J51GT5IjVOkbJPEQ/92jf7lX5plQd
+8ogc7Y3N4drpKQdPr364mxy0o8cr0d4H+rXBYpWy/JiUEQ+ZsrGjeX1URALkdOcO
+SIUAL1UhMysNUXGP8OttP9+TKlZEInFS6bZea+5Hm7SG/3upFfMd9SVPIbw8ixzD
+0w9uCrmQ89hy/qSarAn5PFSY2aVpCxGs4Nbs9tG150Y54GgnIjFEmb65EKsHOGf8
+/XqWg1Y426a7K56OjwxJGKEc2C0vWxNhvOapNcL7O9/2omkqhh/VRDkNiPYq7byi
+/AjAm9tqu08fIHJb8OTa08SPxIxOEd8spdPCf3C/NmtAiuUjGbFlgP5SVAuLdeiT
+noaJEcGeungkmeZmdgKlvEFbObfVe1kIqyxSx9wVofnmAMyl7rga9BOIwAECYPOU
+iAyq7pFswDUISiulDnKBusJShh9IhQO+kbmw3RL+wuZk25vd0WqeCVgB1zPvyAPs
+L1JcIKRyVf3OW4zv00AO5sqdUoUg06si0jzGGFSOoyOESh49Pi6ie2lWBLJRvRVQ
+fMqEdSN8xSWvEMgFfIDc/VO2zlT17Tj/S2KRMdgaArcKYQoIRSC91vJc4tvFgNkQ
+lan6RXy9GG9+UQkCAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAbgARj/AUE8GKubN5
+ycg1yWr95dRS/YeOWEZHnFL4JO85IYXgH/KndgtadM2UQUUqeGfTZ2KIlNrAgraS
+tjPruxVQq6NwzP6pj3konaoCeGs2kiepJBHgZ5c/YUT7CVjInUMOz3bxqMxVDRda
+ZuewaJp6IMZGeBL0Oeb2pXVFGjKehkGBlNr/nuH2tvFVVRPMu/ooqrHhyj0+WHbD
+NYVTNAnwHqFy4mNKOH/MwW7KGUORvAQtR1n8yzityGW1q//KfRuwqsn+ZCwnEe82
+UiYGZKAL1pKZDWtp6QdbEt2sF4U7HFAvhvWekRd9gVanMtILfI9Sosbo9HLtMvyq
+Pq0fIPFdxOj6G5vRkJqGnHzF88l1oyBQXdw16CNETxj+LyhrzlKTwgy5GQz1Eohn
+4N9rfnCp5salP9naH5eRD/dBFA0m/yhYWF5MeRlNSbuxk3jxuZ7/HsbxG5zczEuZ
+VfUybBMnJC6A1ZML6fzMv18vj8t6Ub8cEdDIP0hCYlLTw9DY5eOJrR3jxdRZqOQ6
+pk85aEOG5fyGV+m7VWXl9l2tt9xALxTlMe0WJAIkRvfUsbm0tursc5uu6yM8WEsf
+5TCQtSgGoQv3ZvMKZxsxYvuv7BKvvM37aOkgmjdXFteR62lo79j8wPeUdoFrN8Zv
+iQrSTvd4cxrJEGxRWz2rfSZOAUM=
+-----END CERTIFICATE-----
+`
+	cert3CertSubj = "CN=oauth2-proxy test cert3 ca"
+	cert3Cert     = `-----BEGIN CERTIFICATE-----
+MIIEwDCCAqgCAQEwDQYJKoZIhvcNAQELBQAwJTEjMCEGA1UEAwwab2F1dGgyLXBy
+b3h5IHRlc3Qgcm9vdDMgY2EwIBcNMjIwNzA3MjMyNzUzWhgPMzAyMTExMDcyMzI3
+NTNaMCUxIzAhBgNVBAMMGm9hdXRoMi1wcm94eSB0ZXN0IGNlcnQzIGNhMIICIjAN
+BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA3eyZDmSxuGDhSvzU0k3VxZDI9c+X
+ruVRjXwj/bA5K4OJa56GG7U2Md1VNog3t2CtJK4eMHMPl9t8wKj4ECs+IgL54o+o
+Vf/RTV1o/PUNYhKmQhuprPFEGYkpwJzZy9f9cgB16Lr4wuUhD/LzzzJwftn7orBM
+h6GooNtnxCYcmJVtFI73uwBT70cXEV+ziDyfutPIxSOC7XtcDzl5ZocapzezqTjN
+xFczC3r1bRDOEGa/B8yLaH1FqtH92+cZguwdhEq8TxZE+V1Ze/jqOfodFSpcpcz4
+8A2BIjNU2gDjsnWOKlI5NxS7UbTHqXX65GsYPR5jfsaEkGf1B5aqXXuElW2iW+cA
+YzSz2K2yI1VNAy1Vpw+P0PhvEjZyKFPihMn1KEkE3RRRbqaXI50csruUhqdH3mOO
+8sfLW5c4CsHB1FmLp3hD0S6yeOVb+QIQLNwVwcHsnhNKbz6h+J20yg5KYdN6pt/X
+45eDl/RTsw4utpHllQrq9m++KmZhiDWEtjnSTR4dAtKjW9AFvt/ntytG6hbxlnZb
+9dXLvE9FFqKSq4QGWc2YL+n+5iWSFk2ZqbMRPUsJ0eHukGkM0JMG7cwapnWuUKtx
+USxGbcW2k+DfgmlWhOfMHQszSWA9A1zFItf56s3i3OqpBd6YsBAK0W2tq19ZlgTR
+zWcDqKMep3TrDLkCAwEAATANBgkqhkiG9w0BAQsFAAOCAgEApdjE/eHCFOtBxvyk
+hNtjnXPwkTxesPbK1GwqhdDRA+k8xTFHML/zSS4OwYlxZBGCeBrFmxsN3mOvNnJc
+iNWaqjnKWA+Bec2oTXo7LX+i1YWHh0cJpPrSmMeVoqX1rsiugOzR3FtAawMN+b+Z
+UIC3q0zxZebB074xQxPcMtX7N288VWpFaITLcZftmSQ9w16qAuixVfTYbHQbcE2Q
+hCR2lNlNaGX5N9RaDD+MonB1iSJMFL8G9lhGHLPcjJwjkSHpcGp+yplbMRlsUqww
+ysfSIPMKydlLJUoVfhl1CnZmFE4NidxSF+wV0M3ai9CFGZ34HcXx28Jj8rFjnibN
+AFgjUfC7DvXLb3INJYHi9ZVTXZlliElF5evKhk9NzNXk1gdam+7uFvqPKSYGecX/
+fDcov2LkS95u4Zd692xaiE6UUxT4qcKEoZ5TM0aGjJjx0fDAYqgXes5lA3AYIN0S
+hAEEC1diGFkQivnBjfxKMkauStsV828yTxtqOdlu75sUcW58AUKJ0LZ6bf8gzVSf
+iY7GK6JBDUyme+kio9EutSx5WrTylPzCek9ajNoEUvlmBX8Dz+qG66vFfcff+bo2
+Ixm/I9aL4lNQiCmE5E044L00IX802hE9iSdYqFbUmefKqk9NWK3CPtDoobcFLi6u
+WrW4JMzLaGDtoHxRNNfo8E7fGkQ=
 -----END CERTIFICATE-----
 `
 )
@@ -77,21 +205,34 @@ func TestGetCertPool(t *testing.T) {
 		}
 	}(tempDir)
 
-	certFile1 := makeTestCertFile(t, testCA1, tempDir)
-	certFile2 := makeTestCertFile(t, testCA2, tempDir)
+	certFile1 := makeTestCertFile(t, root1Cert, tempDir)
+	certFile2 := makeTestCertFile(t, root2Cert, tempDir)
 
 	certPool, err := GetCertPool([]string{certFile1.Name(), certFile2.Name()})
 	assert.NoError(t, err)
 
-	subj := certPool.Subjects()
-	got := make([]string, 0)
-	for i := range subj {
-		var subject pkix.RDNSequence
-		_, err := asn1.Unmarshal(subj[i], &subject)
-		assert.NoError(t, err)
-		got = append(got, subject.String())
+	cert1Block, _ := pem.Decode([]byte(cert1Cert))
+	cert1, _ := x509.ParseCertificate(cert1Block.Bytes)
+	assert.Equal(t, cert1.Subject.String(), cert1CertSubj)
+
+	cert2Block, _ := pem.Decode([]byte(cert2Cert))
+	cert2, _ := x509.ParseCertificate(cert2Block.Bytes)
+	assert.Equal(t, cert2.Subject.String(), cert2CertSubj)
+
+	cert3Block, _ := pem.Decode([]byte(cert3Cert))
+	cert3, _ := x509.ParseCertificate(cert3Block.Bytes)
+	assert.Equal(t, cert3.Subject.String(), cert3CertSubj)
+
+	opts := x509.VerifyOptions{
+		Roots: certPool,
 	}
 
-	expectedSubjects := []string{testCA1Subj, testCA2Subj}
-	assert.Equal(t, expectedSubjects, got)
+	// "cert1" and "cert2" should be valid because "root1" and "root2" are in the certPool
+	// "cert3" should not be valid because "root3" is not in the certPool
+	_, err1 := cert1.Verify(opts)
+	assert.NoError(t, err1)
+	_, err2 := cert2.Verify(opts)
+	assert.NoError(t, err2)
+	_, err3 := cert3.Verify(opts)
+	assert.Error(t, err3)
 }
diff --git a/providers/keycloak_oidc_test.go b/providers/keycloak_oidc_test.go
index d1a50b2a..4f326f70 100644
--- a/providers/keycloak_oidc_test.go
+++ b/providers/keycloak_oidc_test.go
@@ -199,4 +199,22 @@ var _ = Describe("Keycloak OIDC Provider Tests", func() {
 			Expect(existingSession.Groups).To(BeEquivalentTo([]string{"role:write", "role:default:read"}))
 		})
 	})
+
+	Context("Create new session from token", func() {
+		It("should create a session and extract roles ", func() {
+			server, provider := newTestKeycloakOIDCSetup()
+			url, err := url.Parse(server.URL)
+			Expect(err).To(BeNil())
+			defer server.Close()
+
+			provider.ProfileURL = url
+
+			session, err := provider.CreateSessionFromToken(context.Background(), getAccessToken())
+			Expect(err).To(BeNil())
+			Expect(session.ExpiresOn).ToNot(BeNil())
+			Expect(session.CreatedAt).ToNot(BeNil())
+			Expect(session.Groups).To(BeEquivalentTo([]string{"role:write", "role:default:read"}))
+		})
+	})
+
 })