From 61ffa3ddb716081873d9969dcbdebc34cf092ab4 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Wed, 16 Jul 2025 17:33:25 +0800 Subject: [PATCH 1/2] feat: adopt OAuth2-style token fields in authentication responses - Replace token response fields with OAuth2-style fields: access_token, token_type, expires_in, refresh_token, and scope - Add a test assertion to verify that refresh_token is present and not empty in the response fix https://github.com/appleboy/gin-jwt/issues/346 Signed-off-by: Bo-Yi Wu --- auth_jwt.go | 16 ++++++++++------ auth_jwt_test.go | 2 ++ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/auth_jwt.go b/auth_jwt.go index fabb6d9..adae91f 100644 --- a/auth_jwt.go +++ b/auth_jwt.go @@ -357,9 +357,11 @@ func (mw *GinJWTMiddleware) MiddlewareInit() error { if mw.LoginResponse == nil { mw.LoginResponse = func(c *gin.Context, code int, token string, expire time.Time) { c.JSON(http.StatusOK, gin.H{ - "code": http.StatusOK, - "token": token, - "expire": expire.Format(time.RFC3339), + "access_token": token, + "token_type": "Bearer", + "expires_in": int(time.Until(expire).Seconds()), + "refresh_token": "", + "scope": "create", }) } } @@ -375,9 +377,11 @@ func (mw *GinJWTMiddleware) MiddlewareInit() error { if mw.RefreshResponse == nil { mw.RefreshResponse = func(c *gin.Context, code int, token string, expire time.Time) { c.JSON(http.StatusOK, gin.H{ - "code": http.StatusOK, - "token": token, - "expire": expire.Format(time.RFC3339), + "access_token": token, + "token_type": "Bearer", + "expires_in": int(time.Until(expire).Seconds()), + "refresh_token": "", + "scope": "create", }) } } diff --git a/auth_jwt_test.go b/auth_jwt_test.go index fa4036f..28afec1 100644 --- a/auth_jwt_test.go +++ b/auth_jwt_test.go @@ -527,9 +527,11 @@ func TestRefreshHandlerRS256(t *testing.T) { Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { message := gjson.Get(r.Body.String(), "message") cookie := gjson.Get(r.Body.String(), "cookie") + refreshToken := gjson.Get(r.Body.String(), "refresh_token") assert.Equal(t, "refresh successfully", message.String()) assert.Equal(t, http.StatusOK, r.Code) assert.Equal(t, makeTokenString("RS256", "admin"), cookie.String()) + assert.NotEmpty(t, refreshToken.String(), "refresh_token should not be empty") }) } From 0ac235af972423760a1d6322c14ad20d58b0ce4b Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Wed, 16 Jul 2025 18:08:36 +0800 Subject: [PATCH 2/2] fix: improve refresh token handling and update related tests - Add error handling and response for refresh token generation failures during middleware initialization - Return the actual refresh token in the response instead of an empty string - Update test claims to include standard JWT fields with current timestamps - Change test code to extract access_token instead of token from responses - Remove redundant test assertions for refresh_token presence Signed-off-by: Bo-Yi Wu --- auth_jwt.go | 20 ++++++++++++++++++-- auth_jwt_test.go | 15 ++++++++++----- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/auth_jwt.go b/auth_jwt.go index adae91f..07ef7ee 100644 --- a/auth_jwt.go +++ b/auth_jwt.go @@ -356,11 +356,19 @@ func (mw *GinJWTMiddleware) MiddlewareInit() error { if mw.LoginResponse == nil { mw.LoginResponse = func(c *gin.Context, code int, token string, expire time.Time) { + refreshToken, _, err := mw.RefreshToken(c) + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{ + "code": http.StatusUnauthorized, + "message": mw.HTTPStatusMessageFunc(err, c), + }) + return + } c.JSON(http.StatusOK, gin.H{ "access_token": token, "token_type": "Bearer", "expires_in": int(time.Until(expire).Seconds()), - "refresh_token": "", + "refresh_token": refreshToken, "scope": "create", }) } @@ -376,11 +384,19 @@ func (mw *GinJWTMiddleware) MiddlewareInit() error { if mw.RefreshResponse == nil { mw.RefreshResponse = func(c *gin.Context, code int, token string, expire time.Time) { + refreshToken, _, err := mw.RefreshToken(c) + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{ + "code": http.StatusUnauthorized, + "message": mw.HTTPStatusMessageFunc(err, c), + }) + return + } c.JSON(http.StatusOK, gin.H{ "access_token": token, "token_type": "Bearer", "expires_in": int(time.Until(expire).Seconds()), - "refresh_token": "", + "refresh_token": refreshToken, "scope": "create", }) } diff --git a/auth_jwt_test.go b/auth_jwt_test.go index 28afec1..384dd02 100644 --- a/auth_jwt_test.go +++ b/auth_jwt_test.go @@ -527,11 +527,9 @@ func TestRefreshHandlerRS256(t *testing.T) { Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { message := gjson.Get(r.Body.String(), "message") cookie := gjson.Get(r.Body.String(), "cookie") - refreshToken := gjson.Get(r.Body.String(), "refresh_token") assert.Equal(t, "refresh successfully", message.String()) assert.Equal(t, http.StatusOK, r.Code) assert.Equal(t, makeTokenString("RS256", "admin"), cookie.String()) - assert.NotEmpty(t, refreshToken.String(), "refresh_token should not be empty") }) } @@ -720,7 +718,14 @@ func TestClaimsDuringAuthorization(t *testing.T) { testkey = "" } // Set custom claim, to be checked in Authorizator method - return MapClaims{"identity": data.(string), "testkey": testkey, "exp": 0} + now := time.Now() + return MapClaims{ + "identity": data.(string), + "testkey": testkey, + "exp": now.Add(time.Hour).Unix(), + "iat": now.Unix(), + "nbf": now.Unix(), + } }, Authenticator: func(c *gin.Context) (interface{}, error) { var loginVals Login @@ -782,7 +787,7 @@ func TestClaimsDuringAuthorization(t *testing.T) { "password": "admin", }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { - token := gjson.Get(r.Body.String(), "token") + token := gjson.Get(r.Body.String(), "access_token") userToken = token.String() assert.Equal(t, http.StatusOK, r.Code) }) @@ -801,7 +806,7 @@ func TestClaimsDuringAuthorization(t *testing.T) { "password": "test", }). Run(handler, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { - token := gjson.Get(r.Body.String(), "token") + token := gjson.Get(r.Body.String(), "access_token") userToken = token.String() assert.Equal(t, http.StatusOK, r.Code) })