Enhance refresh token functionality and response handling

- Updated MakeRefreshToken and generateRefreshToken methods to accept an optional expiresIn parameter for customizable token expiration.
- Modified storeRefreshTokenWithScope to handle dynamic expiration times based on the provided parameter.
- Enhanced the authback function to send cookies with access and refresh tokens, including their respective expiration times.
- Updated LoginResponse structure to include RefreshTokenExpiresIn for better client-side management of token lifetimes.
- Adjusted client configuration to set default refresh token expiration duration.
This commit is contained in:
Max 2025-08-04 16:52:57 +08:00
parent c1b28f3b69
commit 5657c75201
5 changed files with 45 additions and 25 deletions

View file

@ -253,8 +253,8 @@ func (s *Service) MakeAccessToken(clientID, scope, subject string, expiresIn int
} }
// MakeRefreshToken generates a new refresh token with specific parameters and stores it // MakeRefreshToken generates a new refresh token with specific parameters and stores it
func (s *Service) MakeRefreshToken(clientID, scope, subject string) (string, error) { func (s *Service) MakeRefreshToken(clientID, scope, subject string, expiresIn ...int) (string, error) {
return s.generateRefreshToken(clientID, scope, subject) return s.generateRefreshToken(clientID, scope, subject, expiresIn...)
} }
// Subject converts a userID to a subject using NanoID fingerprint // Subject converts a userID to a subject using NanoID fingerprint
@ -415,14 +415,14 @@ func (s *Service) revokeAccessToken(accessToken string) error {
} }
// generateRefreshToken generates and stores a new refresh token with scope and subject // generateRefreshToken generates and stores a new refresh token with scope and subject
func (s *Service) generateRefreshToken(clientID, scope, subject string) (string, error) { func (s *Service) generateRefreshToken(clientID, scope, subject string, expiresIn ...int) (string, error) {
refreshToken, err := s.generateToken("rfk", clientID) refreshToken, err := s.generateToken("rfk", clientID)
if err != nil { if err != nil {
return "", err return "", err
} }
// Store refresh token with metadata // Store refresh token with metadata
err = s.storeRefreshTokenWithScope(refreshToken, clientID, scope, subject) err = s.storeRefreshTokenWithScope(refreshToken, clientID, scope, subject, expiresIn...)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -529,7 +529,7 @@ func (s *Service) storeRefreshToken(refreshToken, clientID string) error {
} }
// storeRefreshTokenWithScope stores refresh token with metadata including scope and subject // storeRefreshTokenWithScope stores refresh token with metadata including scope and subject
func (s *Service) storeRefreshTokenWithScope(refreshToken, clientID, scope, subject string) error { func (s *Service) storeRefreshTokenWithScope(refreshToken, clientID, scope, subject string, expiresIn ...int) error {
tokenData := map[string]interface{}{ tokenData := map[string]interface{}{
"client_id": clientID, "client_id": clientID,
"scope": scope, "scope": scope,
@ -538,7 +538,12 @@ func (s *Service) storeRefreshTokenWithScope(refreshToken, clientID, scope, subj
"issued_at": time.Now().Unix(), "issued_at": time.Now().Unix(),
} }
return s.store.Set(s.refreshTokenKey(refreshToken), tokenData, s.config.Token.RefreshTokenLifetime) expires := s.config.Token.RefreshTokenLifetime
if len(expiresIn) > 0 && expiresIn[0] > 0 {
expires = time.Duration(expiresIn[0]) * time.Second
}
return s.store.Set(s.refreshTokenKey(refreshToken), tokenData, expires)
} }
// getRefreshTokenData retrieves refresh token data // getRefreshTokenData retrieves refresh token data

View file

@ -214,7 +214,18 @@ func authback(c *gin.Context) {
return return
} }
response.RespondWithSuccess(c, response.StatusOK, loginResponse) // Authorize Cookie
accessToken := fmt.Sprintf("%s %s", loginResponse.TokenType, loginResponse.AccessToken)
refreshToken := fmt.Sprintf("%s %s", loginResponse.TokenType, loginResponse.RefreshToken)
// Send Cookie
expires := time.Now().Add(time.Duration(loginResponse.ExpiresIn) * time.Second)
refreshExpires := time.Now().Add(time.Duration(loginResponse.RefreshTokenExpiresIn) * time.Second)
response.SendAccessTokenCookieWithExpiry(c, accessToken, expires)
response.SendRefreshTokenCookieWithExpiry(c, refreshToken, refreshExpires)
// Send IDToken to the client
response.RespondWithSuccess(c, response.StatusOK, map[string]interface{}{"id_token": loginResponse.IDToken})
} }
// getOAuthAuthorizationURL generates OAuth authorization URL for a provider // getOAuthAuthorizationURL generates OAuth authorization URL for a provider

