JWT in Action: Secure Authentication & Authorization in Go
Leapcell: The Best Serverless Platform for Golang Hosting
In - depth Explanation of JWT: Principles, Format, Features, and Application in Go Projects
What is JWT
JWT is the abbreviation of JSON Web Token, which is a cross - domain authentication solution. It plays a vital role in web applications, enabling secure and convenient authentication and information transfer.
Problems Solved by Using JWT
Limitations of the Traditional User Authentication Process
Traditional authentication relies on client - side cookies and server - side sessions. This works well for single - server applications. However, when it comes to multi - server deployments, there is a problem of session sharing. For example, in a large - scale distributed system where multiple servers work together, each server maintains an independent session. When a user switches between servers, there may be inconsistent login states. At the same time, single - sign - on cannot be achieved across different domains through cookie + session because cookies are set based on domains, and different domains cannot directly share cookies, which restricts unified authentication and access control in multi - business systems.
Advantages of JWT
JWT makes applications stateless, avoiding the need for session sharing. It includes user information within its own structure. Servers do not need to store sessions. Instead, they can confirm the user's identity by verifying the validity of the JWT for each request. In a distributed system, JWT facilitates server expansion and is not affected by the number and distribution of servers.
Format of JWT
A correct JWT format is as follows:
eyJhbGciOiJIUzI1NiIsInR5c.eyJ1c2VybmFtZaYjiJ9._eCVNYFYnMXwpgGX9Iu412EQSOFuEGl2c
As can be seen, a JWT string consists of three parts: Header, Payload, and Signature, connected by dots.
Header
The Header is a JSON object composed of two parts: the token type and the encryption algorithm. For example:
{
"typ": "JWT",// Usually "JWT"
"alg": "HS256"// Supports various encryption algorithms
}
Convert the above JSON object into a string using the Base64URL algorithm to obtain the Header part of the JWT. It should be noted that JWT encoding does not use standard Base64 but Base64Url. This is because in the string generated by Base64, there may be three special symbols in URLs: +, /, and =. And we may pass the token on the URL (e.g., test.com?token = xxx). The Base64URL algorithm, on the basis of the string generated by the Base64 algorithm, omits the =, replaces + with -, and replaces / with _. This ensures that the generated string can be passed in the URL without problems.
Payload
The Payload part of the JWT, like the Header, is also a JSON object used to store the actual data we need. The JWT standard provides seven optional fields, namely:
iss(issuer): The issuer, whose value is a case - sensitive string or Uri.
sub(subject): The subject, used to identify a user.
exp(expiration time): The expiration time.
aud(audience): The audience.
iat(issued at): The issued time.
nbf(not before): The time before which the JWT is not valid.
jti(JWT ID): The identifier.
In addition to the standard fields, we can define private fields as needed to meet business requirements. For example:
{
iss:"admin",// Standard field
jti:"test",// Standard field
username:"leapcell",// Custom field
"gender":"male",
"avatar":"https://avatar.leapcell.jpg"
}
Convert the above JSON object into a string using the Base64URL algorithm to obtain the Payload part of the JWT.
Signature
The Signature is the signature of the JWT. The generation method is as follows: Encode the Header and Payload using the Base64URL algorithm, connect them with a dot, and then encrypt them using the secret key (secretKey) and the encryption method specified in the Header to finally generate the Signature. The role of the signature is to ensure that the JWT has not been tampered with during transmission. The server can verify the integrity and authenticity of the JWT by verifying the signature.
Features of JWT
Security Recommendation: It is best to use the HTTPS protocol to prevent the possibility of JWT theft. Because under the HTTP protocol, data transmission is in plaintext, which is easy to be intercepted and tampered with. HTTPS can effectively protect the security of JWT through encrypted transmission.
Limitation of the Invalidation Mechanism: Except for the expiration of the JWT's issued time, there is no other way to invalidate an already - generated JWT, unless the server - side changes the algorithm. This means that once a JWT is issued, if it is stolen within the validity period, it may be maliciously used.
Storage of Sensitive Information: When the JWT is not encrypted, sensitive information should not be stored in it. If sensitive information needs to be stored, it
In - depth Explanation of JWT: Principles, Format, Features, and Application in Go Projects
What is JWT
JWT is the abbreviation of JSON Web Token, which is a cross - domain authentication solution. It plays a vital role in web applications, enabling secure and convenient authentication and information transfer.
Problems Solved by Using JWT
Limitations of the Traditional User Authentication Process
Traditional authentication relies on client - side cookies and server - side sessions. This works well for single - server applications. However, when it comes to multi - server deployments, there is a problem of session sharing. For example, in a large - scale distributed system where multiple servers work together, each server maintains an independent session. When a user switches between servers, there may be inconsistent login states. At the same time, single - sign - on cannot be achieved across different domains through cookie + session because cookies are set based on domains, and different domains cannot directly share cookies, which restricts unified authentication and access control in multi - business systems.
Advantages of JWT
JWT makes applications stateless, avoiding the need for session sharing. It includes user information within its own structure. Servers do not need to store sessions. Instead, they can confirm the user's identity by verifying the validity of the JWT for each request. In a distributed system, JWT facilitates server expansion and is not affected by the number and distribution of servers.
Convert the above JSON object into a string using the Base64URL algorithm to obtain the Header part of the JWT. It should be noted that JWT encoding does not use standard Base64 but Base64Url. This is because in the string generated by Base64, there may be three special symbols in URLs: +, /, and =. And we may pass the token on the URL (e.g., test.com?token = xxx). The Base64URL algorithm, on the basis of the string generated by the Base64 algorithm, omits the =, replaces + with -, and replaces / with _. This ensures that the generated string can be passed in the URL without problems.
Payload
The Payload part of the JWT, like the Header, is also a JSON object used to store the actual data we need. The JWT standard provides seven optional fields, namely:
iss(issuer): The issuer, whose value is a case - sensitive string or Uri.
sub(subject): The subject, used to identify a user.
exp(expiration time): The expiration time.
aud(audience): The audience.
iat(issued at): The issued time.
nbf(not before): The time before which the JWT is not valid.
jti(JWT ID): The identifier.
In addition to the standard fields, we can define private fields as needed to meet business requirements. For example:
Convert the above JSON object into a string using the Base64URL algorithm to obtain the Payload part of the JWT.
Signature
The Signature is the signature of the JWT. The generation method is as follows: Encode the Header and Payload using the Base64URL algorithm, connect them with a dot, and then encrypt them using the secret key (secretKey) and the encryption method specified in the Header to finally generate the Signature. The role of the signature is to ensure that the JWT has not been tampered with during transmission. The server can verify the integrity and authenticity of the JWT by verifying the signature.
Features of JWT
Security Recommendation: It is best to use the HTTPS protocol to prevent the possibility of JWT theft. Because under the HTTP protocol, data transmission is in plaintext, which is easy to be intercepted and tampered with. HTTPS can effectively protect the security of JWT through encrypted transmission.
Limitation of the Invalidation Mechanism: Except for the expiration of the JWT's issued time, there is no other way to invalidate an already - generated JWT, unless the server - side changes the algorithm. This means that once a JWT is issued, if it is stolen within the validity period, it may be maliciously used.
Storage of Sensitive Information: When the JWT is not encrypted, sensitive information should not be stored in it. If sensitive information needs to be stored, it is best to encrypt it again. Because the JWT itself can be decoded. If it contains sensitive information and is not encrypted, there will be a security risk.
Setting of Expiration Time: It is advisable to set a short expiration time for the JWT to prevent it from remaining valid if stolen, reducing potential losses. A short expiration time can reduce the risk after the JWT is stolen. Even if it is stolen, its valid time is limited.
Storage of Business Information: The Payload of the JWT can also store some business information, which can reduce database queries. For example, basic user information can be stored in the Payload. Each time a request is made, the server can directly obtain this information from the JWT without querying the database again, improving the performance and response speed of the system.
Usage of JWT
After the server issues the JWT, it sends it to the client. If the client is a browser, it can be stored in a cookie or localStorage. If it is an APP, it can be stored in an sqlite database. Then, for each interface request, the JWT is carried. There are many ways to carry it to the server - side, such as query, cookie, header, or body. In short, any way that can carry data to the server can be used. However, the more standardized approach is to upload it through the header Authorization, with the following format:
Authorization:Bearer
This way of passing the JWT in the HTTP request header conforms to common authentication specifications and is convenient for the server to perform unified authentication processing.
Using JWT in Go Projects
Generating JWT
Use the github.com/golang - jwt/jwt library to help us generate or parse JWT. We can use the NewWithClaims() method to generate a Token object and then use the method of the Token object to generate a JWT string. For example:
packagemainimport("fmt""time""github.com/golang-jwt/jwt")funcmain(){hmacSampleSecret:=[]byte("123")// Secret key, must not be leaked// Generate a token objecttoken:=jwt.NewWithClaims(jwt.SigningMethodHS256,jwt.MapClaims{"foo":"bar","nbf":time.Date(2015,10,10,12,0,0,0,time.UTC).Unix(),})// Generate a jwt stringtokenString,err:=token.SignedString(hmacSampleSecret)fmt.Println(tokenString,err)}
We can also use the New() method to generate a Token object and then generate a JWT string. For example:
packagemainimport("fmt""time""github.com/golang-jwt/jwt")funcmain(){hmacSampleSecret:=[]byte("123")token:=jwt.New(jwt.SigningMethodHS256)// Data cannot be carried when created through the New method, so data can be defined by assigning values to token.Claimstoken.Claims=jwt.MapClaims{"foo":"bar","nbf":time.Date(2015,10,10,12,0,0,0,time.UTC).Unix(),}tokenString,err:=token.SignedString(hmacSampleSecret)fmt.Println(tokenString,err)}
In the above examples, the data in the Payload of the JWT is defined through the jwt.MapClaims data structure. In addition to using jwt.MapClaims, we can also use a custom structure. However, this structure must implement the following interface:
typeClaimsinterface{Valid()error}
The following is an example of implementing a custom data structure:
If we want to use the fields defined in the JWT standard in the custom structure, we can do it like this:
typeCustomerClaimsstruct{*jwt.StandardClaims// Standard fieldsUsernamestring`json:"username"`Genderstring`json:"gender"`Avatarstring`json:"avatar"`Emailstring`json:"email"`}
Parsing JWT
Parsing is the reverse operation of generation. We parse a token to obtain its Header, Payload, and verify whether the data has been tampered with through the Signature. The following is the specific implementation:
packagemainimport("fmt""github.com/golang-jwt/jwt")typeCustomerClaimsstruct{Usernamestring`json:"username"`Genderstring`json:"gender"`Avatarstring`json:"avatar"`Emailstring`json:"email"`jwt.StandardClaims}funcmain(){varhmacSampleSecret=[]byte("111")// The token generated in the previous exampletokenString:="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IuWwj-aYjiIsImdlbmRlciI6IueUtyIsImF2YXRhciI6Imh0dHBzOi8vMS5qcGciLCJlbWFpbCI6InRlc3RAMTYzLmNvbSJ9.mJlWv5lblREwgnP6wWg-P75VC1FqQTs8iOdOzX6Efqk"token,err:=jwt.ParseWithClaims(tokenString,&CustomerClaims{},func(t*jwt.Token)(interface{},error){returnhmacSampleSecret,nil})iferr!=nil{fmt.Println(err)return}claims:=token.Claims.(*CustomerClaims)fmt.Println(claims)}
Using JWT in Gin Projects
In the Gin framework, login authentication is generally implemented through middleware. The github.com/appleboy/gin - jwt library has integrated the implementation of github.com/golang - jwt/jwt and defined corresponding middleware and controllers for us. The following is a specific example:
packagemainimport("log""net/http""time"jwt"github.com/appleboy/gin-jwt/v2""github.com/gin-gonic/gin")// Used to receive the username and password for logintypeloginstruct{Usernamestring`form:"username" json:"username" binding:"required"`Passwordstring`form:"password" json:"password" binding:"required"`}varidentityKey="id"// Data in the payload of jwttypeUserstruct{UserNamestringFirstNamestringLastNamestring}funcmain(){// Define a Gin middlewareauthMiddleware,err:=jwt.New(&jwt.GinJWTMiddleware{Realm:"test zone",// IdentificationSigningAlgorithm:"HS256",// Encryption algorithmKey:[]byte("secret key"),// Secret keyTimeout:time.Hour,MaxRefresh:time.Hour,// Maximum refresh extension timeIdentityKey:identityKey,// Specify the id of the cookiePayloadFunc:func(datainterface{})jwt.MapClaims{// Payload, where the data in the payload of the returned jwt can be definedifv,ok:=data.(*User);ok{returnjwt.MapClaims{identityKey:v.UserName,}}returnjwt.MapClaims{}},IdentityHandler:func(c*gin.Context)interface{}{claims:=jwt.ExtractClaims(c)return&User{UserName:claims[identityKey].(string),}},Authenticator:Authenticator,// Login verification logic can be written hereAuthorizator:func(datainterface{},c*gin.Context)bool{// When a user requests a restricted interface through a token, this logic will be executedifv,ok:=data.(*User);ok&&v.UserName=="admin"{returntrue}returnfalse},Unauthorized:func(c*gin.Context,codeint,messagestring){// Response when there is an errorc.JSON(code,gin.H{"code":code,"message":message,})},// Specify where to get the token. The format is: ":". If there are multiple, separate them with commasTokenLookup:"header: Authorization, query: token, cookie: jwt",TokenHeadName:"Bearer",TimeFunc:time.Now,})iferr!=nil{log.Fatal("JWT Error:"+err.Error())}r:=gin.Default()// Login interfacer.POST("/login",authMiddleware.LoginHandler)auth:=r.Group("/auth")// Logoutauth.POST("/logout",authMiddleware.LogoutHandler)// Refresh token, extend the token's validity periodauth.POST("/refresh_token",authMiddleware.RefreshHandler)auth.Use(authMiddleware.MiddlewareFunc())// Apply the middleware{auth.GET("/hello",helloHandler)}iferr:=http.ListenAndServe(":8005",r);err!=nil{log.Fatal(err)}}funcAuthenticator(c*gin.Context)(interface{},error){varloginValsloginiferr:=c.ShouldBind(&loginVals);err!=nil{return"",jwt.ErrMissingLoginValues}userID:=loginVals.Usernamepassword:=loginVals.Passwordif(userID=="admin"&&password=="admin")||(userID=="test"&&password=="test"){return&User{UserName:userID,LastName:"Leapcell",FirstName:"Admin",},nil}returnnil,jwt.ErrFailedAuthentication}// Controller for handling the /hello routefunchelloHandler(c*gin.Context){claims:=jwt.ExtractClaims(c)user,_:=c.Get(identityKey)c.JSON(200,gin.H{"userID":claims[identityKey],"userName":user.(*User).UserName,"text":"Hello World.",})}
After running the server, send a login request through the curl command, such as: