Skip to content

Commit 783cd64

Browse files
jonasfranzlunny
authored andcommitted
Add option to disable refresh token invalidation (#6584)
* Add option to disable refresh token invalidation Signed-off-by: Jonas Franz <[email protected]> * Add integration tests and remove wrong todos Signed-off-by: Jonas Franz <[email protected]> * Fix typo Signed-off-by: Jonas Franz <[email protected]> * Fix tests and add documentation Signed-off-by: Jonas Franz <[email protected]>
1 parent 3ff0a12 commit 783cd64

File tree

6 files changed

+57
-11
lines changed

6 files changed

+57
-11
lines changed

custom/conf/app.ini.sample

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -680,6 +680,8 @@ ENABLED = true
680680
ACCESS_TOKEN_EXPIRATION_TIME=3600
681681
; Lifetime of an OAuth2 access token in hours
682682
REFRESH_TOKEN_EXPIRATION_TIME=730
683+
; Check if refresh token got already used
684+
INVALIDATE_REFRESH_TOKENS=false
683685
; OAuth2 authentication secret for access and refresh tokens, change this a unique string.
684686
JWT_SECRET=Bk0yK7Y9g_p56v86KaHqjSbxvNvu3SbKoOdOt2ZcXvU
685687

docs/content/doc/advanced/config-cheat-sheet.en-us.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,7 @@ NB: You must `REDIRECT_MACARON_LOG` and have `DISABLE_ROUTER_LOG` set to `false`
409409
- `ENABLED`: **true**: Enables OAuth2 provider.
410410
- `ACCESS_TOKEN_EXPIRATION_TIME`: **3600**: Lifetime of an OAuth2 access token in seconds
411411
- `REFRESH_TOKEN_EXPIRATION_TIME`: **730**: Lifetime of an OAuth2 access token in hours
412+
- `INVALIDATE_REFRESH_TOKEN`: **false**: Check if refresh token got already used
412413
- `JWT_SECRET`: **\<empty\>**: OAuth2 authentication secret for access and refresh tokens, change this a unique string.
413414

414415
## i18n (`i18n`)

integrations/oauth_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"encoding/json"
99
"testing"
1010

11+
"code.gitea.io/gitea/modules/setting"
12+
1113
"github.com/stretchr/testify/assert"
1214
)
1315

@@ -177,3 +179,42 @@ func TestAccessTokenExchangeWithBasicAuth(t *testing.T) {
177179
})
178180
resp = MakeRequest(t, req, 400)
179181
}
182+
183+
func TestRefreshTokenInvalidation(t *testing.T) {
184+
prepareTestEnv(t)
185+
req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
186+
"grant_type": "authorization_code",
187+
"client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
188+
"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
189+
"redirect_uri": "a",
190+
"code": "authcode",
191+
"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", // test PKCE additionally
192+
})
193+
resp := MakeRequest(t, req, 200)
194+
type response struct {
195+
AccessToken string `json:"access_token"`
196+
TokenType string `json:"token_type"`
197+
ExpiresIn int64 `json:"expires_in"`
198+
RefreshToken string `json:"refresh_token"`
199+
}
200+
parsed := new(response)
201+
assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsed))
202+
203+
// test without invalidation
204+
setting.OAuth2.InvalidateRefreshTokens = false
205+
206+
refreshReq := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
207+
"grant_type": "refresh_token",
208+
"client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
209+
"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
210+
"redirect_uri": "a",
211+
"refresh_token": parsed.RefreshToken,
212+
})
213+
MakeRequest(t, refreshReq, 200)
214+
MakeRequest(t, refreshReq, 200)
215+
216+
// test with invalidation
217+
setting.OAuth2.InvalidateRefreshTokens = true
218+
MakeRequest(t, refreshReq, 200)
219+
MakeRequest(t, refreshReq, 400)
220+
}

modules/auth/user_form.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,6 @@ type AccessTokenForm struct {
172172
ClientID string
173173
ClientSecret string
174174
RedirectURI string
175-
// TODO Specify authentication code length to prevent against birthday attacks
176175
Code string
177176
RefreshToken string
178177

modules/setting/setting.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,12 +304,14 @@ var (
304304
Enable bool
305305
AccessTokenExpirationTime int64
306306
RefreshTokenExpirationTime int64
307+
InvalidateRefreshTokens bool
307308
JWTSecretBytes []byte `ini:"-"`
308309
JWTSecretBase64 string `ini:"JWT_SECRET"`
309310
}{
310311
Enable: true,
311312
AccessTokenExpirationTime: 3600,
312313
RefreshTokenExpirationTime: 730,
314+
InvalidateRefreshTokens: false,
313315
}
314316

315317
U2F = struct {

routers/user/oauth.go

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -102,18 +102,19 @@ const (
102102

103103
// AccessTokenResponse represents a successful access token response
104104
type AccessTokenResponse struct {
105-
AccessToken string `json:"access_token"`
106-
TokenType TokenType `json:"token_type"`
107-
ExpiresIn int64 `json:"expires_in"`
108-
// TODO implement RefreshToken
109-
RefreshToken string `json:"refresh_token"`
105+
AccessToken string `json:"access_token"`
106+
TokenType TokenType `json:"token_type"`
107+
ExpiresIn int64 `json:"expires_in"`
108+
RefreshToken string `json:"refresh_token"`
110109
}
111110

112111
func newAccessTokenResponse(grant *models.OAuth2Grant) (*AccessTokenResponse, *AccessTokenError) {
113-
if err := grant.IncreaseCounter(); err != nil {
114-
return nil, &AccessTokenError{
115-
ErrorCode: AccessTokenErrorCodeInvalidGrant,
116-
ErrorDescription: "cannot increase the grant counter",
112+
if setting.OAuth2.InvalidateRefreshTokens {
113+
if err := grant.IncreaseCounter(); err != nil {
114+
return nil, &AccessTokenError{
115+
ErrorCode: AccessTokenErrorCodeInvalidGrant,
116+
ErrorDescription: "cannot increase the grant counter",
117+
}
117118
}
118119
}
119120
// generate access token to access the API
@@ -366,7 +367,7 @@ func handleRefreshToken(ctx *context.Context, form auth.AccessTokenForm) {
366367
}
367368

368369
// check if token got already used
369-
if grant.Counter != token.Counter || token.Counter == 0 {
370+
if setting.OAuth2.InvalidateRefreshTokens && (grant.Counter != token.Counter || token.Counter == 0) {
370371
handleAccessTokenError(ctx, AccessTokenError{
371372
ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
372373
ErrorDescription: "token was already used",

0 commit comments

Comments
 (0)