-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathjwt-with-go.html
268 lines (228 loc) · 10.6 KB
/
jwt-with-go.html
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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>janzenbupa.github.io</title>
<style>
body {
margin: 0;
font-family: Arial, Helvetica, sans-serif;
background-color: #f8f8f8;
}
.nav-bar-header {
background-color: #1f1f2e;
color: #e6e6e6;
padding: 20px;
text-align: center;
}
.nav-bar-header h2 {
margin: 0;
}
.nav-bar ul {
list-style-type: none;
margin: 0;
padding: 0;
overflow: hidden;
background-color: #1f1f2e;
text-align: center;
}
.nav-bar li {
display: inline-block;
margin-right: 20px;
}
.nav-bar li a {
display: block;
color: #fff;
padding: 10px 15px;
text-decoration: none;
}
.nav-bar li a:hover {
background-color: #735581;
text-decoration: underline;
}
.body-padding {
padding: 0 20px;
}
.p-center {
text-align: center;
margin-bottom: 20px;
}
code {
background: hsl(0, 3%, 92%);
}
.code-container {
display: flex;
justify-content: center;
align-items: center;
height: 300px;
}
.code-block {
width: 500px;
height: 190px;
padding: 1px;
margin: 0px;
background-color: #f5f5f5;
border: 1px solid #ddd;
overflow: auto;
white-space: pre-wrap;
text-align: left;
}
</style>
</head>
<body>
<div class="nav-bar-header">
<h2 class="h2-title">software engineering</h2>
<p></p>
</div>
<div class="nav-bar">
<ul>
<li><a class="a-look" href="index.html">home</a></li>
<li class="li-header"><a class="a-look" href="blog.html">blog</a></li>
</ul>
</div>
</div>
<div class="p-center" style="padding-bottom: 20px;">
<H2 class="h2-title" style="color:#676798">Using JWTs in Golang</H2>
<p>JSON Web Tokens are great for authentication and here is a simple way to implement them using Golang and Gin. The first step is to create a project and fetch the necessary requirements.
</p>
<div style="text-align: center;">
<code class="codeblock" style="display: inline-block;text-align: left;width: 400px;margin-bottom: 20px;">
go mod init jwt-project-name</br>
go get github.com/dgrijalva/jwt-go</br>
go get github.com/gin-gonic-gin</br>
</code>
</div>
<p>It is important to remember that if there are any missing dependencies when adding code or functions that running</p>
<div style="text-align: center;">
<code class="codeblock" style="display: inline-block;text-align: left;width: 400px;margin-bottom: 20px;">
go get .</br>
</code>
</div>
<p>Can fetch missing dependencies that may have been forgotten about, or if the location is unknown to the user.
It is good at fetching standard library dependencies and third party packages.</p>
<p>Next we want to create a function that can generate a token for us. This can be done many ways but one simple way for the sake of this post is to have a user send in
a username and password pair of some sort and after authenticating that username and password pair, generate a token with specified claims.
</p>
<p>We will have a Claims struct, and specify some endpoints that are authorized for a given user.</p>
<div class="code-container">
<code class="code-block" style="height:180px;">
type Claims struct {
Password string `json:"password"`
Username string `json:"username"`
Endpoints []string
jwt.StandardClaims
}
var endpoints = []string{
"/users",
}
</code>
</div>
<p>Next, we will create function that generates our token.</p>
<div class="code-container"style="height: 300px;margin-bottom: 20px;">
<code class="code-block" style="height: 280px;width: 600px;">
func GenerateToken(password string, username string) (string, error) {
claims := &Claims{
Password: password,
Username: username,
Endpoints: endpoints,
StandardClaims: jwt.StandardClaims{
ExpiresAt: time.Now().Add(time.Hour * 24).Unix(),
IssuedAt: time.Now().Unix(),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte("secret-key"))
}
</code>
</div>
<p style="margin-left:20px;margin-right:20px;">So far, it is simple enough. We could generate a POST endpoint that accepts our username and password that would of course have to be validated some how.
For this post I don't think it is relevant how that is done. But a simple database check should validate that while generating the token, but a different solution could be valid as well.
For now, I am just going to create an endpoint that accepts a username and password, and calls GenerateToken without any sort of validation on the username and password.
</p>
<div class="code-container"style="height: 550px;">
<code class="code-block" style="height: 300px;width: 700px;">
func main() {
router := gin.Default()
router.POST("/authenticate", authenticate)
router.Run("localhost:1234")
}
func authenticate(c *gin.Context) {
var claims auth.Claims
if err := c.BindJSON(&claims); err != nil {
c.IndentedJSON(http.StatusBadRequest, gin.H{"Status": "Failed to generate token"})
}
token, _ := auth.GenerateToken(claims.Password, claims.Username)
c.IndentedJSON(http.StatusOK, gin.H{"jwt": token})
}
</code>
</div>
<p style="margin-left:20px;margin-right:20px;">Now we need to add a "/users" endpoint, which is what we defined in our claims.
This endpoint will need to be authorized for our authenticated user, but since we already added the endpoint to our claims, we just need to add middleware to handle that authorization.
We simply defined a []string in our code to generate a token. When generating a token we set our "Endpoints" claim to a slice that we hardcoded, in reality these would come a database configuration or somewhere other than a slice in code.
</p>
<p style="margin-left:20px;margin-right:20px;">When we define our "/users" endpoint, we also add middleware that can handle our authorization. Our middleware can go with our authentication logic, and it will verify that the token is valid, that the endpoint we are trying to hit is authorized,
and either return a 401 response or call our function that we pass into our handler, which is our endpoint function. First, I will show the handler middleware that we need.
</p>
<div class="code-container"style="height: 350px;margin-bottom: 20px;">
<code class="code-block" style="height: 320px;width: 800px;">
func AuthHandler(handlerFunc gin.HandlerFunc) gin.HandlerFunc {
return func(c *gin.Context) {
tokenString := c.GetHeader("Authorization")
if tokenString == "" {
c.AbortWithStatus(http.StatusUnauthorized)
return
}
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return []byte("secret-key"), nil
})
if err != nil || !token.Valid {
c.AbortWithStatus(http.StatusUnauthorized)
return
}
claims, authorized := token.Claims.(jwt.MapClaims)
if !authorized {
c.AbortWithStatus(http.StatusUnauthorized)
return
}
endpoint := c.Request.URL.Path
for _, val := range claims["Endpoints"].([]interface{}) {
if val == endpoint {
handlerFunc(c)
return
}
}
c.AbortWithStatus(http.StatusUnauthorized)
}
}
</code>
</div>
<p style="margin-left:20px;margin-right:20px;">Alright, this AuthHandler function is our middleware for performing authentication and authorizing our claims. This takes our endpoint that we specify below, and our gin.Context.
It takes the gin.Context and fetches the Authorization header. It grabs the token from the header, and searches for the endpoint claim that matches our endpoint call. If it is there, it
calls the endpoint function like we expect. Otherwise, it returns a 401 response. Below is how we use this middleware logic in our gin application.
</p>
<div class="code-container"style="height: 350px;margin-bottom: 20px;">
<code class="code-block" style="height: 320px;width: 700px;">
func main() {
router := gin.Default()
router.POST("/authenticate", authenticate)
router.GET("/users", auth.AuthHandler(users))//This is our new line for middleware
router.Run("localhost:1234")
}
func users(c *gin.Context) {
users := []string{
"Janzen", "John Doe", "Jane Doe", "Stewie",
}
c.IndentedJSON(http.StatusOK, gin.H{"users": users})
}
</code>
</div>
<p>This code ensures that when we call the "/users" endpoint, the first thing we do is pass our function and context to our AuthHandler middleware.
If everything is valid with our token and our claims, we can then proceed to call our users function which will return a list of users for us.
</p>
<p>If we want to verify that this works, we can create a new endpoint that uses our middleware without actually having the claim added to our "Endpoints" claim.
Since our middleware AuthHandler function will never find our endpoint claim, it will return a 401 status indicating that our request is unauthorized.
</p>
</div>
</body>
</html>