Authentication
For web applications, OAuth or password-based authentication is commonly used.
For APIs, token-based authentication is more prevalent.
Authentication with JWT
In simple terms, JWTs (JSON Web Tokens) are signed JSON documents that adhere to a specific format.
Add jwt/main.go:
package jwt
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"os"
"strings"
"time"
)
type Claims struct {
Issuer string `json:"iss"`
Subject string `json:"sub"`
Audience string `json:"aud"`
ExpiresAt int64 `json:"exp"`
IssuedAt int64 `json:"iat"`
JwtId string `json:"jti"`
}
func WriteJWT(secretKey []byte, claims Claims) (string, error) {
payload, err := json.Marshal(claims)
if err != nil {
return "", err
}
message := base64.URLEncoding.EncodeToString([]byte(`{"alg":"HS256","typ":"JWT"}`)) +
"." + base64.URLEncoding.EncodeToString(payload)
hash := hmac.New(sha256.New, secretKey)
hash.Write([]byte(message))
return message + "." + base64.URLEncoding.EncodeToString(hash.Sum(nil)), nil
}
func ReadJWT(secretKey []byte, jwt string) (Claims, error) {
parts := strings.Split(jwt, ".")
if len(parts) != 3 {
return Claims{}, fmt.Errorf("invalid JWT format")
}
payloadBytes, err := base64.URLEncoding.DecodeString(parts[1])
if err != nil {
return Claims{}, fmt.Errorf("payload decode error: %w", err)
}
signatureBytes, err := base64.URLEncoding.DecodeString(parts[2])
if err != nil {
return Claims{}, fmt.Errorf("signature decode error: %w", err)
}
hash := hmac.New(sha256.New, secretKey)
hash.Write([]byte(parts[0] + "." + parts[1]))
if !hmac.Equal(signatureBytes, hash.Sum(nil)) {
return Claims{}, fmt.Errorf("invalid JWT signature")
}
var claims Claims
if err := json.Unmarshal(payloadBytes, &claims); err != nil {
return Claims{}, fmt.Errorf("unmarshal claims error: %w", err)
}
if time.Now().Unix() > claims.ExpiresAt {
return Claims{}, fmt.Errorf("JWT expired")
}
return claims, nil
}
Issuing a JWT Token
initialize Go modules:
$ go mod init wdgfs
Update server.go with following content:
package main
import (
"fmt"
"net/http"
"os"
"strings"
"time"
"wdgfs/jwt"
)
func main() {
if os.Args[1] == "token" {
claims := jwt.Claims{
Issuer: "issuer",
Subject: "subject",
Audience: "audience",
ExpiresAt: time.Now().Add(time.Hour * 1).Unix(),
IssuedAt: time.Now().Unix(),
JwtId: os.Args[2],
}
token, _ := jwt.WriteJWT([]byte(os.Getenv("JWT_SECRET")), claims)
fmt.Println(token)
return
}
http.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
token := strings.Split(r.Header.Get("Authorization"), " ")
if (len(token)) != 2 {
http.Error(w, "Invalid token", http.StatusUnauthorized)
return
}
claims, err := jwt.ReadJWT([]byte(os.Getenv("JWT_SECRET")), token[1])
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
fmt.Fprintf(w, "%+v", claims)
})
if err := http.ListenAndServe(":3000", nil); err != nil {
panic(err)
}
}
Issue a token
$ JWT_SECRET=secret go run server.go token admin
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Start server
$ JWT_SECRET=secret go run server.go serve
Make a request
$ curl localhost:3000/api
Invalid Token
Make a request with token
$ curl localhost:3000/api -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'
{Issuer:issuer Subject:subject Audience:audience ExpiresAt:1730035781 IssuedAt:1730032181 JwtId:admin}