View file

@ -119,7 +119,7 @@ func LoginByUserID(userid string, ip string) (*LoginResponse, error) {
} }
// Refresh Token // Refresh Token
refreshToken, err := oauth.OAuth.MakeRefreshToken(yaoClientConfig.ClientID, strings.Join(scopes, " "), subject) refreshToken, err := oauth.OAuth.MakeRefreshToken(yaoClientConfig.ClientID, strings.Join(scopes, " "), subject, yaoClientConfig.RefreshTokenExpiresIn)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -129,6 +129,7 @@ func LoginByUserID(userid string, ip string) (*LoginResponse, error) {
IDToken: oidcToken, IDToken: oidcToken,
RefreshToken: refreshToken, RefreshToken: refreshToken,
ExpiresIn: yaoClientConfig.ExpiresIn, ExpiresIn: yaoClientConfig.ExpiresIn,
RefreshTokenExpiresIn: yaoClientConfig.RefreshTokenExpiresIn,
TokenType: "Bearer", TokenType: "Bearer",
Scope: strings.Join(scopes, " "), Scope: strings.Join(scopes, " "),
}, nil }, nil

View file

@ -157,6 +157,7 @@ func registerClient(clientID string) (*YaoClientConfig, error) {
clientConfig.ClientID = response.ClientID clientConfig.ClientID = response.ClientID
clientConfig.ClientSecret = response.ClientSecret clientConfig.ClientSecret = response.ClientSecret
clientConfig.ExpiresIn = 3600 * 24 // 24 hours clientConfig.ExpiresIn = 3600 * 24 // 24 hours
clientConfig.RefreshTokenExpiresIn = 3600 * 24 * 30 // 30 days
clientConfig.Scopes = []string{"openid", "profile", "email"} clientConfig.Scopes = []string{"openid", "profile", "email"}
return clientConfig, nil return clientConfig, nil
} }

View file

@ -70,6 +70,7 @@ type YaoClientConfig struct {
ClientSecret string `json:"client_secret,omitempty"` ClientSecret string `json:"client_secret,omitempty"`
Scopes []string `json:"scopes,omitempty"` // Default scopes if not set in the provider config Scopes []string `json:"scopes,omitempty"` // Default scopes if not set in the provider config
ExpiresIn int `json:"expires_in,omitempty"` // Default expires in for the access token (optional) in seconds ExpiresIn int `json:"expires_in,omitempty"` // Default expires in for the access token (optional) in seconds
RefreshTokenExpiresIn int `json:"refresh_token_expires_in,omitempty"` // Default expires in for the refresh token (optional) in seconds
} }
// Provider represents a third party login provider // Provider represents a third party login provider
@ -165,6 +166,7 @@ type LoginResponse struct {
IDToken string `json:"id_token,omitempty"` IDToken string `json:"id_token,omitempty"`
RefreshToken string `json:"refresh_token,omitempty"` RefreshToken string `json:"refresh_token,omitempty"`
ExpiresIn int `json:"expires_in,omitempty"` ExpiresIn int `json:"expires_in,omitempty"`
RefreshTokenExpiresIn int `json:"refresh_token_expires_in,omitempty"`
TokenType string `json:"token_type,omitempty"` TokenType string `json:"token_type,omitempty"`
Scope string `json:"scope,omitempty"` Scope string `json:"scope,omitempty"`
} }