-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathauth.go
127 lines (113 loc) · 3.28 KB
/
auth.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package auth
import (
"context"
"fmt"
"net/http"
"strings"
"github.com/golang-jwt/jwt/v5"
)
// Handle returns an HTTP middleware that validates JWT tokens from the Authorization header.
// It uses the provided configuration and options to create a new Auth instance and returns its Handler.
//
// Example usage:
//
// r := chi.NewRouter()
// cfg := auth.Config{
// Secret: "your-secret",
// }
//
// r.Use(auth.Handle(cfg))
// r.Get("/protected", HandleProtectedInfo)
//
// http.ListenAndServe(":3000", r)
//
// Parameters:
//
// cfg - the configuration for the Auth instance.
// opts - optional parameters for customizing the Auth instance.
//
// Returns:
//
// func(next http.Handler) http.Handler - the middleware function that wraps the next http.Handler with JWT validation.
func Handle(cfg Config, opts ...Option) func(next http.Handler) http.Handler {
a := New(cfg, opts...)
return a.Handler
}
type Auth struct {
cfg Config
opts *options
}
func New(cfg Config, opts ...Option) *Auth {
o := new(options)
for _, opt := range opts {
opt(o)
}
return &Auth{cfg: cfg, opts: o}
}
// Handler is an HTTP middleware that validates JWT tokens from the Authorization header.
// If the token is valid, it extracts the claims and adds them to the request context.
// If the token is invalid or missing, it responds with an unauthorized status.
// If the admin option is enabled, it also checks if the user has admin privileges.
//
// Example usage:
//
// cfg := auth.Config{
// Secret: "your-secret",
// TokenExp: time.Hour,
// }
//
// mux := http.NewServeMux()
//
// handler := func(w http.ResponseWriter, r *http.Request) {
// w.WriteHeader(http.StatusOK)
// w.Write([]byte("success"))
// }
//
// mux.Handle("/protected", auth.New(cfg).Handler(http.HandlerFunc(handler)))
//
// http.ListenAndServe(":3000", mux)
//
// Parameters:
//
// next - the next http.Handler to be called if the token is valid.
//
// Returns:
//
// http.Handler - the wrapped http.Handler with JWT validation.
func (a *Auth) Handler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := a.extractBearerToken(r)
if token == "" {
http.Error(w, "unauthorized request", http.StatusUnauthorized)
return
}
claims, err := a.validateToken(token)
if err != nil {
http.Error(
w,
fmt.Errorf("unauthorized request: %v", err).Error(),
http.StatusUnauthorized,
)
}
ctx := claims.toContext()
if a.opts.admin && !ctx.IsAdmin { // check if the user is admin
http.Error(w, "forbidden request", http.StatusForbidden)
return
}
// add claims to the request context and pass it to the next handler
next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), CtxKey, ctx)))
})
}
func (a *Auth) extractBearerToken(r *http.Request) string {
return strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")
}
func (a *Auth) validateToken(token string) (*JwtClaims, error) {
claims := new(JwtClaims)
_, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return []byte(a.cfg.Secret), nil
}, jwt.WithExpirationRequired(), jwt.WithIssuedAt())
return claims, err
}