-
Notifications
You must be signed in to change notification settings - Fork 1
/
main.go
142 lines (123 loc) · 4.57 KB
/
main.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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package main
import (
"fmt"
"net/http"
"os"
"strings"
"time"
"github.com/clerkinc/clerk-sdk-go/clerk"
"github.com/gin-gonic/gin"
)
// Set some leeway for checking the tokens so we don't need to keep regenerating
// them for testing purposes.
const leeway = 50000 * time.Minute
func main() {
// Initialize clerk client.
developmentSecretKey := os.Getenv("CLERK_DEVELOPMENT_SECRET_KEY")
clerkClient, err := clerk.NewClient(developmentSecretKey)
if err != nil {
panic(err)
}
// Create two gin router groups, one with middleware and one without.
baseRouter := gin.Default()
routerWithMiddleware := baseRouter.Group("/")
// routerWithMiddleware.Use(clerkMiddlewareAttempt1(clerkClient)) // uncomment to try this one.
routerWithMiddleware.Use(clerkMiddlewareAttempt2(clerkClient))
routerNoMiddleware := baseRouter.Group("/")
// This endpoint is a dummy health check to prove the server is up.
baseRouter.GET("/livez", func(c *gin.Context) {
c.String(http.StatusOK, "ok")
})
// This endpoint reads and verifies the bearer token directly to parse claims
// and fetch the user.
routerNoMiddleware.GET("/user", func(c *gin.Context) {
sessionToken := c.Request.Header.Get("Authorization")
sessionToken = strings.TrimPrefix(sessionToken, "Bearer ")
sessClaims, err := clerkClient.VerifyToken(sessionToken, clerk.WithLeeway(leeway))
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"message": "Unauthorized",
})
return
}
user, err := clerkClient.Users().Read(sessClaims.Claims.Subject)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"message": "failed to read user from claims",
})
}
c.JSON(http.StatusOK, gin.H{
"firstName": user.FirstName,
"lastName": user.LastName,
})
})
// This endpoint relies on the session claims being available in the
// context of the request, and then uses the session claims to fetch the user.
// I expect the session to be available, but it is not.
routerWithMiddleware.GET("/user-with-middleware", func(c *gin.Context) {
clerkSessionClaims, found := clerk.SessionFromContext(c.Request.Context())
if !found {
c.JSON(http.StatusInternalServerError, gin.H{
"message": "failed to retrieve session from context",
})
}
user, err := clerkClient.Users().Read(clerkSessionClaims.Claims.Subject)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"message": "failed to read user from claims",
})
}
c.JSON(http.StatusOK, gin.H{
"firstName": user.FirstName,
"lastName": user.LastName,
})
})
srv := &http.Server{
Addr: fmt.Sprintf(":%d", 4242),
Handler: baseRouter,
}
// Skipping graceful shutdown logic since this is just an example.
srv.ListenAndServe()
}
func clerkMiddlewareAttempt1(clerkClient clerk.Client) gin.HandlerFunc {
checkAndInjectActiveSession := clerk.RequireSessionV2(clerkClient, clerk.WithLeeway(leeway))
var noop http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
_, found := clerk.SessionFromContext(r.Context())
if found {
fmt.Println("found session in noop handler") // <-- this logs, so the session is passed down into here. but, it's not available in the gin handler.
} else {
fmt.Println("did not find session in noop handler")
}
}
return gin.WrapH(checkAndInjectActiveSession(noop))
}
// This doesn't use gin.H and instead tries to wrap the Clerk middleware manually.
func clerkMiddlewareAttempt2(clerkClient clerk.Client) gin.HandlerFunc {
checkAndInjectActiveSession := clerk.RequireSessionV2(clerkClient, clerk.WithLeeway(leeway))
var noop http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
_, found := clerk.SessionFromContext(r.Context())
if found {
fmt.Println("found session in noop handler") // <-- this logs, so the session is passed down into here. but, it's not available in the gin handler.
} else {
fmt.Println("did not find session in noop handler")
}
}
return func(c *gin.Context) {
handler := checkAndInjectActiveSession(noop)
handler.ServeHTTP(c.Writer, c.Request) // <- runs the handler provided by clerk middleware
_, found := clerk.SessionFromContext(c.Request.Context())
// If you run this, the session is NOT found, because the Clerk middleware doesn't modify the request.
// So, downstream gin handlers will not have accesss to the session in the request context.
if found {
fmt.Println("found session in middleware")
} else {
fmt.Println("did not find session in middleware") // <-- this logs
}
// If the status has been written by the middleware, return out.
if c.Writer.Status() != http.StatusOK {
c.Abort()
return
}
c.Next()
}
}