Skip to content

Clarify manual session verification examples with caching and token sources with Go #2431

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

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
219 changes: 170 additions & 49 deletions docs/references/go/verifying-sessions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ There are two ways to verify a session with Clerk in your Go application:
<br />If you want to verify a session in an HTTP context, it is recommended to use Clerk middleware. Clerk middleware guarantees better performance and efficiency by making the minimum necessary requests to the Clerk Backend API.

- [Manually verifying the session token](#manually-verify-a-session-token)
<br />If you want to verify a session in a non-HTTP context, or if you would like total control over the verification process, you can verify the session token on your own.
<br />If you want to verify a session in a non-HTTP context, or if you want full control over the verification process, you can verify the session token manually.

## Use Clerk middleware to verify a session

Expand Down Expand Up @@ -77,69 +77,190 @@ func protectedRoute(w http.ResponseWriter, r *http.Request) {

## Manually verify a session token

Verifying a Clerk session manually is useful when you want to verify a session in a non-HTTP context, or if you would like total control over the verification process. With the solution above, Clerk middleware makes the minimum necessary requests to the Clerk Backend API. With this solution, you must be mindful of API rate limits.
Manually verifying a Clerk session is useful when you want to verify a session in a non-HTTP context, or when you want full control over the verification process. Verifying a session token requires providing a JSON Web Key (JWK). With the solution above, Clerk middleware fetches the JSON Web Key **once** and automatically caches it for you, so it makes the minimum necessary requests to the Clerk Backend API.

Verifying a session token requires providing a JSON Web Key. When using Clerk middleware to verify a session, it fetches the JSON Web Key once and caches it for you. However, when manually verifying a session, there's no caching layer for the JSON Web Key.
However, when manually verifying a session, you're reponsible for fetching and caching the JSON Web Key yourself, as there is no caching layer for the key. For that reason, you must be mindful of API rate limits and unnecessary requests.

Clerk Go SDK provides a set of functions for decoding and verifying JWTs, as well as fetching JSON Web Keys. It is recommended to cache your JSON Web Key and invalidate the cache only when a replacement key is generated.
The Clerk Go SDK provides a set of functions for decoding and verifying JWTs, as well as fetching JSON Web Keys. It is recommended to cache your JSON Web Key and invalidate the cache only when a replacement key is generated.

The following example demonstrates how to manually verify a session token. If the user tries accessing the route and their session token is valid, the user's ID and banned status will be returned in the response.
Before verifying the session token, you will need to retrieve it from the client. Please note that Clerk sessions tokens can be retrieved either from the `__session` cookie for same-origin requests or from the `Authorization` header for cross-origin requests.

```go {{ filename: 'main.go' }}
package main
The following example demonstrates how to manually verify a session token. If the user tries accessing the route and their session token is valid, the user's ID and banned status will be returned in the response. Use the following tabs to select your preferred method of retrieving the session token.

import (
"fmt"
"net/http"
"strings"
<Tabs items={["Using the Authorization Header", "Using the __session cookie"]}>
<Tab>
```go {{ filename: 'main.go', collapsible: true}}
package main

"github.com/clerk/clerk-sdk-go/v2"
"github.com/clerk/clerk-sdk-go/v2/jwt"
"github.com/clerk/clerk-sdk-go/v2/user"
)
import (
"fmt"
"net/http"
"strings"

func main() {
clerk.SetKey("{{secret}}")
"github.com/clerk/clerk-sdk-go/v2"
"github.com/clerk/clerk-sdk-go/v2/jwks"
"github.com/clerk/clerk-sdk-go/v2/jwt"
"github.com/clerk/clerk-sdk-go/v2/user"
)

mux := http.NewServeMux()
mux.HandleFunc("/", publicRoute)
mux.HandleFunc("/protected", protectedRoute)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", publicRoute)

http.ListenAndServe(":3000", mux)
}
config := &clerk.ClientConfig{}
config.Key = clerk.String("{{secret}}")
jwksClient := jwks.NewClient(config)
mux.HandleFunc("/protected", protectedRoute(jwksClient))

func publicRoute(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`{"access": "public"}`))
}
http.ListenAndServe(":3000", mux)
}

func protectedRoute(w http.ResponseWriter, r *http.Request) {
// Get the session JWT from the Authorization header
sessionToken := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")

// Verify the session
claims, err := jwt.Verify(r.Context(), &jwt.VerifyParams{
Token: sessionToken,
})
if err != nil {
// handle the error
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(`{"access": "unauthorized"}`))
return
}
func publicRoute(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`{"access": "public"}`))
}

