Skip to content

Commit 6886b44

Browse files
committed
fix: enforce secure refresh token delivery and update documentation
- Remove support for refresh tokens sent via query parameters to improve security and prevent token leakage - Update documentation and code comments to clarify that refresh tokens should only be delivered via cookies, JSON body, or form data - Adjust tests to ensure query parameters containing refresh tokens are ignored and do not take precedence over secure delivery methods - Add explicit security notice to documentation explaining risks of query parameter usage for token transmission Signed-off-by: appleboy <[email protected]>
1 parent b196f65 commit 6886b44

File tree

5 files changed

+132
-131
lines changed

5 files changed

+132
-131
lines changed

README.md

Lines changed: 42 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -424,38 +424,38 @@ Advanced authorization patterns including:
424424

425425
The `GinJWTMiddleware` struct provides the following configuration options:
426426

427-
| Option | Type | Required | Default | Description |
428-
| ---------------------- | ------------------------------------------------ | -------- | ------------------------ | ------------------------------------------------------------- |
429-
| Realm | `string` | No | `"gin jwt"` | Realm name to display to the user. |
430-
| SigningAlgorithm | `string` | No | `"HS256"` | Signing algorithm (HS256, HS384, HS512, RS256, RS384, RS512). |
431-
| Key | `[]byte` | Yes | - | Secret key used for signing. |
432-
| Timeout | `time.Duration` | No | `time.Hour` | Duration that a jwt token is valid. |
433-
| MaxRefresh | `time.Duration` | No | `0` | Duration that a refresh token is valid. |
434-
| Authenticator | `func(c *gin.Context) (any, error)` | Yes | - | Callback to authenticate the user. Returns user data. |
435-
| Authorizer | `func(c *gin.Context, data any) bool` | No | `true` | Callback to authorize the authenticated user. |
436-
| PayloadFunc | `func(data any) jwt.MapClaims` | No | - | Callback to add additional payload data to the token. |
437-
| Unauthorized | `func(c *gin.Context, code int, message string)` | No | - | Callback for unauthorized requests. |
438-
| LoginResponse | `func(c *gin.Context, token *core.Token)` | No | - | Callback for successful login response. |
439-
| LogoutResponse | `func(c *gin.Context)` | No | - | Callback for successful logout response. |
440-
| RefreshResponse | `func(c *gin.Context, token *core.Token)` | No | - | Callback for successful refresh response. |
441-
| IdentityHandler | `func(*gin.Context) any` | No | - | Callback to retrieve identity from claims. |
442-
| IdentityKey | `string` | No | `"identity"` | Key used to store identity in claims. |
443-
| TokenLookup | `string` | No | `"header:Authorization"` | Source to extract token from (header, query, cookie). |
444-
| TokenHeadName | `string` | No | `"Bearer"` | Header name prefix. |
445-
| TimeFunc | `func() time.Time` | No | `time.Now` | Function to provide current time. |
446-
| PrivKeyFile | `string` | No | - | Path to private key file (for RS algorithms). |
447-
| PubKeyFile | `string` | No | - | Path to public key file (for RS algorithms). |
448-
| SendCookie | `bool` | No | `false` | Whether to send token as a cookie. |
449-
| CookieMaxAge | `time.Duration` | No | `Timeout` | Duration that the cookie is valid. |
427+
| Option | Type | Required | Default | Description |
428+
| ---------------------- | ------------------------------------------------ | -------- | ------------------------ | ----------------------------------------------------------------------------------------------------- |
429+
| Realm | `string` | No | `"gin jwt"` | Realm name to display to the user. |
430+
| SigningAlgorithm | `string` | No | `"HS256"` | Signing algorithm (HS256, HS384, HS512, RS256, RS384, RS512). |
431+
| Key | `[]byte` | Yes | - | Secret key used for signing. |
432+
| Timeout | `time.Duration` | No | `time.Hour` | Duration that a jwt token is valid. |
433+
| MaxRefresh | `time.Duration` | No | `0` | Duration that a refresh token is valid. |
434+
| Authenticator | `func(c *gin.Context) (any, error)` | Yes | - | Callback to authenticate the user. Returns user data. |
435+
| Authorizer | `func(c *gin.Context, data any) bool` | No | `true` | Callback to authorize the authenticated user. |
436+
| PayloadFunc | `func(data any) jwt.MapClaims` | No | - | Callback to add additional payload data to the token. |
437+
| Unauthorized | `func(c *gin.Context, code int, message string)` | No | - | Callback for unauthorized requests. |
438+
| LoginResponse | `func(c *gin.Context, token *core.Token)` | No | - | Callback for successful login response. |
439+
| LogoutResponse | `func(c *gin.Context)` | No | - | Callback for successful logout response. |
440+
| RefreshResponse | `func(c *gin.Context, token *core.Token)` | No | - | Callback for successful refresh response. |
441+
| IdentityHandler | `func(*gin.Context) any` | No | - | Callback to retrieve identity from claims. |
442+
| IdentityKey | `string` | No | `"identity"` | Key used to store identity in claims. |
443+
| TokenLookup | `string` | No | `"header:Authorization"` | Source to extract token from (header, query, cookie). |
444+
| TokenHeadName | `string` | No | `"Bearer"` | Header name prefix. |
445+
| TimeFunc | `func() time.Time` | No | `time.Now` | Function to provide current time. |
446+
| PrivKeyFile | `string` | No | - | Path to private key file (for RS algorithms). |
447+
| PubKeyFile | `string` | No | - | Path to public key file (for RS algorithms). |
448+
| SendCookie | `bool` | No | `false` | Whether to send token as a cookie. |
449+
| CookieMaxAge | `time.Duration` | No | `Timeout` | Duration that the cookie is valid. |
450450
| SecureCookie | `bool` | No | `false` | Whether to use secure cookies for access token (HTTPS only). Refresh token cookies are always secure. |
451-
| CookieHTTPOnly | `bool` | No | `false` | Whether to use HTTPOnly cookies. |
452-
| CookieDomain | `string` | No | - | Domain for the cookie. |
453-
| CookieName | `string` | No | `"jwt"` | Name of the cookie. |
454-
| RefreshTokenCookieName | `string` | No | `"refresh_token"` | Name of the refresh token cookie. |
455-
| CookieSameSite | `http.SameSite` | No | - | SameSite attribute for the cookie. |
456-
| SendAuthorization | `bool` | No | `false` | Whether to return authorization header for every request. |
457-
| DisabledAbort | `bool` | No | `false` | Disable abort() of context. |
458-
| ParseOptions | `[]jwt.ParserOption` | No | - | Options for parsing the JWT. |
451+
| CookieHTTPOnly | `bool` | No | `false` | Whether to use HTTPOnly cookies. |
452+
| CookieDomain | `string` | No | - | Domain for the cookie. |
453+
| CookieName | `string` | No | `"jwt"` | Name of the cookie. |
454+
| RefreshTokenCookieName | `string` | No | `"refresh_token"` | Name of the refresh token cookie. |
455+
| CookieSameSite | `http.SameSite` | No | - | SameSite attribute for the cookie. |
456+
| SendAuthorization | `bool` | No | `false` | Whether to return authorization header for every request. |
457+
| DisabledAbort | `bool` | No | `false` | Disable abort() of context. |
458+
| ParseOptions | `[]jwt.ParserOption` | No | - | Options for parsing the JWT. |
459459

