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
func (s *Service) MakeRefreshToken(clientID, scope, subject string) (string, error) {
return s.generateRefreshToken(clientID, scope, subject)
func (s *Service) MakeRefreshToken(clientID, scope, subject string, expiresIn ...int) (string, error) {
return s.generateRefreshToken(clientID, scope, subject, expiresIn...)
}
// 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
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)
if err != nil {
return "", err
}
// Store refresh token with metadata
err = s.storeRefreshTokenWithScope(refreshToken, clientID, scope, subject)
err = s.storeRefreshTokenWithScope(refreshToken, clientID, scope, subject, expiresIn...)
if err != nil {
return "", err
}
@ -529,7 +529,7 @@ func (s *Service) storeRefreshToken(refreshToken, clientID string) error {
}
// 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{}{
"client_id": clientID,
"scope": scope,
@ -538,7 +538,12 @@ func (s *Service) storeRefreshTokenWithScope(refreshToken, clientID, scope, subj
"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

View file

@ -214,7 +214,18 @@ func authback(c *gin.Context) {
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

View file

@ -119,17 +119,18 @@ func LoginByUserID(userid string, ip string) (*LoginResponse, error) {
}
// 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 {
return nil, err
}
return &LoginResponse{
AccessToken: accessToken,
IDToken: oidcToken,
RefreshToken: refreshToken,
ExpiresIn: yaoClientConfig.ExpiresIn,
TokenType: "Bearer",
Scope: strings.Join(scopes, " "),
AccessToken: accessToken,
IDToken: oidcToken,
RefreshToken: refreshToken,
ExpiresIn: yaoClientConfig.ExpiresIn,
RefreshTokenExpiresIn: yaoClientConfig.RefreshTokenExpiresIn,
TokenType: "Bearer",
Scope: strings.Join(scopes, " "),
}, nil
}

View file

@ -156,7 +156,8 @@ func registerClient(clientID string) (*YaoClientConfig, error) {
var clientConfig *YaoClientConfig = &YaoClientConfig{}
clientConfig.ClientID = response.ClientID
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"}
return clientConfig, nil
}

View file

@ -66,10 +66,11 @@ type RegisterConfig struct {
// YaoClientConfig represents the Yao OpenAPI Client config
type YaoClientConfig struct {
ClientID string `json:"client_id,omitempty"`
ClientSecret string `json:"client_secret,omitempty"`
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
ClientID string `json:"client_id,omitempty"`
ClientSecret string `json:"client_secret,omitempty"`
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
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
@ -161,12 +162,13 @@ type OIDCAddress = oauthtypes.OIDCAddress
// LoginResponse represents the response for login
type LoginResponse struct {
AccessToken string `json:"access_token"`
IDToken string `json:"id_token,omitempty"`
RefreshToken string `json:"refresh_token,omitempty"`
ExpiresIn int `json:"expires_in,omitempty"`
TokenType string `json:"token_type,omitempty"`
Scope string `json:"scope,omitempty"`
AccessToken string `json:"access_token"`
IDToken string `json:"id_token,omitempty"`
RefreshToken string `json:"refresh_token,omitempty"`
ExpiresIn int `json:"expires_in,omitempty"`
RefreshTokenExpiresIn int `json:"refresh_token_expires_in,omitempty"`
TokenType string `json:"token_type,omitempty"`
Scope string `json:"scope,omitempty"`
}
// Built-in preset mapping types