Skip to content

Commit

Permalink
Merge pull request #2 from janzenbupa/jwt-golang
Browse files Browse the repository at this point in the history
Adding a page for jwt with golang and updating the other pages to con…
  • Loading branch information
janzenbupa authored Mar 14, 2024
2 parents 96effd6 + 98b075f commit ce865a7
Show file tree
Hide file tree
Showing 3 changed files with 308 additions and 1 deletion.
1 change: 1 addition & 0 deletions dynamodb-with-go.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ <h2 class="h2-title">software engineering</h2>
<ul>
<li><a class="a-look" href="index.html">home</a></li>
<li><a class="a-look" href="dynamodb-with-go.html">DynamoDb with Golang</a></li>
<li><a class="a-look" href="jwt-with-go.html">JWT with Golang</a></li>
</ul>
</div>
</div>
Expand Down
3 changes: 2 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@
</head>
<body>
<div class="nav-bar-header">
<h2 class="h2-title">Janzen is a software engineer</h2>
<h2 class="h2-title">Janzen's programming</h2>
<p></p>
</div>
<div class="nav-bar">
<ul>
<li><a class="a-look" href="index.html">home</a></li>
<li><a class="a-look" href="dynamodb-with-go.html">DynamoDb with Golang</a></li>
<li><a class="a-look" href="jwt-with-go.html">JWT with Golang</a></li>
</ul>
</div>
<div class="body-padding">
Expand Down
305 changes: 305 additions & 0 deletions jwt-with-go.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,305 @@
<!DOCTYPE html>
<html>
<head>
<title>janzenbupa.github.io</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</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><a class="a-look" href="dynamodb-with-go.html">DynamoDb with Golang</a></li>
<li><a class="a-look" href="jwt-with-go.html">JWT with Golang</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>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>

<style>
.p-center{
text-align: center;
}

.h2-title{
text-align: center;
color: #e6e6e6;
font-family: "Retina", Arial, Helvetica, sans-serif;
}

a{
font-family: "Retina", Arial, Helvetica, sans-serif;
}

a:link{
text-decoration: none;
color: Violet;
}

a:visited{
text-decoration: none;
color: Violet;
}

a:active{
text-decoration: none;
color: Violet;
}

.nav-bar-header{
background-color: #1f1f2e/*SlateBlue*/;
overflow: hidden;
margin: 0px;
border-bottom: 0.5px solid #676798/*LightSlateGray*/;
}

.nav-bar{
background-color: #676798;
overflow: hidden;
display: block;
padding: 0%;
text-decoration: none;
}

body{
margin: 0%;
}

.body-padding{
margin-left: 10%;
margin-right: 10%;
}


/*list heading nav bar*/
ul {
list-style-type: none;
margin: 0;
padding: 0;
overflow: hidden;
background-color: #676798;
}

li {
float: left;
}

li a {
display: block;
color: Violet;
text-align: center;
padding: 14px 16px;
text-decoration: none;
}

li:active{
background-color: #1f1f2e;
}

li:hover{
background-color: #1f1f2e;
}

code {
background: hsl(0, 3%, 92%);
}

pre {
/* white-space: pre-wrap;
background: hsl(30,80%,90%); */
}

.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>

0 comments on commit ce865a7

Please sign in to comment.