Skip to content

Commit 2b05770

Browse files
Merge pull request #266 from supertokens/feat/jwt-rework
feat: Access token rework
2 parents 691aeed + ef20adf commit 2b05770

File tree

74 files changed

+4231
-1875
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+4231
-1875
lines changed

CHANGELOG.md

Lines changed: 296 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,304 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [unreleased]
99

10-
## [0.12.0] - 2023-05-03
10+
## [0.12.0] - 2023-05-05
11+
12+
### Added
13+
1114
- added optional password policy check in `updateEmailOrPassword`
1215

16+
### Breaking Changes
17+
18+
- Changed the interface and configuration of the Session recipe, see below for details. If you do not use the Session recipe directly and do not provide custom configuration, then no migration is necessary.
19+
- Renamed `GetSessionData` to `GetSessionDataInDatabase` to clarify that it always hits the DB
20+
- Renamed `GetSessionDataWithContext` to `GetSessionDataInDatabaseWithContext` to clarify that it always hits the DB
21+
- Renamed `UpdateSessionData` to `UpdateSessionDataInDatabase`
22+
- Renamed `UpdateSessionDataWithContext` to `UpdateSessionDataInDatabaseWithContext` to clarify that it always hits the DB
23+
- Renamed `SessionData` to `SessionDataInDatabase` in `SessionInformation`
24+
- Renamed `sessionData` to `sessionDataInDatabase` in the input to `CreateNewSession`
25+
- Added `useStaticSigningKey` to `CreateJWT` and `CreateJWTWithContext`
26+
- Added support for CDI version `2.21`
27+
- Dropped support for CDI version `2.8`-`2.20`
28+
- `GetAccessTokenPayload` will now return standard (`sub`, `iat`, `exp`) claims and some SuperTokens specific claims along the user defined ones in `GetAccessTokenPayload`.
29+
- Some claim names are now prohibited in the root level of the access token payload
30+
- They are: `sub`, `iat`, `exp`, `sessionHandle`, `parentRefreshTokenHash1`, `refreshTokenHash1`, `antiCsrfToken`
31+
- If you used these in the root level of the access token payload, then you'll need to migrate your sessions or they will be logged out during the next refresh
32+
- These props should be renamed (e.g., by adding a prefix) or moved inside an object in the access token payload
33+
- You can migrate these sessions by updating their payload to match your new structure, by calling `MergeIntoAccessTokenPayload`
34+
- New access tokens are valid JWTs now
35+
- They can be used directly (i.e.: by calling `GetAccessToken` on the session) if you need a JWT
36+
- The `jwt` prop in the access token payload is removed
37+
- JWT and OpenId related configuration has been removed from the Session recipe config. If necessary, they can be added by initializing the OpenId recipe before the Session recipe.
38+
- Changed the Session recipe interface - CreateNewSession, GetSession and RefreshSession overrides now do not take response and request and return status instead of throwing
39+
- Renamed `AccessTokenPayload` to `CustomClaimsInAccessTokenPayload` in `SessionInformation` (the return value of `GetSessionInformation`). This reflects the fact that it doesn't contain some default claims (`sub`, `iat`, etc.)
40+
41+
### Changed
42+
43+
- Refactors the URL for the JWKS endpoint exposed by SuperTokens core
44+
- Added new optional `useStaticSigningKey` param to `CreateJWT`
45+
- The Session recipe now always initializes the OpenID recipe if it hasn't been initialized.
46+
- Refactored how access token validation is done
47+
- Added support for new access token version
48+
- Removed the handshake call to improve start-up times
49+
- Removed `GetAccessTokenLifeTimeMS` and `GetRefreshTokenLifeTimeMS` functions
50+
- Added `ExposeAccessTokenToFrontendInCookieBasedAuth` (defaults to `false`) option to the Session recipe config
51+
- Added new `checkDatabase` param to `VerifySession` and `GetSession`
52+
- Removed deprecated `UpdateAccessTokenPayload`, `UpdateAccessTokenPayloadWithContext`, `RegenerateAccessToken` and `RegenerateAccessTokenWithContext` from the Session recipe interface
53+
- Added `CreateNewSessionWithoutRequestResponse`, `CreateNewSessionWithContextWithoutRequestResponse`, `GetSessionWithoutRequestResponse`, `GetSessionWithContextWithoutRequestResponse`, `RefreshSession`, `RefreshSessionWithContextWithoutRequestResponse` to the Session recipe.
54+
- Added `GetAllSessionTokensDangerously` to session objects (`SessionContainer`)
55+
- Added `AttachToRequestResponse` to session objects (`SessionContainer`)
56+
57+
### Migration
58+
59+
#### If self-hosting core
60+
61+
1. You need to update the core version
62+
2. There are manual migration steps needed. Check out the core changelogs for more details.
63+
64+
#### If you used the jwt feature of the session recipe
65+
66+
1. Add `ExposeAccessTokenToFrontendInCookieBasedAuth: true` to the Session recipe config on the backend if you need to access the JWT on the frontend.
67+
2. On the frontend where you accessed the JWT before by: `(await Session.getAccessTokenPayloadSecurely()).jwt` update to:
68+
69+
```tsx
70+
let jwt = null;
71+
const accessTokenPayload = await Session.getAccessTokenPayloadSecurely();
72+
if (accessTokenPayload.jwt === undefined) {
73+
jwt = await Session.getAccessToken();
74+
} else {
75+
// This branch is only required if there are valid access tokens created before the update
76+
// It can be removed after the validity period ends
77+
jwt = accessTokenPayload.jwt;
78+
}
79+
```
80+
81+
3. On the backend if you accessed the JWT before by `session.GetAccessTokenPayload()["jwt"]` please update to:
82+
83+
```go
84+
var jwt string
85+
accessTokenPayload := session.GetAccessTokenPayload();
86+
if (accessTokenPayload["jwt"] == nil) {
87+
jwt = session.GetAccessToken();
88+
} else {
89+
// This branch is only required if there are valid access tokens created before the update
90+
// It can be removed after the validity period ends
91+
jwt = accessTokenPayload["jwt"].(string);
92+
}
93+
```
94+
95+
#### If you used to set an issuer in the session recipe `Jwt` configuration
96+
97+
- You can add an issuer claim to access tokens by overriding the `CreateNewSession` function in the session recipe init.
98+
- Check out https://supertokens.com/docs/passwordless/common-customizations/sessions/claims/access-token-payload#during-session-creation for more information
99+
- You can add an issuer claim to JWTs created by the JWT recipe by passing the `iss` claim as part of the payload.
100+
- You can set the OpenId discovery configuration as follows:
101+
102+
Before:
103+
104+
```go
105+
func main() {
106+
supertokens.Init(supertokens.TypeInput{
107+
RecipeList: []supertokens.Recipe{
108+
session.Init(&sessmodels.TypeInput{
109+
Jwt: &sessmodels.JWTInputConfig{
110+
Enable: true,
111+
Issuer: "...",
112+
},
113+
}),
114+
},
115+
})
116+
}
117+
```
118+
119+
After:
120+
121+
```go
122+
func main() {
123+
supertokens.Init(supertokens.TypeInput{
124+
RecipeList: []supertokens.Recipe{
125+
session.Init(&sessmodels.TypeInput{
126+
GetTokenTransferMethod: func(req *http.Request, forCreateNewSession bool, userContext supertokens.UserContext) sessmodels.TokenTransferMethod {
127+
return sessmodels.HeaderTransferMethod
128+
},
129+
Override: &sessmodels.OverrideStruct{
130+
OpenIdFeature: &openidmodels.OverrideStruct{
131+
Functions: func(originalImplementation openidmodels.RecipeInterface) openidmodels.RecipeInterface {
132+
(*originalImplementation.GetOpenIdDiscoveryConfiguration) = func(userContext *map[string]interface{}) (openidmodels.GetOpenIdDiscoveryConfigurationResponse, error) {
133+
return openidmodels.GetOpenIdDiscoveryConfigurationResponse{
134+
OK: &struct{Issuer string; Jwks_uri string}{
135+
Issuer: "your issuer",
136+
Jwks_uri: "https://your.api.domain/auth/jwt/jwks.json",
137+
},
138+
}, nil
139+
}
140+
141+
return originalImplementation
142+
},
143+
},
144+
},
145+
}),
146+
},
147+
})
148+
}
149+
```
150+
151+
#### If you used `sessionData` (not `accessTokenPayload`)
152+
153+
Related functions/prop names have changes (`sessionData` became `sessionDataFromDatabase`):
154+
155+
- Renamed `GetSessionData` to `GetSessionDataFromDatabase` to clarify that it always hits the DB
156+
- Renamed `UpdateSessionData` to `UpdateSessionDataInDatabase`
157+
- Renamed `sessionData` to `sessionDataInDatabase` in `SessionInformation` and the input to `CreateNewSession`
158+
159+
#### If you used to set `access_token_blacklisting` in the core config
160+
161+
- You should now set `CheckDatabase` to true in the verifySession params.
162+
163+
#### If you used to set `access_token_signing_key_dynamic` in the core config
164+
165+
- You should now set `useDynamicAccessTokenSigningKey` in the Session recipe config.
166+
167+
#### If you used to use standard/protected props in the access token payload root:
168+
169+
1. Update you application logic to rename those props (e.g., by adding a prefix)
170+
2. Update the session recipe config (in this example `sub` is the protected property we are updating by adding the `app` prefix):
171+
172+
Before:
173+
174+
```go
175+
func main() {
176+
supertokens.Init(supertokens.TypeInput{
177+
RecipeList: []supertokens.Recipe{
178+
session.Init(&sessmodels.TypeInput{
179+
Override: &sessmodels.OverrideStruct{
180+
Functions: func(originalImplementation sessmodels.RecipeInterface) sessmodels.RecipeInterface {
181+
originalCreateNewSession := *originalImplementation.CreateNewSession
182+
(*originalImplementation.CreateNewSession) = func(req *http.Request, res http.ResponseWriter, userID string, accessTokenPayload, sessionData map[string]interface{}, userContext supertokens.UserContext) (sessmodels.SessionContainer, error) {
183+
if accessTokenPayload == nil {
184+
accessTokenPayload = map[string]interface{}{}
185+
}
186+
187+
accessTokenPayload["sub"] = userID + "!!!"
188+
189+
return originalCreateNewSession(req, res, userID, accessTokenPayload, sessionData, userContext)
190+
}
191+
192+
return originalImplementation
193+
},
194+
},
195+
}),
196+
},
197+
})
198+
}
199+
```
200+
201+
After:
202+
203+
```go
204+
func main() {
205+
supertokens.Init(supertokens.TypeInput{
206+
RecipeList: []supertokens.Recipe{
207+
session.Init(&sessmodels.TypeInput{
208+
Override: &sessmodels.OverrideStruct{
209+
Functions: func(originalImplementation sessmodels.RecipeInterface) sessmodels.RecipeInterface {
210+
originalGetSession := *originalImplementation.GetSession
211+
212+
(*originalImplementation.GetSession) = func(req *http.Request, res http.ResponseWriter, options *sessmodels.VerifySessionOptions, userContext *map[string]interface{}) (*sessmodels.TypeSessionContainer, error) {
213+
result, err := originalGetSession(req, res, options, userContext)
214+
215+
if result != nil {
216+
originalPayload := result.GetAccessTokenPayload()
217+
218+
if originalPayload["appSub"] == nil {
219+
result.MergeIntoAccessTokenPayload(map[string]interface{}{
220+
"appSub": originalPayload["sub"],
221+
"sub": nil,
222+
})
223+
}
224+
}
225+
226+
return result, err
227+
}
228+
229+
originalCreateNewSession := *originalImplementation.CreateNewSession
230+
231+
232+
(*originalImplementation.CreateNewSession) = func(req *http.Request, res http.ResponseWriter, userID string, accessTokenPayload map[string]interface{}, sessionData map[string]interface{}, userContext *map[string]interface{}) (*sessmodels.TypeSessionContainer, error) {
233+
if accessTokenPayload == nil {
234+
accessTokenPayload = map[string]interface{}{}
235+
}
236+
237+
accessTokenPayload["sub"] = userID + "!!!"
238+
239+
return originalCreateNewSession(req, res, userID, accessTokenPayload, sessionData, userContext)
240+
}
241+
242+
return originalImplementation
243+
},
244+
},
245+
}),
246+
},
247+
})
248+
}
249+
```
250+
251+
#### If you added an override for `CreateNewSession`/`RefreshSession`/`GetSession`:
252+
253+
This example uses `GetSession`, but the changes required for the other ones are very similar. Before:
254+
255+
```go
256+
session.Init(&sessmodels.TypeInput{
257+
Override: &sessmodels.OverrideStruct{
258+
Functions: func(originalImplementation sessmodels.RecipeInterface) sessmodels.RecipeInterface {
259+
originalGetSession := *originalImplementation.GetSession
260+
261+
newGetSession := func(req *http.Request, res http.ResponseWriter, options *sessmodels.VerifySessionOptions, userContext supertokens.UserContext) (sessmodels.SessionContainer, error) {
262+
response, err := originalGetSession(req, res, options, userContext)
263+
264+
if err != nil {
265+
return nil, err
266+
}
267+
268+
return response, nil
269+
}
270+
271+
*originalImplementation.GetSession = newGetSession
272+
return originalImplementation
273+
},
274+
},
275+
})
276+
```
277+
278+
After:
279+
280+
```go
281+
session.Init(&sessmodels.TypeInput{
282+
Override: &sessmodels.OverrideStruct{
283+
Functions: func(originalImplementation sessmodels.RecipeInterface) sessmodels.RecipeInterface {
284+
originalGetSession := *originalImplementation.GetSession
285+
286+
newGetSession := func(accessToken string, antiCSRFToken *string, options *sessmodels.VerifySessionOptions, userContext supertokens.UserContext) (sessmodels.SessionContainer, error) {
287+
defaultUserContext := (*userContext)["_default"].(map[string]interface{})
288+
request := defaultUserContext["request"]
289+
290+
print(request)
291+
292+
response, err := originalGetSession(accessToken, antiCSRFToken, options, userContext)
293+
if err != nil {
294+
return nil, err
295+
}
296+
297+
return response, nil
298+
}
299+
300+
*originalImplementation.GetSession = newGetSession
301+
302+
return originalImplementation
303+
},
304+
},
305+
}),
306+
```
307+
13308
## [0.11.0] - 2023-04-28
14309
- Added missing arguments in `GetUsersNewestFirst` and `GetUsersOldestFirst`
15310

