Monday, May 13, 2024

Create and Parse JWT in GO



 


In this post we will review how to create and parse JWT in GO.


We use the "user" claim to specify the user. We create a signed JWT, and then parse it back to get the user from the JWT.


package jwtparsing

import (
"fmt"
"github.com/golang-jwt/jwt"
"testing"
"time"
)

const userClaim = "user"

func TestValidation(t *testing.T) {
signedToken := createJwtToken("myUser1")
fmt.Printf("token is: %v\n", signedToken)

user := parseJwtToken(signedToken)
fmt.Printf("user is: %v\n", user)
}


To create a JWT we should use a secret know only at the server side. The JWT is based on a specific signing method that should be supported on the client side as well.


func createJwtToken(
user string,
) string {
var secretKey = []byte("secret-key")

token := jwt.NewWithClaims(
jwt.SigningMethodHS256,
jwt.MapClaims{
userClaim: user,
"exp": time.Now().Add(time.Hour * 24).Unix(),
})

signedToken, err := token.SignedString(secretKey)
if err != nil {
panic(err)
}
return signedToken
}


In this case we choose to parse the JWT without verifying it. It is important to understand the content of the JWT is not encrypted by only signed, hence we can parse it anywhere we want, without verification of the signature. This is ok only if we know that someone had already previously verified it, otherwise our system is broken.


func parseJwtToken(
signedToken string,
) string {
var jwtParser jwt.Parser
claims := jwt.MapClaims{}
_, _, err := jwtParser.ParseUnverified(signedToken, claims)
if err != nil {
panic(err)
}
jwtValue := claims[userClaim]
user, ok := jwtValue.(string)
if !ok {
panic("convert claim failed")
}

return user
}


The output of the test is:


=== RUN   TestValidation
token is: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTU2NzA3NDcsInVzZXIiOiJteVVzZXIxIn0.ynQLZ47Eup60OgkE0vbOtvii1g3MVSv4MxnvEE4Cv1U
user is: myUser1
--- PASS: TestValidation (0.00s)






Sunday, May 5, 2024

Sending a Multipart Request


Multipart request is usually used by browsers to upload files to the server. Additional parameters can be also specified as part of the body. In this post we show an example of building a multipart request.


The following is an example to build a multipart HTTP request:


const Boundary = "MyBoundary"
const fileName = "file"

func CreateFormRequest(
parameters map[string]string,
fileData string,
) string {
var stringBuilder strings.Builder
for key, value := range parameters {
stringBuilder.WriteString("--" + Boundary + "\n")
line := fmt.Sprintf(`Content-Disposition: form-data; name="%v"`, key)
stringBuilder.WriteString(line + "\n\n")
stringBuilder.WriteString(value)
stringBuilder.WriteString("\n")

}
stringBuilder.WriteString("--" + Boundary + "\n")
line := fmt.Sprintf(`Content-Disposition: form-data; name="%v"; filename="%v"`, fileName, fileName)
stringBuilder.WriteString(line + "\n")
stringBuilder.WriteString("Content-Type: text/plain\n")
stringBuilder.WriteString("\n")
stringBuilder.WriteString(fileData)
stringBuilder.WriteString("\n")
stringBuilder.WriteString("--" + Boundary + "--\n")

return stringBuilder.String()
}


Note the HTTP request should specify the multipart boundary string in the content type header:


headers := map[string]string{
"Content-Type": "multipart/form-data; boundary=" + Boundary,
}


On the server side we will use a struct to read the multipart content:

type FormContent struct {
File *multipart.FileHeader `form:"file"`
Values map[string]string
}


The server will read the multipart using a dedicated bind function:


func NewBindFile(originalBinder echo.Binder) echo.Binder {
return BindFunc(func(i interface{}, ctx echo.Context) error {
contentType := ctx.Request().Header.Get(echo.HeaderContentType)
if !strings.HasPrefix(contentType, echo.MIMEApplicationForm) && !strings.HasPrefix(contentType, echo.MIMEMultipartForm) {
return originalBinder.Bind(i, ctx)
}

formContent, ok := i.(*web.FormContent)
if !ok {
return fmt.Errorf("fail casting to form content")
}

form, err := ctx.MultipartForm()
if err != nil {
return err
}

formContent.File = form.File["file"][0]
formContent.Values = make(map[string]string)
for key, value := range form.Value {
formContent.Values[key] = value[0]
}

return nil
})
}


The related request handler can get the file data using:

fileData := web.ReadFormFile(formContent.File)
func ReadFormFile(fileHeader *multipart.FileHeader) []byte {
if fileHeader == nil {
return nil
}
file, err := fileHeader.Open()
kiterr.RaiseIfError(err)
body, err := io.ReadAll(file)
kiterr.RaiseIfError(err)
return body
}