460460
---
461461

@@ -1241,21 +1241,20 @@ Using RFC 6749 compliant refresh tokens (default behavior):
12411241
# First login to get refresh token
12421242
http -v --json POST localhost:8000/login username=admin password=admin
12431243

1244-
# Method 1: Use refresh token from response (manual)
1245-
http -v --form POST localhost:8000/refresh refresh_token=your_refresh_token_here
1246-
1247-
# Method 2: With cookies enabled (automatic - recommended for browsers)
1244+
# Method 1: With cookies enabled (automatic - recommended for browsers)
12481245
# The refresh token cookie is automatically sent, no need to manually include it
12491246
http -v POST localhost:8000/refresh --session=./session.json
12501247

1251-
# Method 3: Send refresh token in JSON body
1248+
# Method 2: Send refresh token in JSON body
12521249
http -v --json POST localhost:8000/refresh refresh_token=your_refresh_token_here
12531250

1254-
# Method 4: Send refresh token as query parameter
1255-
http -v POST localhost:8000/refresh?refresh_token=your_refresh_token_here
1251+
# Method 3: Use refresh token from response via form data
1252+
http -v --form POST localhost:8000/refresh refresh_token=your_refresh_token_here
12561253
```
12571254

1258-
**Note**: When `SendCookie` is enabled, refresh tokens are automatically stored in httpOnly cookies. Browser-based applications can simply call the refresh endpoint without manually including the token - it's handled automatically by the cookie mechanism.
1255+
**Security Note**: When `SendCookie` is enabled, refresh tokens are automatically stored in httpOnly cookies. Browser-based applications can simply call the refresh endpoint without manually including the token - it's handled automatically by the cookie mechanism.
1256+
1257+
**Important**: Query parameters are NOT supported for refresh tokens as they expose tokens in server logs, proxy logs, browser history, and Referer headers. Use cookies (recommended), JSON body, or form data instead.
12591258

12601259
![Refresh screenshot](screenshot/refresh.png)
12611260

@@ -1664,8 +1663,9 @@ This is a provided function to be called on any refresh token endpoint. The hand
16641663

16651664
1. **Cookie** (most common for browser-based apps): `RefreshTokenCookieName` cookie (default: `"refresh_token"`)
16661665
2. **POST Form**: `refresh_token` form field
1667-
3. **Query Parameter**: `refresh_token` query string parameter
1668-
4. **JSON Body**: `refresh_token` field in request body
1666+
3. **JSON Body**: `refresh_token` field in request body
1667+
1668+
**Security Note**: Query parameters are NOT supported for refresh tokens to prevent token leakage through server logs, proxy logs, browser history, and Referer headers. Only secure delivery methods are supported.
16691669

16701670
If the refresh token is valid and not expired, the handler will:
16711671

@@ -1674,7 +1674,7 @@ If the refresh token is valid and not expired, the handler will:
16741674
- Set both tokens as cookies (if `SendCookie` is enabled)
16751675
- Pass the new tokens into `RefreshResponse`
16761676

1677-
This follows OAuth 2.0 security best practices by rotating refresh tokens and supporting multiple delivery methods.
1677+
This follows OAuth 2.0 security best practices by rotating refresh tokens and supporting multiple secure delivery methods.
16781678

16791679
**Cookie-Based Authentication**: When using cookies (recommended for browser apps), the refresh token is automatically sent with the request, so you don't need to manually include it. Simply call the refresh endpoint and the middleware handles everything.
16801680

0 commit comments

Comments
 (0)