From b58b07f0acb2a77c9a20334da98d773d85da3963 Mon Sep 17 00:00:00 2001 From: Piotr Biernat Date: Mon, 20 Oct 2025 20:59:00 +0200 Subject: [PATCH] Added JWT Refresh token --- deploy/.env.dist | 7 ++-- src/internal/server/jwt_middleware.go | 5 ++- src/internal/service/auth.go | 18 +++++++---- src/internal/service/jwt.go | 46 ++++++++++++++++++++------- 4 files changed, 53 insertions(+), 23 deletions(-) diff --git a/deploy/.env.dist b/deploy/.env.dist index d3617cf..418da90 100644 --- a/deploy/.env.dist +++ b/deploy/.env.dist @@ -12,5 +12,8 @@ API_CACHE_PASSWORD=12345678 API_MONGODB_URL=mongodb://mongodb:12345678@mongo-db:27017 # EVENTBUS_URL=amqp://guest:guest@api-eventbus:5672 -JWT_SECRET_KEY=SomeFancySecretAndRandomString -JWT_TOKEN_EXPIRE_TIME=1 # hours +JWT_ACCESS_TOKEN_SECRET_KEY=SomeFancySecretAndRandomString +JWT_ACCESS_TOKEN_EXPIRE_TIME=1 # hours + +JWT_REFRESH_TOKEN_SECRET_KEY=SomeFancySecretAndRandomStringAgain +JWT_REFRESH_TOKEN_EXPIRE_TIME=7 # days diff --git a/src/internal/server/jwt_middleware.go b/src/internal/server/jwt_middleware.go index d00af85..ecf9cdf 100644 --- a/src/internal/server/jwt_middleware.go +++ b/src/internal/server/jwt_middleware.go @@ -1,8 +1,7 @@ package server import ( - "os" - + baseCnf "git.ego.freeddns.org/egommerce/go-api-pkg/config" "github.com/gofiber/fiber/v2" jwt "github.com/gofiber/jwt/v2" ) @@ -12,7 +11,7 @@ import ( func JWTProtected() func(*fiber.Ctx) error { // Create config for JWT authentication middleware. config := jwt.Config{ - SigningKey: []byte(os.Getenv("JWT_SECRET_KEY")), + SigningKey: []byte(baseCnf.GetEnv("JWT_ACCESS_TOKEN_SECRET_KEY", "FallbackAccessTokenSecret")), ContextKey: "jwt", // used in private routes ErrorHandler: jwtError, } diff --git a/src/internal/service/auth.go b/src/internal/service/auth.go index 2f93bf0..a128b7d 100644 --- a/src/internal/service/auth.go +++ b/src/internal/service/auth.go @@ -46,12 +46,13 @@ func (a *Auth) Login(login, passwd string) (string, error) { return "", ErrLoginIncorrect } - token, _ := jwtSrv.CreateToken(id) - if err = a.saveTokenToCache(token, id); err != nil { + accessToken, _ := jwtSrv.CreateAccessToken(id) + refreshToken, _ := jwtSrv.CreateRefreshToken(id) + if err = a.saveTokensToCache(accessToken, refreshToken, id); err != nil { return "", ErrUnableToCacheToken } - return token, nil + return accessToken, nil } func (a *Auth) Register(email, login, passwd string) (string, error) { @@ -70,10 +71,15 @@ func (a *Auth) Register(email, login, passwd string) (string, error) { return id, nil } -func (a *Auth) saveTokenToCache(token, id string) error { - res := a.cache.Set(context.Background(), "auth:token:"+id, token, tokenExpireTime) +func (a *Auth) saveTokensToCache(accessToken, refreshToken, id string) error { + res := a.cache.Set(context.Background(), "auth:access_token:"+id, accessToken, accessTokenExpireTime) if err := res.Err(); err != nil { - fmt.Println("failed to save token in redis: ", err.Error()) + fmt.Println("failed to save access token in redis: ", err.Error()) + } + + res = a.cache.Set(context.Background(), "auth:refresh_token:"+id, refreshToken, refreshTokenExpireTime) + if err := res.Err(); err != nil { + fmt.Println("failed to save refresh token in redis: ", err.Error()) } return nil diff --git a/src/internal/service/jwt.go b/src/internal/service/jwt.go index 4768255..7f000af 100644 --- a/src/internal/service/jwt.go +++ b/src/internal/service/jwt.go @@ -10,33 +10,55 @@ import ( ) var ( - tokenExpireTime time.Duration + accessTokenExpireTime time.Duration + refreshTokenExpireTime time.Duration ) func init() { - min, _ := strconv.Atoi(baseCnf.GetEnv("JWT_TOKEN_EXPIRE_TIME", "5")) - tokenExpireTime = time.Duration(int(time.Hour) * min) + expAccessTokenTime, _ := strconv.Atoi(baseCnf.GetEnv("JWT_ACCESS_TOKEN_EXPIRE_TIME", "5")) + accessTokenExpireTime = time.Duration(int(time.Hour) * expAccessTokenTime) // hours + + expRefreshTokenTime, _ := strconv.Atoi(baseCnf.GetEnv("JWT_REFRESH_TOKEN_EXPIRE_TIME", "7")) + refreshTokenExpireTime = time.Duration(int(time.Hour*24) * expRefreshTokenTime) // days jwtSrv = &JWT{ - tokenExpireTime, - []byte(baseCnf.GetEnv("JWT_SECRET_KEY", "B413IlIv9nKQfsMCXTE0Cteo4yHgUEfqaLfjg73sNlh")), + accessTokenExpireTime, + []byte(baseCnf.GetEnv("JWT_ACCESS_TOKEN_SECRET_KEY", "FallbackAccessTokenSecret")), + refreshTokenExpireTime, + []byte(baseCnf.GetEnv("JWT_REFRESH_TOKEN_SECRET_KEY", "FallbackRefreshTokenSecret")), } } type JWT struct { - tokenExpireTime time.Duration - tokenSecret []byte + accessTokenExpireTime time.Duration + accessTokenSecret []byte + + refreshTokenExpireTime time.Duration + refreshTokenSecret []byte } -func (s *JWT) CreateToken(id string) (string, error) { +func (s *JWT) CreateAccessToken(id string) (string, error) { claims := &jwt.StandardClaims{ - Id: id, - ExpiresAt: time.Now().Add(time.Duration(s.tokenExpireTime) * time.Minute).Unix(), + Subject: id, + IssuedAt: time.Now().Unix(), + ExpiresAt: time.Now().Add(s.accessTokenExpireTime).Unix(), } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - return token.SignedString(s.tokenSecret) + return token.SignedString(s.accessTokenSecret) +} + +func (s *JWT) CreateRefreshToken(id string) (string, error) { + claims := &jwt.StandardClaims{ + Subject: id, + IssuedAt: time.Now().Unix(), + ExpiresAt: time.Now().Add(s.refreshTokenExpireTime).Unix(), + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + + return token.SignedString(s.accessTokenSecret) } func (s *JWT) ValidateToken(tokenStr string) error { @@ -46,7 +68,7 @@ func (s *JWT) ValidateToken(tokenStr string) error { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } - return s.tokenSecret, nil + return s.accessTokenSecret, nil }) if _, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {