Repository: istio-ecosystem/authservice (go)

Issue:
Possible regression: case-sensitive validation of token_type
In 1.0.0, the functions [isValidIDPNewTokensResponse ](https://github.com/istio-ecosystem/authservice/blob/f2ef0e41de8a2d664b35a51bd43748e0ca4d10e8/internal/authz/oidc.go#L529) and [isValidIDPRefreshTokenResponse](https://github.com/istio-ecosystem/authservice/blob/f2ef0e41de8a2d664b35a51bd43748e0ca4d10e8/internal/authz/oidc.go#L554) check if the token_type in an IDP response exactly matches (case-sensitive) `Bearer`, whereas in version 0.5.3 this field was checked case-insensitively [here](https://github.com/istio-ecosystem/authservice/blob/e36b900b20ca3988e4c8d8caafb88cb2df7e734e/src/filters/oidc/token_response.cc#L215). This appears to be breaking our auth flows on the new version because ADFS returns the value as `bearer` (lowercase "b").

```
time="2024/05/10 14:48:12" level=debug msg="handling callback request" x-request-id="c772af90-0000-49a8-8a22-2b0c63d719f2" scope="authz" type="oidc" session-id="PEmFYiEPCYQImdc7OBLFhzOYgi7EmrzF3vysCSa9fLjXcknnYl8aimyK2HedRVJq"
time="2024/05/10 14:48:12" level=debug msg="getting authorization state" x-request-id="c772af90-0000-49a8-8a22-2b0c63d719f2" scope="session" type="memory" session-id="PEmFYiEPCYQImdc7OBLFhzOYgi7EmrzF3vysCSa9fLjXcknnYl8aimyK2HedRVJq"
time="2024/05/10 14:48:12" level=info msg="performing request to retrieve new tokens" x-request-id="c772af90-0000-49a8-8a22-2b0c63d719f2" scope="authz" type="oidc" session-id="PEmFYiEPCYQImdc7OBLFhzOYgi7EmrzF3vysCSa9fLjXcknnYl8aimyK2HedRVJq"
time="2024/05/10 14:48:12" level=info msg="token type is not Bearer in token response" x-request-id="c772af90-0000-49a8-8a22-2b0c63d719f2" scope="authz" type="oidc" session-id="PEmFYiEPCYQImdc7OBLFhzOYgi7EmrzF3vysCSa9fLjXcknnYl8aimyK2HedRVJq" token-type="bearer"
time="2024/05/10 14:48:12" level=debug msg="process result" x-request-id="c772af90-0000-49a8-8a22-2b0c63d719f2" scope="authz" type="oidc" session-id="PEmFYiEPCYQImdc7OBLFhzOYgi7EmrzF3vysCSa9fLjXcknnYl8aimyK2HedRVJq" allow=false status="InvalidArgument"
```

From [RFC-6749](https://datatracker.ietf.org/doc/html/rfc6749#section-5.1):
```
   token_type
         REQUIRED.  The type of the token issued as described in
         Section 7.1.  Value is case insensitive.
```

So it appears the old behavior of case-insensitive validation was correct.

Pull request:
Validate token_type case-insensitively
Fixes #255 

Patch:
diff --git a/internal/authz/oidc.go b/internal/authz/oidc.go
index d77ba90..d5a2d8f 100644
--- a/internal/authz/oidc.go
+++ b/internal/authz/oidc.go
@@ -528,7 +528,7 @@ func performIDPRequest(log telemetry.Logger, client *http.Client, uri string, fo
 // https://openid.net/specs/openid-connect-core-1_0.html#TokenResponse
 func isValidIDPNewTokensResponse(log telemetry.Logger, config *oidcv1.OIDCConfig, tokenResponse *idpTokensResponse) bool {
 	// token_type must be Bearer
-	if tokenResponse.TokenType != "Bearer" {
+	if !strings.EqualFold(tokenResponse.TokenType, "Bearer") {
 		log.Info("token type is not Bearer in token response", "token-type", tokenResponse.TokenType)
 		return false
 	}
@@ -553,7 +553,7 @@ func isValidIDPNewTokensResponse(log telemetry.Logger, config *oidcv1.OIDCConfig
 // https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokenResponse
 func isValidIDPRefreshTokenResponse(log telemetry.Logger, tokenResponse *idpTokensResponse) bool {
 	// token_type must be Bearer
-	if tokenResponse.TokenType != "Bearer" {
+	if !strings.EqualFold(tokenResponse.TokenType, "Bearer") {
 		log.Info("token type is not Bearer in token response", "token-type", tokenResponse.TokenType)
 		return false
 	}


Test patch:
diff --git a/internal/authz/oidc_test.go b/internal/authz/oidc_test.go
index c54a4e1..c51355d 100644
--- a/internal/authz/oidc_test.go
+++ b/internal/authz/oidc_test.go
@@ -868,6 +868,24 @@ func TestOIDCProcess(t *testing.T) {
 				requireStoredState(t, store, sessionID, false)
 			},
 		},
+		{
+			name:                "IDP server returns lowercase 'bearer' token, succeeds",
+			req:                 withSessionHeader,
+			storedTokenResponse: expiredTokenResponse,
+			mockTokensResponse: &idpTokensResponse{
+				IDToken:     validIDToken,
+				AccessToken: "access-token",
+				TokenType:   "bearer",
+				ExpiresIn:   10,
+			},
+			responseVerify: func(t *testing.T, resp *envoy.CheckResponse) {
+				require.Equal(t, int32(codes.OK), resp.GetStatus().GetCode())
+				require.NotNil(t, resp.GetOkResponse())
+				requireTokensInResponse(t, resp.GetOkResponse(), basicOIDCConfig, validIDToken, "access-token")
+				requireStoredTokens(t, store, sessionID, true)
+				requireStoredTokens(t, store, newSessionID, false)
+			},
+		},
 		{
 			name:                "succeed",
 			req:                 withSessionHeader,