Update tests for incorporating fosite bug fix for ID token at_hash claim
This commit is contained in:
parent
afc73221d6
commit
9e39df405a
@ -2807,8 +2807,6 @@ func TestRefreshGrant(t *testing.T) {
|
|||||||
test.idps.RequireExactlyZeroCallsToValidateToken(t)
|
test.idps.RequireExactlyZeroCallsToValidateToken(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The bug in fosite that prevents at_hash from appearing in the initial ID token does not impact the refreshed ID token
|
|
||||||
wantAtHashClaimInIDToken := true
|
|
||||||
// Refreshed ID tokens do not include the nonce from the original auth request
|
// Refreshed ID tokens do not include the nonce from the original auth request
|
||||||
wantNonceValueInIDToken := false
|
wantNonceValueInIDToken := false
|
||||||
|
|
||||||
@ -2816,7 +2814,6 @@ func TestRefreshGrant(t *testing.T) {
|
|||||||
test.refreshRequest.want,
|
test.refreshRequest.want,
|
||||||
test.authcodeExchange.want.wantGroups, // the old groups from the initial login
|
test.authcodeExchange.want.wantGroups, // the old groups from the initial login
|
||||||
test.authcodeExchange.customSessionData, // the old custom session data from the initial login
|
test.authcodeExchange.customSessionData, // the old custom session data from the initial login
|
||||||
wantAtHashClaimInIDToken,
|
|
||||||
wantNonceValueInIDToken,
|
wantNonceValueInIDToken,
|
||||||
refreshResponse,
|
refreshResponse,
|
||||||
authCode,
|
authCode,
|
||||||
@ -2854,8 +2851,9 @@ func TestRefreshGrant(t *testing.T) {
|
|||||||
err = secondIDTokenDecoded.UnsafeClaimsWithoutVerification(&claimsOfSecondIDToken)
|
err = secondIDTokenDecoded.UnsafeClaimsWithoutVerification(&claimsOfSecondIDToken)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
requireClaimsAreNotEqual(t, "jti", claimsOfFirstIDToken, claimsOfSecondIDToken) // JWT ID
|
requireClaimsAreNotEqual(t, "jti", claimsOfFirstIDToken, claimsOfSecondIDToken) // JWT ID
|
||||||
requireClaimsAreNotEqual(t, "exp", claimsOfFirstIDToken, claimsOfSecondIDToken) // expires at
|
requireClaimsAreNotEqual(t, "at_hash", claimsOfFirstIDToken, claimsOfSecondIDToken) // access token hash
|
||||||
|
requireClaimsAreNotEqual(t, "exp", claimsOfFirstIDToken, claimsOfSecondIDToken) // expires at
|
||||||
require.Greater(t, claimsOfSecondIDToken["exp"], claimsOfFirstIDToken["exp"])
|
require.Greater(t, claimsOfSecondIDToken["exp"], claimsOfFirstIDToken["exp"])
|
||||||
requireClaimsAreNotEqual(t, "iat", claimsOfFirstIDToken, claimsOfSecondIDToken) // issued at
|
requireClaimsAreNotEqual(t, "iat", claimsOfFirstIDToken, claimsOfSecondIDToken) // issued at
|
||||||
require.Greater(t, claimsOfSecondIDToken["iat"], claimsOfFirstIDToken["iat"])
|
require.Greater(t, claimsOfSecondIDToken["iat"], claimsOfFirstIDToken["iat"])
|
||||||
@ -2937,14 +2935,12 @@ func exchangeAuthcodeForTokens(t *testing.T, test authcodeExchangeInputs, idps p
|
|||||||
t.Logf("response: %#v", rsp)
|
t.Logf("response: %#v", rsp)
|
||||||
t.Logf("response body: %q", rsp.Body.String())
|
t.Logf("response body: %q", rsp.Body.String())
|
||||||
|
|
||||||
wantAtHashClaimInIDToken := false // due to a bug in fosite, the at_hash claim is not filled in during authcode exchange
|
wantNonceValueInIDToken := true // ID tokens returned by the authcode exchange must include the nonce from the auth request (unliked refreshed ID tokens)
|
||||||
wantNonceValueInIDToken := true // ID tokens returned by the authcode exchange must include the nonce from the auth request (unliked refreshed ID tokens)
|
|
||||||
|
|
||||||
requireTokenEndpointBehavior(t,
|
requireTokenEndpointBehavior(t,
|
||||||
test.want,
|
test.want,
|
||||||
goodGroups, // the old groups from the initial login
|
goodGroups, // the old groups from the initial login
|
||||||
test.customSessionData, // the old custom session data from the initial login
|
test.customSessionData, // the old custom session data from the initial login
|
||||||
wantAtHashClaimInIDToken,
|
|
||||||
wantNonceValueInIDToken,
|
wantNonceValueInIDToken,
|
||||||
rsp,
|
rsp,
|
||||||
authCode,
|
authCode,
|
||||||
@ -2961,7 +2957,6 @@ func requireTokenEndpointBehavior(
|
|||||||
test tokenEndpointResponseExpectedValues,
|
test tokenEndpointResponseExpectedValues,
|
||||||
oldGroups []string,
|
oldGroups []string,
|
||||||
oldCustomSessionData *psession.CustomSessionData,
|
oldCustomSessionData *psession.CustomSessionData,
|
||||||
wantAtHashClaimInIDToken bool,
|
|
||||||
wantNonceValueInIDToken bool,
|
wantNonceValueInIDToken bool,
|
||||||
tokenEndpointResponse *httptest.ResponseRecorder,
|
tokenEndpointResponse *httptest.ResponseRecorder,
|
||||||
authCode string,
|
authCode string,
|
||||||
@ -2995,7 +2990,7 @@ func requireTokenEndpointBehavior(
|
|||||||
expectedNumberOfIDSessionsStored := 0
|
expectedNumberOfIDSessionsStored := 0
|
||||||
if wantIDToken {
|
if wantIDToken {
|
||||||
expectedNumberOfIDSessionsStored = 1
|
expectedNumberOfIDSessionsStored = 1
|
||||||
requireValidIDToken(t, parsedResponseBody, jwtSigningKey, wantAtHashClaimInIDToken, wantNonceValueInIDToken, test.wantGroups, parsedResponseBody["access_token"].(string))
|
requireValidIDToken(t, parsedResponseBody, jwtSigningKey, wantNonceValueInIDToken, test.wantGroups, parsedResponseBody["access_token"].(string))
|
||||||
}
|
}
|
||||||
if wantRefreshToken {
|
if wantRefreshToken {
|
||||||
requireValidRefreshTokenStorage(t, parsedResponseBody, oauthStore, test.wantRequestedScopes, test.wantGrantedScopes, test.wantGroups, test.wantCustomSessionDataStored, secrets)
|
requireValidRefreshTokenStorage(t, parsedResponseBody, oauthStore, test.wantRequestedScopes, test.wantGrantedScopes, test.wantGroups, test.wantCustomSessionDataStored, secrets)
|
||||||
@ -3518,7 +3513,6 @@ func requireValidIDToken(
|
|||||||
t *testing.T,
|
t *testing.T,
|
||||||
body map[string]interface{},
|
body map[string]interface{},
|
||||||
jwtSigningKey *ecdsa.PrivateKey,
|
jwtSigningKey *ecdsa.PrivateKey,
|
||||||
wantAtHashClaimInIDToken bool,
|
|
||||||
wantNonceValueInIDToken bool,
|
wantNonceValueInIDToken bool,
|
||||||
wantGroupsInIDToken []string,
|
wantGroupsInIDToken []string,
|
||||||
actualAccessToken string,
|
actualAccessToken string,
|
||||||
@ -3548,13 +3542,7 @@ func requireValidIDToken(
|
|||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note that there is a bug in fosite which prevents the `at_hash` claim from appearing in this ID token
|
idTokenFields := []string{"sub", "aud", "iss", "jti", "auth_time", "exp", "iat", "rat", "at_hash", "groups", "username"}
|
||||||
// during the initial authcode exchange, but does not prevent `at_hash` from appearing in the refreshed ID token.
|
|
||||||
// We can add a workaround for this later.
|
|
||||||
idTokenFields := []string{"sub", "aud", "iss", "jti", "auth_time", "exp", "iat", "rat", "groups", "username"}
|
|
||||||
if wantAtHashClaimInIDToken {
|
|
||||||
idTokenFields = append(idTokenFields, "at_hash")
|
|
||||||
}
|
|
||||||
if wantNonceValueInIDToken {
|
if wantNonceValueInIDToken {
|
||||||
idTokenFields = append(idTokenFields, "nonce")
|
idTokenFields = append(idTokenFields, "nonce")
|
||||||
}
|
}
|
||||||
@ -3590,12 +3578,8 @@ func requireValidIDToken(
|
|||||||
testutil.RequireTimeInDelta(t, goodRequestedAtTime, requestedAt, timeComparisonFudgeSeconds*time.Second)
|
testutil.RequireTimeInDelta(t, goodRequestedAtTime, requestedAt, timeComparisonFudgeSeconds*time.Second)
|
||||||
testutil.RequireTimeInDelta(t, goodAuthTime, authTime, timeComparisonFudgeSeconds*time.Second)
|
testutil.RequireTimeInDelta(t, goodAuthTime, authTime, timeComparisonFudgeSeconds*time.Second)
|
||||||
|
|
||||||
if wantAtHashClaimInIDToken {
|
require.NotEmpty(t, actualAccessToken)
|
||||||
require.NotEmpty(t, actualAccessToken)
|
require.Equal(t, hashAccessToken(actualAccessToken), claims.AccessTokenHash)
|
||||||
require.Equal(t, hashAccessToken(actualAccessToken), claims.AccessTokenHash)
|
|
||||||
} else {
|
|
||||||
require.Empty(t, claims.AccessTokenHash)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func deepCopyRequestForm(r *http.Request) *http.Request {
|
func deepCopyRequestForm(r *http.Request) *http.Request {
|
||||||
|
@ -5,6 +5,7 @@ package integration
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@ -1825,7 +1826,7 @@ func testSupervisorLogin(
|
|||||||
tokenResponse, err := downstreamOAuth2Config.Exchange(oidcHTTPClientContext, authcode, pkceParam.Verifier())
|
tokenResponse, err := downstreamOAuth2Config.Exchange(oidcHTTPClientContext, authcode, pkceParam.Verifier())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
expectedIDTokenClaims := []string{"iss", "exp", "sub", "aud", "auth_time", "iat", "jti", "nonce", "rat", "username", "groups"}
|
expectedIDTokenClaims := []string{"iss", "exp", "sub", "aud", "auth_time", "iat", "jti", "nonce", "rat", "username", "groups", "at_hash"}
|
||||||
verifyTokenResponse(t,
|
verifyTokenResponse(t,
|
||||||
tokenResponse, discovery, downstreamOAuth2Config, nonceParam,
|
tokenResponse, discovery, downstreamOAuth2Config, nonceParam,
|
||||||
expectedIDTokenClaims, wantDownstreamIDTokenSubjectToMatch, wantDownstreamIDTokenUsernameToMatch(username), wantDownstreamIDTokenGroups)
|
expectedIDTokenClaims, wantDownstreamIDTokenSubjectToMatch, wantDownstreamIDTokenUsernameToMatch(username), wantDownstreamIDTokenGroups)
|
||||||
@ -1861,7 +1862,7 @@ func testSupervisorLogin(
|
|||||||
refreshedTokenResponse, err := refreshSource.Token()
|
refreshedTokenResponse, err := refreshSource.Token()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// When refreshing, expect to get an "at_hash" claim, but no "nonce" claim.
|
// When refreshing, do not expect a "nonce" claim.
|
||||||
expectRefreshedIDTokenClaims := []string{"iss", "exp", "sub", "aud", "auth_time", "iat", "jti", "rat", "username", "groups", "at_hash"}
|
expectRefreshedIDTokenClaims := []string{"iss", "exp", "sub", "aud", "auth_time", "iat", "jti", "rat", "username", "groups", "at_hash"}
|
||||||
verifyTokenResponse(t,
|
verifyTokenResponse(t,
|
||||||
refreshedTokenResponse, discovery, downstreamOAuth2Config, "",
|
refreshedTokenResponse, discovery, downstreamOAuth2Config, "",
|
||||||
@ -1982,6 +1983,21 @@ func verifyTokenResponse(
|
|||||||
require.NotEmpty(t, tokenResponse.RefreshToken)
|
require.NotEmpty(t, tokenResponse.RefreshToken)
|
||||||
// Refresh tokens should start with the custom prefix "pin_rt_" to make them identifiable as refresh tokens when seen by a user out of context.
|
// Refresh tokens should start with the custom prefix "pin_rt_" to make them identifiable as refresh tokens when seen by a user out of context.
|
||||||
require.True(t, strings.HasPrefix(tokenResponse.RefreshToken, "pin_rt_"), "token %q did not have expected prefix 'pin_rt_'", tokenResponse.RefreshToken)
|
require.True(t, strings.HasPrefix(tokenResponse.RefreshToken, "pin_rt_"), "token %q did not have expected prefix 'pin_rt_'", tokenResponse.RefreshToken)
|
||||||
|
|
||||||
|
// The at_hash claim should be present and should be equal to the hash of the access token.
|
||||||
|
actualAccessTokenHashClaimValue := idTokenClaims["at_hash"]
|
||||||
|
require.NotEmpty(t, actualAccessTokenHashClaimValue)
|
||||||
|
require.Equal(t, hashAccessToken(tokenResponse.AccessToken), actualAccessTokenHashClaimValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
func hashAccessToken(accessToken string) string {
|
||||||
|
// See https://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken.
|
||||||
|
// "Access Token hash value. Its value is the base64url encoding of the left-most half of
|
||||||
|
// the hash of the octets of the ASCII representation of the access_token value, where the
|
||||||
|
// hash algorithm used is the hash algorithm used in the alg Header Parameter of the ID
|
||||||
|
// Token's JOSE Header."
|
||||||
|
b := sha256.Sum256([]byte(accessToken))
|
||||||
|
return base64.RawURLEncoding.EncodeToString(b[:len(b)/2])
|
||||||
}
|
}
|
||||||
|
|
||||||
func requestAuthorizationUsingBrowserAuthcodeFlow(t *testing.T, downstreamAuthorizeURL, downstreamCallbackURL, _, _ string, httpClient *http.Client) {
|
func requestAuthorizationUsingBrowserAuthcodeFlow(t *testing.T, downstreamAuthorizeURL, downstreamCallbackURL, _, _ string, httpClient *http.Client) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user