fix: allow expired ID token hint to end sessions (#522)

* fix: allow expired ID token hint to end sessions

This change adds a specific error for expired ID Token hints, including too old "issued at" and "max auth age".
The error is returned VerifyIDTokenHint so that the end session handler can choose to ignore this error.

This fixes the behavior to be in line with [OpenID Connect RP-Initiated Logout 1.0, section 4](https://openid.net/specs/openid-connect-rpinitiated-1_0.html#ValidationAndErrorHandling).

* Tes IDTokenHintExpiredError
This commit is contained in:
Tim Möhlmann 2024-01-19 12:30:51 +02:00 committed by GitHub
parent 3f26eb10ad
commit b8e520afd0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 74 additions and 46 deletions

View file

@ -2,6 +2,7 @@ package op
import (
"context"
"errors"
"testing"
"time"
@ -57,6 +58,13 @@ func TestNewIDTokenHintVerifier(t *testing.T) {
}
}
func Test_IDTokenHintExpiredError(t *testing.T) {
var err error = IDTokenHintExpiredError{oidc.ErrExpired}
assert.True(t, errors.Unwrap(err) == oidc.ErrExpired)
assert.ErrorIs(t, err, oidc.ErrExpired)
assert.ErrorAs(t, err, &IDTokenHintExpiredError{})
}
func TestVerifyIDTokenHint(t *testing.T) {
verifier := &IDTokenHintVerifier{
Issuer: tu.ValidIssuer,
@ -71,21 +79,23 @@ func TestVerifyIDTokenHint(t *testing.T) {
tests := []struct {
name string
tokenClaims func() (string, *oidc.IDTokenClaims)
wantErr bool
wantClaims bool
wantErr error
}{
{
name: "success",
tokenClaims: tu.ValidIDToken,
wantClaims: true,
},
{
name: "parse err",
tokenClaims: func() (string, *oidc.IDTokenClaims) { return "~~~~", nil },
wantErr: true,
wantErr: oidc.ErrParse,
},
{
name: "invalid signature",
tokenClaims: func() (string, *oidc.IDTokenClaims) { return tu.InvalidSignatureToken, nil },
wantErr: true,
wantErr: oidc.ErrSignatureUnsupportedAlg,
},
{
name: "wrong issuer",
@ -96,29 +106,7 @@ func TestVerifyIDTokenHint(t *testing.T) {
tu.ValidACR, tu.ValidAMR, tu.ValidClientID, tu.ValidSkew, "",
)
},
wantErr: true,
},
{
name: "expired",
tokenClaims: func() (string, *oidc.IDTokenClaims) {
return tu.NewIDToken(
tu.ValidIssuer, tu.ValidSubject, tu.ValidAudience,
tu.ValidExpiration.Add(-time.Hour), tu.ValidAuthTime, tu.ValidNonce,
tu.ValidACR, tu.ValidAMR, tu.ValidClientID, tu.ValidSkew, "",
)
},
wantErr: true,
},
{
name: "wrong IAT",
tokenClaims: func() (string, *oidc.IDTokenClaims) {
return tu.NewIDToken(
tu.ValidIssuer, tu.ValidSubject, tu.ValidAudience,
tu.ValidExpiration, tu.ValidAuthTime, tu.ValidNonce,
tu.ValidACR, tu.ValidAMR, tu.ValidClientID, -time.Hour, "",
)
},
wantErr: true,
wantErr: oidc.ErrIssuerInvalid,
},
{
name: "wrong acr",
@ -129,7 +117,31 @@ func TestVerifyIDTokenHint(t *testing.T) {
"else", tu.ValidAMR, tu.ValidClientID, tu.ValidSkew, "",
)
},
wantErr: true,
wantErr: oidc.ErrAcrInvalid,
},
{
name: "expired",
tokenClaims: func() (string, *oidc.IDTokenClaims) {
return tu.NewIDToken(
tu.ValidIssuer, tu.ValidSubject, tu.ValidAudience,
tu.ValidExpiration.Add(-time.Hour), tu.ValidAuthTime, tu.ValidNonce,
tu.ValidACR, tu.ValidAMR, tu.ValidClientID, tu.ValidSkew, "",
)
},
wantClaims: true,
wantErr: IDTokenHintExpiredError{oidc.ErrExpired},
},
{
name: "IAT too old",
tokenClaims: func() (string, *oidc.IDTokenClaims) {
return tu.NewIDToken(
tu.ValidIssuer, tu.ValidSubject, tu.ValidAudience,
tu.ValidExpiration, tu.ValidAuthTime, tu.ValidNonce,
tu.ValidACR, tu.ValidAMR, tu.ValidClientID, time.Hour, "",
)
},
wantClaims: true,
wantErr: IDTokenHintExpiredError{oidc.ErrIatToOld},
},
{
name: "expired auth",
@ -140,7 +152,8 @@ func TestVerifyIDTokenHint(t *testing.T) {
tu.ValidACR, tu.ValidAMR, tu.ValidClientID, tu.ValidSkew, "",
)
},
wantErr: true,
wantClaims: true,
wantErr: IDTokenHintExpiredError{oidc.ErrAuthTimeToOld},
},
}
for _, tt := range tests {
@ -148,14 +161,12 @@ func TestVerifyIDTokenHint(t *testing.T) {
token, want := tt.tokenClaims()
got, err := VerifyIDTokenHint[*oidc.IDTokenClaims](context.Background(), token, verifier)
if tt.wantErr {
assert.Error(t, err)
assert.Nil(t, got)
require.ErrorIs(t, err, tt.wantErr)
if tt.wantClaims {
assert.Equal(t, got, want, "claims")
return
}
require.NoError(t, err)
require.NotNil(t, got)
assert.Equal(t, got, want)
assert.Nil(t, got, "claims")
})
}
}