usr, err := user.Get(r.Context(), claims.Subject)
if err != nil {
// handle the error
}
fmt.Fprintf(w, `{"user_id": "%s", "user_banned": "%t"}`, usr.ID, usr.Banned)
}
```
func protectedRoute(jwksClient *jwks.Client) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
// Get the session JWT from the Authorization header
sessionToken := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")

// Decode the session JWT so that we can find the key ID
unsafeClaims, err := jwt.Decode(r.Context(), &jwt.DecodeParams{
Token: sessionToken,
})
if err != nil {
// handle the error
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(`{"access": "unauthorized"}`))
return
}

// Fetch the JSON Web Key
jwk, err := jwt.GetJSONWebKey(r.Context(), &jwt.GetJSONWebKeyParams{
KeyID: unsafeClaims.KeyID,
JWKSClient: jwksClient,
})
if err != nil {
// handle the error
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(`{"access": "unauthorized"}`))
return
}

// Verify the session
claims, err := jwt.Verify(r.Context(), &jwt.VerifyParams{
Token: sessionToken,
JWK: jwk,
})
if err != nil {
// handle the error
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(`{"access": "unauthorized"}`))
return
}

usr, err := user.Get(r.Context(), claims.Subject)
if err != nil {
// handle the error
}
fmt.Fprintf(w, `{"user_id": "%s", "user_banned": "%t"}`, usr.ID, usr.Banned)
}
}
```
</Tab>

<Tab>
```go {{ filename: 'main.go', collapsible: true }}
package main

import (
"fmt"
"net/http"

"github.com/clerk/clerk-sdk-go/v2"
"github.com/clerk/clerk-sdk-go/v2/jwks"
"github.com/clerk/clerk-sdk-go/v2/jwt"
"github.com/clerk/clerk-sdk-go/v2/user"
)

func main() {
config := &clerk.ClientConfig{}
config.Key = clerk.String("{{secret}}")
jwksClient := jwks.NewClient(config)

mux := http.NewServeMux()
mux.HandleFunc("/", publicRoute)
mux.HandleFunc("/protected", protectedRoute(jwksClient))

http.ListenAndServe(":3000", mux)
}

func publicRoute(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`{"access": "public"}`))
}

func protectedRoute(jwksClient *jwks.Client) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Get the session JWT from the session cookie
cookie, err := r.Cookie("__session")
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(`{"access": "unauthorized"}`))
return
}
sessionToken := cookie.Value

// Decode the session JWT so that we can find the key ID
unsafeClaims, err := jwt.Decode(r.Context(), &jwt.DecodeParams{
Token: sessionToken,
})
if err != nil {
// handle the error
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(`{"access": "unauthorized"}`))
return
}

// Fetch the JSON Web Key
jwk, err := jwt.GetJSONWebKey(r.Context(), &jwt.GetJSONWebKeyParams{
KeyID: unsafeClaims.KeyID,
JWKSClient: jwksClient,
})
if err != nil {
// handle the error
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(`{"access": "unauthorized"}`))
return
}

// Verify the session
claims, err := jwt.Verify(r.Context(), &jwt.VerifyParams{
Token: sessionToken,
JWK: jwk,
})
if err != nil {
// handle the error
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(`{"access": "unauthorized"}`))
return
}

usr, err := user.Get(r.Context(), claims.Subject)
if err != nil {
// handle the error
}
fmt.Fprintf(w, `{"user_id": "%s", "user_banned": "%t"}`, usr.ID, usr.Banned)
}
}
```
</Tab>
</Tabs>

If you need to verify session tokens frequently, it's recommended to cache the JSON Web Key for your instance in order to avoid making too many requests to the Clerk Backend API.

The following example includes the same code as above, with the addition of demonstrating a possible way to store your JSON Web Key. The example is meant to serve as a guide; implementation may vary depending on your needs.
The following example uses the same manual verification flow as above, with the addition of demonstrating a possible way to cache your JSON Web Key to avoid repeated requests to Clerk. The example is meant to serve as a guide; implementation may vary depending on your needs.

```go {{ filename: 'main.go' }}
```go {{ filename: 'main.go', mark: [[18, 24], [43, 44], [69, 71], [93, 104]], collapsible: true}}
package main

import (
Expand Down Expand Up @@ -185,7 +306,7 @@ func protectedRoute(jwksClient *jwks.Client, store JWKStore) func(http.ResponseW
// Attempt to get the JSON Web Key from your store.
jwk := store.GetJWK()
if jwk == nil {
// Decode the session JWT so that we can find the key ID.
// Decode the session JWT so that we can find the key ID
unsafeClaims, err := jwt.Decode(r.Context(), &jwt.DecodeParams{
Token: sessionToken,
})
Expand Down
Loading