Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Guide] - Validate Auth token using Fiber #281

Closed
imchivaa opened this issue Apr 3, 2024 · 16 comments
Closed

[Guide] - Validate Auth token using Fiber #281

imchivaa opened this issue Apr 3, 2024 · 16 comments

Comments

@imchivaa
Copy link

imchivaa commented Apr 3, 2024

Hello,

I am using Fiber with Clerk and i like to validate the token sending by the frontend. The documentation shows validation using mux, how do i apply this to Fiber?

Anyone implemented this before would share some insights? Thanks.

@imchivaa
Copy link
Author

imchivaa commented Apr 4, 2024

I tried something like this, but got err: missing json web key, need to set JWK in the params. May i am missing out something here.

main.go

app.Use(
	healthcheck.New(),
	logger.New(),
	cors.New(),
	middleware.AuthMiddleware(),
)

auth_middleware.go

import (
	"fmt"
	"net/http"
	"os"
	"strings"

	"github.com/clerk/clerk-sdk-go/v2"
	"github.com/clerk/clerk-sdk-go/v2/jwt"
	"github.com/gofiber/fiber/v2"
	"github.com/imchivaa/mindful-backend/responses"
)

func AuthMiddleware() fiber.Handler {
	return func(c *fiber.Ctx) error {
		clerk.SetKey(os.Getenv("CLERK_SECRET_KEY"))

		headerAuth := c.Request().Header.Peek("Authorization")
		authTokenBearer := string(headerAuth[:])
		authToken := strings.TrimPrefix(authTokenBearer, "Bearer ")

		_, err := jwt.Verify(c.Context(), &jwt.VerifyParams{
			Token: authToken,
		})

		fmt.Println("err: " + err.Error())

		if err != nil {
			return c.Status(http.StatusUnauthorized).JSON(responses.AuthResponse{Status: http.StatusUnauthorized, Message: "unauthorized"})
		}

		return c.Next()
	}
}

@imchivaa
Copy link
Author

imchivaa commented Apr 4, 2024

tried a diff way, i can see the Context have the header Authorization with the Bearer value, but ok boolean always return false even the JWT token is not expired.

func main() {
	app := fiber.New()
	app.Use(
		healthcheck.New(),
		logger.New(),
		cors.New(),
		withHeaderAuthorizationHandlerMiddleware(),
	)
	routes.TodoRoute(app)
	app.Listen(os.Getenv("HOST"))
}

func withHeaderAuthorizationHandlerMiddleware() fiber.Handler {
	return func(c *fiber.Ctx) error {
		_, ok := clerk.SessionClaimsFromContext(c.Context())
		if !ok {
			return c.Status(http.StatusUnauthorized).JSON(responses.AuthResponse{Status: http.StatusUnauthorized, Message: "unauthorized"})
		}
		return c.Next()
	}
}

@imchivaa
Copy link
Author

imchivaa commented Apr 4, 2024

Okay, i got it working, but not sure if this is the best practice or not.

import (
	"net/http"
	"os"
	"strings"

	"mindful/responses"
	"mindful/routes"

	"github.com/clerk/clerk-sdk-go/v2"
	clerkInc "github.com/clerkinc/clerk-sdk-go/clerk"
	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/fiber/v2/middleware/cors"
	"github.com/gofiber/fiber/v2/middleware/healthcheck"
	"github.com/gofiber/fiber/v2/middleware/logger"
)

// Main entry point of the GoFiber application
func main() {
	// Initialize Fiber
	app := fiber.New()

	// Apply Clerk secret key
	clerk.SetKey(os.Getenv("CLERK_SECRET_KEY"))

	// Apply some middlewares
	app.Use(
		healthcheck.New(),
		logger.New(),
		cors.New(),
		authorizationMiddleware(),
	)

	// Register all our routes
	routes.TodoRoute(app)

	// Start the application
	app.Listen(os.Getenv("HOST"))
}

func authorizationMiddleware() fiber.Handler {
	return func(c *fiber.Ctx) error {
		client, _ := clerkInc.NewClient(os.Getenv("CLERK_SECRET_KEY"))

		headerAuth := c.Request().Header.Peek("Authorization")
		authTokenBearer := string(headerAuth[:])
		authToken := strings.TrimPrefix(authTokenBearer, "Bearer ")

		sessClaims, err := client.VerifyToken(authToken)
		if err != nil {
			return c.Status(http.StatusUnauthorized).JSON(responses.AuthResponse{Status: http.StatusUnauthorized, Message: "unauthorized"})
		}

		_, err = client.Users().Read(sessClaims.Claims.Subject)
		if err != nil {
			panic(err)
		}
		return c.Next()
	}
}

