JWT Tokens in Go (Golang): A Complete Guide with the golang-jwt Library
JSON Web Token (JWT) is an open standard (RFC 7519) for creating access tokens based on JSON. JWT is widely used for authentication and information exchange between parties. In Go (Golang), the most popular and actively maintained library for working with JWT is golang-jwt/jwt/v5. In this article, we will thoroughly cover how to install, configure, and use this library to build secure web applications.
Installing the golang-jwt Library
To get started, add the library to your project using go get:
go get github.com/golang-jwt/jwt/v5After installation, import the package into your code:
import ( "github.com/golang-jwt/jwt/v5")Basic Components of JWT
JWT consists of three parts separated by dots: Header, Payload, and Signature. The golang-jwt library provides convenient structures for working with each of them:
- Header — contains metadata about the token type and signing algorithm (e.g., HS256, RS256).
- Payload — contains claims, such as user ID, expiration time, roles, and other data.
- Signature — created using a secret key or a key pair (private/public).
Creating a JWT Access Token
Let's look at an example of creating an access token using the HS256 algorithm (symmetric encryption). First, define a structure with custom claims:
type CustomClaims struct { UserID int `json:"user_id"` Username string `json:"username"` jwt.RegisteredClaims}Now, create a function to generate the token:
func GenerateAccessToken(userID int, username string, secretKey []byte) (string, error) { claims := CustomClaims{ UserID: userID, Username: username, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(15 * time.Minute)), IssuedAt: jwt.NewNumericDate(time.Now()), Subject: "access", }, }
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString(secretKey)}Here, we set the token lifetime to 15 minutes, which is standard practice for an access token. Use jwt.NewNumericDate() for correct time formatting.
Token Verification and Parsing
To verify the authenticity of a token, you need to extract the claims and check the signature:
func ValidateToken(tokenString string, secretKey []byte) (*CustomClaims, error) { token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) { // Check that the expected signing algorithm is used if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } return secretKey, nil })
if err != nil { return nil, err }
if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid { return claims, nil }
return nil, fmt.Errorf("invalid token")}It is important to check the signing algorithm in the callback function to avoid algorithm substitution attacks.
Creating and Refreshing a Refresh Token
To enhance security, a pair of tokens is often used: a short-lived access token and a long-lived refresh token. The refresh token allows obtaining a new access token without re-entering login credentials.
func GenerateRefreshToken(userID int, secretKey []byte) (string, error) {