In this section, we will cover the essential concepts and techniques for securing RESTful APIs, focusing on authentication and authorization, with a deep dive into using JSON Web Tokens (JWT).
JWT is a compact, URL-safe means of representing claims to be transferred between two parties. It is widely used for securing RESTful APIs due to its simplicity and ease of use.
Example of a JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Header: Specify the algorithm and token type.
json{
"alg": "HS256",
"typ": "JWT"
}
Payload: Include the claims.
json{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
Signature: Sign the header and payload using a secret key.
bashHMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)
Here's how you can implement JWT-based authentication and authorization in a Go RESTful API.
First, you need to install the github.com/dgrijalva/jwt-go
package.
bashgo get github.com/dgrijalva/jwt-go
Create a struct to handle user login data.
gotype Credentials struct {
Username string `json:"username"`
Password string `json:"password"`
}
Write a function to generate a JWT token.
goimport (
"time"
"github.com/dgrijalva/jwt-go"
)
var jwtKey = []byte("my_secret_key")
func GenerateJWT(username string) (string, error) {
expirationTime := time.Now().Add(24 * time.Hour)
claims := &jwt.StandardClaims{
Subject: username,
ExpiresAt: expirationTime.Unix(),
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString(jwtKey)
if err != nil {
return "", err
}
return tokenString, nil
}
Write a handler function for user login that validates credentials and issues a JWT token.
goimport (
"encoding/json"
"net/http"
)
func Login(w http.ResponseWriter, r *http.Request) {
var creds Credentials
err := json.NewDecoder(r.Body).Decode(&creds)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
// Here, you should validate the user credentials with your database
if creds.Username != "user" || creds.Password != "password" {
w.WriteHeader(http.StatusUnauthorized)
return
}
token, err := GenerateJWT(creds.Username)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
http.SetCookie(w, &http.Cookie{
Name: "token",
Value: token,
Expires: time.Now().Add(24 * time.Hour),
})
}
Create a middleware function to protect your routes.
gofunc AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie("token")
if err != nil {
if err == http.ErrNoCookie {
w.WriteHeader(http.StatusUnauthorized)
return
}
w.WriteHeader(http.StatusBadRequest)
return
}
tokenStr := cookie.Value
claims := &jwt.StandardClaims{}
token, err := jwt.ParseWithClaims(tokenStr, claims, func(token *jwt.Token) (interface{}, error) {
return jwtKey, nil
})
if err != nil {
if err == jwt.ErrSignatureInvalid {
w.WriteHeader(http.StatusUnauthorized)
return
}
w.WriteHeader(http.StatusBadRequest)
return
}
if !token.Valid {
w.WriteHeader(http.StatusUnauthorized)
return
}
ctx := context.WithValue(r.Context(), "username", claims.Subject)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
Apply the middleware to secure your API endpoints.
gohttp.Handle("/protected", AuthMiddleware(http.HandlerFunc(ProtectedEndpoint)))
func ProtectedEndpoint(w http.ResponseWriter, r *http.Request) {
username := r.Context().Value("username").(string)
w.Write([]byte("Welcome " + username))
}
By following these steps, you can secure your RESTful APIs using JWT in Go. This implementation covers the basics of generating and validating tokens, as well as protecting your API endpoints with middleware. Always remember to follow best practices for securing your tokens and handling sensitive data.