I have been reading up about multiple ways one can implement refresh/access token logic. I've received mixed answers on how to tackle it. My API currently requires username/password authentication Where a short life PASETO token is issued, then verified via middleware per each request.
Here is my current middleware :
const (
authorizationHeaderKey = "Authorization"
authorizationTypeBearer = "bearer"
authorizationPayloadKey = "authorization_payload"
)
func authMiddleWare(token tkn.Maker) gin.HandlerFunc {
return func(c *gin.Context) {
// Get the Value of the header
authorizationHeader := c.GetHeader(authorizationHeaderKey)
//Check if a value was provided
if len(authorizationHeader) == 0 {
err := errors.New("authorization header is not provided")
c.AbortWithStatusJSON(http.StatusUnauthorized, errorResponse(err))
return
}
// Split the Header string using space delimiter (separate 'Bearer' from <token>)
fields := strings.Fields(authorizationHeader)
if len(fields) < 2 {
err := errors.New("invalid authorization header format")
c.AbortWithStatusJSON(http.StatusUnauthorized, errorResponse(err))
return
}
// Check the Authorization type of the token (first portion of token)
authorizationType := strings.ToLower(fields[0])
// Check if the auth type matches type bearer
if authorizationType != authorizationTypeBearer {
err := fmt.Errorf("unsupported authorization type %s", authorizationType)
c.AbortWithStatusJSON(http.StatusUnauthorized, errorResponse(err))
return
}
accessToken := fields[1]
// Tell our token maker to verify the token
payload, err := token.VerifyToken(accessToken)
if err != nil {
if errors.Is(err, tkn.ErrExpiredToken) {
// if refresh token is valid and not expired, create a new access token
// else return 403 and force user to re authenticate
}
c.AbortWithStatusJSON(http.StatusUnauthorized, errorResponse(err))
return
}
// Set the payload in the context so the next handler can access it
c.Set(authorizationPayloadKey, payload)
c.Next()
}
}
This is the implementation I am evaluating (after updating my login endpoint to return 2 tokens, one for the access and one for the refresh) :
const (
authorizationHeaderKey = "Authorization"
refreshHeaderKey = "X-Refresh-Token"
authorizationTypeBearer = "Bearer"
authorizationPayloadKey = "authorization_payload"
)
func authMiddleWare(tokenMaker tkn.Maker) gin.HandlerFunc {
return func(c *gin.Context) {
// Get the Value of the header
authorizationHeader := c.GetHeader(authorizationHeaderKey)
refreshTokenHeader := c.GetHeader(refreshHeaderKey)
//Check if a value was provided
if len(authorizationHeader) == 0 {
err := errors.New("authorization header is not provided")
c.AbortWithStatusJSON(http.StatusUnauthorized, errorResponse(err))
return
}
// Split the Header string using space delimiter (separate 'Bearer' from <token>)
fields := strings.Fields(authorizationHeader)
if len(fields) < 2 {
err := errors.New("invalid authorization header format")
c.AbortWithStatusJSON(http.StatusUnauthorized, errorResponse(err))
return
}
// Check the Authorization type of the token (first portion of token)
authorizationType := strings.ToLower(fields[0])
// Check if the auth type matches type bearer
if authorizationType != strings.ToLower(authorizationTypeBearer) {
err := fmt.Errorf("unsupported authorization type %s", authorizationType)
c.AbortWithStatusJSON(http.StatusUnauthorized, errorResponse(err))
return
}
accessToken := fields[1]
// Tell our token maker to verify the token
payload, err := tokenMaker.VerifyToken(accessToken)
if err != nil {
if errors.Is(err, tkn.ErrExpiredToken) {
// if access token is expired, try to refresh it using the refresh token
payload, err = tokenMaker.RefreshToken(refreshTokenHeader)
if err != nil {
// if refresh token is invalid or not provided, return unauthorized status
c.AbortWithStatusJSON(http.StatusUnauthorized, errorResponse(err))
return
}
// generate a new access token
newAccessToken, err := tokenMaker.CreateToken(payload.Username, addSomeDurationToCurrentTime())
if err != nil {
c.AbortWithStatusJSON(http.StatusUnauthorized, errorResponse(err))
return
}
// replace the old access token with the new one
accessToken = newAccessToken
// optionally, set the new token in the response header
c.Header(authorizationHeaderKey, fmt.Sprintf("%s %s", authorizationTypeBearer, newAccessToken))
} else {
c.AbortWithStatusJSON(http.StatusUnauthorized, errorResponse(err))
return
}
}
// Set the payload in the context so the next handler can access it
c.Set(authorizationPayloadKey, payload)
c.Next()
}
}
Am I on the right track or does my flow impose security risks ?