coreDriverInterfaceSupported.json

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,6 @@
11
{
22
"_comment": "contains a list of core-driver interfaces branch names that this core supports",
33
"versions": [
4-
"2.8",
5-
"2.9",
6-
"2.10",
7-
"2.11",
8-
"2.12",
9-
"2.13",
10-
"2.14",
11-
"2.15",
12-
"2.16",
13-
"2.17",
14-
"2.18",
15-
"2.19",
16-
"2.20"
4+
"2.21"
175
]
186
}

examples/go.sum

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
4949
github.com/ClickHouse/clickhouse-go v1.5.4/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
5050
github.com/ClickHouse/clickhouse-go/v2 v2.2.0/go.mod h1:8f2XZUi7XoeU+uPIytSi1cvx8fmJxi7vIgqpvYTF1+o=
5151
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
52-
github.com/MicahParks/keyfunc v1.0.0 h1:O9VAkG6q/LqX4eS+HuIsW9KfC/Luh2NBQr9v4NiwHU0=
53-
github.com/MicahParks/keyfunc v1.0.0/go.mod h1:R8RZa27qn+5cHTfYLJ9/+7aSb5JIdz7cl0XFo0o4muo=
52+
github.com/MicahParks/keyfunc v1.9.0 h1:lhKd5xrFHLNOWrDc4Tyb/Q1AJ4LCzQ48GVJyVIID3+o=
53+
github.com/MicahParks/keyfunc v1.9.0/go.mod h1:IdnCilugA0O/99dW+/MkvlyrsX8+L8+x95xuVNtM5jw=
5454
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
5555
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
5656
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
@@ -198,7 +198,6 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
198198
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
199199
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
200200
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
201-
github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
202201
github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=
203202
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
204203
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=