@gkats
Copy link
Member

gkats commented Apr 4, 2024

Hi @imchivaa, unfortunately I'm not familiar with how the Fiber framework works.

One thing I'm noticing in your last snippet however is that you're mixing the v1 version of the Clerk Go SDK with v2.

image

There should probably only be one version active. I'd suggest sticking with v2.

Regarding the middleware, hopefully our own middleware might provide some guidelines on what needs to be done.

func WithHeaderAuthorization(opts ...AuthorizationOption) func(http.Handler) http.Handler {

The idea is that you need to get the Bearer token from the Authorization header and call jwt.Verify to make sure that it's a valid Clerk token.

Without knowing how Fiber works, I wonder if there's a way that you can adapt a "plain middleware" like WithHeaderAuthorization to become a "Fiber middleware". This way you don't have to reimplement the functionality.

Hope the above helps, sorry I cannot offer more assistance as I'm unfamiliar with Fiber.

@imchivaa
Copy link
Author

imchivaa commented Apr 4, 2024

Hi @gkats ,

Thanks for the feedback, i tried by following the v2 guide (https://clerk.com/docs/references/go/verifying-sessions#manually-verify-a-session-token) but i getting this error when verifying the token: err: missing json web key, need to set JWK in the params.

I think is this snippet from the doc requires the JWK. To be honest, i am also new to Go and Fiber. I am more familiar to Next.js but want to try something new.

claims, err := jwt.Verify(r.Context(), &jwt.VerifyParams{
      Token: sessionToken,
    })

I tried to inject the Clerk middleware with Fiber adaptor to convert the handler, but it keep throwing error during go run, thus, i choose to go with the manual route. This leaves me no choice but mixing the v1 and v2, unless, i can somehow make it work with all v2. 😅

@gkats
Copy link
Member

gkats commented Apr 8, 2024

getting this error when verifying the token: err: missing json web key, need to set JWK in the params

Hello @imchivaa, which version of the Clerk Go SDK are you using? Could you try again with v2.0.2-beta.7 which is the latest version?

I'm running the following code in my "protected" handler (copied from the Clerk docs) and it works fine.

sessionToken := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")
claims, err := jwt.Verify(r.Context(), &jwt.VerifyParams{
  Token: sessionToken,
})

Sorry about the inconvenience with the beta versions, I expect v2 to go GA this week. I realize the beta versioning scheme might be causing some confusion at the moment.

@imchivaa
Copy link
Author

imchivaa commented Apr 9, 2024

Hi @gkats ,

Thanks for the follow up. I confirm it works with beta 7. I was using version v2.0.0. Point it to beta 7 and i able to follow the sample code from Clerk docs.

Thank you again for spending your time on this. 👍

@imchivaa
Copy link
Author

imchivaa commented Apr 9, 2024

But i notice one thing @gkats , with the beta 7, i frequently hit this when generating token using Clerk-Expo package:
go-jose/go-jose/jwt: validation field, token issued in the future (iat)

I didn't get this problems when using v2.0.0. Did you get this when running in your project?

@gkats
Copy link
Member

gkats commented Apr 9, 2024

I confirm it works with beta 7

@imchivaa Glad it worked!

i frequently hit this when generating token using Clerk-Expo package: go-jose/go-jose/jwt: validation field, token issued in the future (iat)

Could you please provide more info into what's happening here? I suspect that there's a clock skew issue between your server and your client, but cannot know for sure.

You can pass a clock to the verification parameters, but I cannot know how it can sync with your client.

@imchivaa
Copy link
Author

imchivaa commented Apr 9, 2024

Thanks for the suggestion @gkats , i will test this our later. I will open a new issue if i found something. Meanwhile, i think we can close this issue since the issue i am facing is resolved now by using the v2.0.2-beta.7.

Thank you for your support and feedback. 👍🏾

@imchivaa imchivaa closed this as completed Apr 9, 2024
@imchivaa
Copy link
Author

I gonna leave this here just in case someone face this issue in the future, a little late on the follow up,
go-jose/go-jose/jwt: validation field, token issued in the future (iat)

Resolve the issue by sync the date and time on Windows that running go with fiber.

adrianhajdin/threads#86 (comment)

  1. Click on "Settings" in the Windows Start menu.
  2. Navigate to "Time & Language" within the Settings menu.
  3. Select "Date & Time" from the Time & Language options.
  4. Click on "Additional Settings."
  5. Finally, select "Sync now" to synchronize your system's date and time settings.

@menottiRicardo
Copy link

Doesn't clerk affect performance?

@zakpaw
Copy link

zakpaw commented Jun 4, 2024

I think you could use fiber adaptor middleware and the clerk WithHeaderAuthorization()

@gj1118
Copy link

gj1118 commented Aug 11, 2024

@imchivaa can you please post sample code here please? I too am facing a similar issue please.
Thanks

@imchivaa
Copy link
Author

@gj1118 , here you go

this is my main.go

package main

import (
	"os"

	"mindful/middlewares"
	"mindful/routes"

	"github.com/clerk/clerk-sdk-go/v2"

	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/fiber/v2/middleware/compress"
	"github.com/gofiber/fiber/v2/middleware/healthcheck"
	"github.com/gofiber/fiber/v2/middleware/logger"
)

// Main entry point
func main() {
	// Initialize Fiber
	app := fiber.New()

	// Apply Clerk secret key
	clerk.SetKey(os.Getenv("CLERK_SECRET_KEY"))

	// Apply some middlewares
	app.Use(
		middlewares.AuthorizationMiddlewareV1()
	)

	// Start the application
	app.Listen(os.Getenv("HOST"))
}

below is my auth_middleware.go

package middlewares

import (
	"log"
	"net/http"
	"os"
	"strings"

	"mindful/responses"

	"github.com/clerk/clerk-sdk-go/v2/jwt"
	"github.com/gofiber/fiber/v2"
)

// Middleware that will intercept the Authorization header and validate the JWT
func AuthorizationMiddlewareV1() fiber.Handler {
	unauthorizedMsg := "unauthorized"

	return func(c *fiber.Ctx) error {
		if os.Getenv("APP_ENV") == "development" {
			log.Println("Skip token validation on local env")
			c.Request().Header.Set("userId", "user_2f2YcXoXqC2gFRKPt8mnuD8AHan")
			return c.Next()
		}

		headerAuth := c.Request().Header.Peek("Authorization")
		if headerAuth == nil {
			log.Println("Missing authorization header")
			return c.Status(http.StatusUnauthorized).JSON(responses.AuthResponse{Status: http.StatusUnauthorized, Message: unauthorizedMsg})
		}
		
		sessionTokenBearer := string(headerAuth[:])
		if sessionTokenBearer == "" {
			log.Println("Missing Bearer token")
			return c.Status(http.StatusUnauthorized).JSON(responses.AuthResponse{Status: http.StatusUnauthorized, Message: unauthorizedMsg})
		}

		sessionToken := strings.TrimPrefix(sessionTokenBearer, "Bearer ")
		if sessionToken == "" {
			log.Println("Missing token after Bearer")
			return c.Status(http.StatusUnauthorized).JSON(responses.AuthResponse{Status: http.StatusUnauthorized, Message: unauthorizedMsg})
		}

		claims, err := jwt.Verify(c.Context(), &jwt.VerifyParams{
			Token: sessionToken,
		})

		if err != nil {
			log.Println("Clerk auth verify token error: ", err)
			return c.Status(http.StatusUnauthorized).JSON(responses.AuthResponse{Status: http.StatusUnauthorized, Message: unauthorizedMsg})
		}

		if claims == nil {
			log.Println("Clerk auth session claim error: ", err)
			return c.Status(http.StatusUnauthorized).JSON(responses.AuthResponse{Status: http.StatusUnauthorized, Message: unauthorizedMsg})
		}

		log.Println(claims.ActiveOrganizationRole)

		c.Request().Header.Set("userId", claims.Subject)

		return c.Next()
	}
}

@gj1118
Copy link

gj1118 commented Aug 13, 2024

Thanks @imchivaa . So you are not using clerk for validating the tokens in your middlware? If so how did you handle the refresh tokens ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants