Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use dynamic signing key switching #406

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [unreleased]

- `session.CreateNewSession` now defaults to the value of the `st-auth-mode` header (if available) if the configured `config.GetTokenTransferMethod` returns `any`.
- Enable smooth switching between `useDynamicAccessTokenSigningKey` settings by allowing refresh calls to change the signing key type of a session.

## [0.17.5] - 2024-03-14
- Adds a type uint64 to the `accessTokenCookiesExpiryDurationMillis` local variable in `recipe/session/utils.go`. It also removes the redundant `uint64` type forcing needed because of the untyped variable.
Expand Down
2 changes: 1 addition & 1 deletion recipe/session/recipeImplementation.go
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ func MakeRecipeImplementation(querier supertokens.Querier, config sessmodels.Typ

supertokens.LogDebugMessage("refreshSession: Started")

response, err := refreshSessionHelper(config, querier, refreshToken, antiCsrfToken, disableAntiCsrf, userContext)
response, err := refreshSessionHelper(config, querier, refreshToken, antiCsrfToken, disableAntiCsrf, config.UseDynamicAccessTokenSigningKey, userContext)
if err != nil {
return nil, err
}
Expand Down
7 changes: 4 additions & 3 deletions recipe/session/sessionFunctions.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,10 +224,11 @@ func getSessionInformationHelper(querier supertokens.Querier, sessionHandle stri
return nil, nil
}

func refreshSessionHelper(config sessmodels.TypeNormalisedInput, querier supertokens.Querier, refreshToken string, antiCsrfToken *string, disableAntiCsrf bool, userContext supertokens.UserContext) (sessmodels.CreateOrRefreshAPIResponse, error) {
func refreshSessionHelper(config sessmodels.TypeNormalisedInput, querier supertokens.Querier, refreshToken string, antiCsrfToken *string, disableAntiCsrf bool, useDynamicAccessTokenSigningKey bool, userContext supertokens.UserContext) (sessmodels.CreateOrRefreshAPIResponse, error) {
requestBody := map[string]interface{}{
"refreshToken": refreshToken,
"enableAntiCsrf": !disableAntiCsrf && config.AntiCsrfFunctionOrString.StrValue == AntiCSRF_VIA_TOKEN,
"refreshToken": refreshToken,
"enableAntiCsrf": !disableAntiCsrf && config.AntiCsrfFunctionOrString.StrValue == AntiCSRF_VIA_TOKEN,
"useDynamicSigningKey": useDynamicAccessTokenSigningKey,
}
if antiCsrfToken != nil {
requestBody["antiCsrfToken"] = *antiCsrfToken
Expand Down
85 changes: 85 additions & 0 deletions recipe/session/sessionHandlingFuncsWithoutReq_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package session

import (
"errors"
"strings"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -472,3 +473,87 @@ func TestRefreshShouldReturnErrorForNonTokens(t *testing.T) {
assert.NotNil(t, err2)
assert.True(t, errors.As(err2, &sessionError.UnauthorizedError{}))
}

func TestUseDynamicAccessTokenSigningKey(t *testing.T) {
useDynamicAccessTokenSigningKey := true
configValue := supertokens.TypeInput{
Supertokens: &supertokens.ConnectionInfo{
ConnectionURI: "http://localhost:8080",
},
AppInfo: supertokens.AppInfo{
AppName: "SuperTokens",
WebsiteDomain: "supertokens.io",
APIDomain: "api.supertokens.io",
},
RecipeList: []supertokens.Recipe{
Init(&sessmodels.TypeInput{
UseDynamicAccessTokenSigningKey: &useDynamicAccessTokenSigningKey,
}),
},
}
BeforeEach()
unittesting.StartUpST("localhost", "8080")
defer AfterEach()

checkAccessTokenSigningKeyType := func(t *testing.T, tokens sessmodels.SessionTokens, isDynamic bool) {
t.Helper()

info, err := ParseJWTWithoutSignatureVerification(tokens.AccessToken)
assert.NoError(t, err)

if isDynamic {
assert.True(t, strings.HasPrefix(*info.KID, "d-"))
} else {
assert.True(t, strings.HasPrefix(*info.KID, "s-"))
}
}

err := supertokens.Init(configValue)
assert.NoError(t, err)

res, err := CreateNewSessionWithoutRequestResponse("public", "test-user-id", map[string]interface{}{
"tokenProp": true,
}, map[string]interface{}{
"dbProp": true,
}, nil)

assert.NoError(t, err)

tokens := res.GetAllSessionTokensDangerously()
checkAccessTokenSigningKeyType(t, tokens, true)

resetAll()

// here we change to false
useDynamicAccessTokenSigningKey = false
err = supertokens.Init(configValue)
assert.NoError(t, err)

t.Run("should throw when verifying", func(t *testing.T) {
_, err = GetSessionWithoutRequestResponse(tokens.AccessToken, tokens.AntiCsrfToken, nil)
assert.Equal(t, err.Error(), "The access token doesn't match the useDynamicAccessTokenSigningKey setting")
})

t.Run("should work after refresh", func(t *testing.T) {
disableAntiCsrf := true
refreshedSession, err := RefreshSessionWithoutRequestResponse(*tokens.RefreshToken, &disableAntiCsrf, tokens.AntiCsrfToken)
assert.NoError(t, err)

tokensAfterRefresh := refreshedSession.GetAllSessionTokensDangerously()
assert.True(t, tokensAfterRefresh.AccessAndFrontendTokenUpdated)
checkAccessTokenSigningKeyType(t, tokensAfterRefresh, false)

verifiedSession, err := GetSessionWithoutRequestResponse(tokensAfterRefresh.AccessToken, tokensAfterRefresh.AntiCsrfToken, nil)
assert.NoError(t, err)

tokensAfterVerify := verifiedSession.GetAllSessionTokensDangerously()
assert.True(t, tokensAfterVerify.AccessAndFrontendTokenUpdated)
checkAccessTokenSigningKeyType(t, tokensAfterVerify, false)

verifiedSession2, err := GetSessionWithoutRequestResponse(tokensAfterVerify.AccessToken, tokensAfterVerify.AntiCsrfToken, nil)
assert.NoError(t, err)

tokensAfterVerify2 := verifiedSession2.GetAllSessionTokensDangerously()
assert.False(t, tokensAfterVerify2.AccessAndFrontendTokenUpdated)
})
}
Loading