examples/with-chi-oso/service/service.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func (s *service) Sessioninfo(w http.ResponseWriter, r *http.Request) {
4848
w.Write([]byte("no session found"))
4949
return
5050
}
51-
sessionData, err := sessionContainer.GetSessionData()
51+
sessionData, err := sessionContainer.GetSessionDataInDatabase()
5252
if err != nil {
5353
err = supertokens.ErrorHandler(err, r, w)
5454
if err != nil {

examples/with-chi/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ func sessioninfo(w http.ResponseWriter, r *http.Request) {
133133
w.Write([]byte("no session found"))
134134
return
135135
}
136-
sessionData, err := sessionContainer.GetSessionData()
136+
sessionData, err := sessionContainer.GetSessionDataInDatabase()
137137
if err != nil {
138138
err = supertokens.ErrorHandler(err, r, w)
139139
if err != nil {

examples/with-fiber/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ func sessioninfo(c *fiber.Ctx) error {
146146
if sessionContainer == nil {
147147
return c.Status(500).JSON("no session found")
148148
}
149-
sessionData, err := sessionContainer.GetSessionData()
149+
sessionData, err := sessionContainer.GetSessionDataInDatabase()
150150
if err != nil {
151151
return c.Status(500).JSON(err.Error())
152152
}

examples/with-gin/server/server.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ func sessioninfo(c *gin.Context) {
6666
c.JSON(500, "no session found")
6767
return
6868
}
69-
sessionData, err := sessionContainer.GetSessionData()
69+
sessionData, err := sessionContainer.GetSessionDataInDatabase()
7070
if err != nil {
7171
err = supertokens.ErrorHandler(err, c.Request, c.Writer)
7272
if err != nil {

examples/with-go-zero/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ func sessioninfo(w http.ResponseWriter, r *http.Request) {
137137
w.Write([]byte("no session found"))
138138
return
139139
}
140-
sessionData, err := sessionContainer.GetSessionData()
140+
sessionData, err := sessionContainer.GetSessionDataInDatabase()
141141
if err != nil {
142142
err = supertokens.ErrorHandler(err, r, w)
143143
if err != nil {

examples/with-http/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ func sessioninfo(w http.ResponseWriter, r *http.Request) {
133133
w.Write([]byte("no session found"))
134134
return
135135
}
136-
sessionData, err := sessionContainer.GetSessionData()
136+
sessionData, err := sessionContainer.GetSessionDataInDatabase()
137137
if err != nil {
138138
err = supertokens.ErrorHandler(err, r, w)
139139
if err != nil {

0 commit comments

Comments
 (0)