The v2
version of the Clerk Go SDK is a complete rewrite and introduces
a lot of breaking changes.
The minimum supported Go version for the v2
version of the Clerk Go SDK is 1.19
.
API operations in v1
of the Clerk Go SDK are organized by service. There are
different services for each API and all services are properties of a single
clerk.Client
.
Let's see a concrete example. Assume that we want to create an organization and then list all available organizations.
Here's how we would do it in v1
.
import (
"github.com/clerkinc/clerk-sdk-go"
)
func main() {
client, err := clerk.NewClient("sk_live_XXX")
if err != nil {
// handle error
}
// Create an organization
org, err := client.Organizations().Create(clerk.CreateOrganizationParams{
Name: "Acme Inc",
})
if err != nil {
if errResp, ok := err.(*clerk.ErrorResponse); ok {
// Access the API errors
errResp.Errors
}
}
// List all organizations, limit results to one.
limit := 1
orgs, err := client.Organizations().ListAll(clerk.ListAllOrganizationsParams{
Limit: &limit,
})
if err != nil {
// handle the error
}
if orgs.TotalCount > 0 {
// Get the first organization in the list
org = orgs.Data[0]
}
}
The v2
version breaks away of the client and services pattern seen above.
In v2
, every API has its own package, following a resource-based structure.
There are two ways to call API operations in v2
: with or without a client. Let's see both approaches.
In most cases, you'll only have to deal with a single API key in your project.
import (
"context"
"github.com/clerk/clerk-sdk-go/v2"
"github.com/clerk/clerk-sdk-go/v2/organization"
)
func main() {
ctx := context.Background()
clerk.SetKey("sk_live_XXX")
// Create an organization
org, err := organization.Create(ctx, &organization.CreateParams{
Name: clerk.String("Acme Inc"),
})
if err != nil {
if apiErr, ok := err.(*clerk.APIErrorResponse); ok {
// Access the API errors and additional information
apiErr.TraceID
apiErr.Error()
apiErr.Response.RawJSON
}
}
// List all organizations, limit results to one.
params := &organization.ListParams{}
params.Limit = clerk.Int64(1)
list, err := organization.List(ctx, params)
if err != nil {
// handle the error
}
if list.TotalCount > 0 {
// Get the first organization in the list
org = list.Organizations[0]
}
}
When you have to deal with more than one API keys in your project, or need more flexibility.
import (
"context"
"github.com/clerk/clerk-sdk-go/v2"
"github.com/clerk/clerk-sdk-go/v2/organization"
)
func main() {
ctx := context.Background()
config := &clerk.ClientConfig{}
config.Key = "sk_live_XXX"
client := organization.NewClient(config)
// Create an organization
org, err := client.Create(ctx, &organization.CreateParams{
Name: clerk.String("Acme Inc"),
})
if err != nil {
if apiErr, ok := err.(*clerk.APIErrorResponse); ok {
// Access the API errors and additional information
apiErr.TraceID
apiErr.Error()
apiErr.Response.RawJSON
}
}
// List all organizations, limit results to one.
params := &organization.ListParams{}
params.Limit = clerk.Int64(1)
list, err := organization.List(ctx, params)
if err != nil {
// handle the error
}
if list.TotalCount > 0 {
// Get the first organization in the list
org = list.Organizations[0]
}
}
A lot of APIs support operations where a resource list is returned. In v2
of the Clerk Go SDK,
the return types of list operations are similar. They always contain the total count of resources
available in the server, while the (sometimes filtered or paginated) results are returned in a slice.
Here's an example. Replace $resource$
with a resource (package) name.
import (
"github.com/clerk/clerk-sdk-go/v2"
"github.com/clerk/clerk-sdk-go/v2/$resource$"
)
ctx := context.Background()
list, err := $resource$.List(ctx, &$resource$.ListParams{})
// If $resource$ was user, the following line would read
// fmt.Println(list.TotalCount, list.Users)
fmt.Println(list.TotalCount, list.$resource$s)
In the library's v1
version, some operations would return the same type, while others wouldn't.
The v2
version moves to a more resource-oriented approach. Each API resource has a type and all types
are defined in the clerk
package.
This means there are types for clerk.User
and clerk.Domain
and these types are returned by
the Users and Domains API operations respectively.
All types were consolidated and updated to match the currently supported request parameters and responses of the Clerk Backend API. Many fields have been renamed and a lot of new fields have been added. Deprecated struct fields have been dropped.
The v2
version of Clerk SDK Go introduces another important change about types that can be used as API operation
parameters. Every field for these structs is a pointer.
The v2
version of the library introduces helper functions to cast various type values to pointers. These are:
clerk.String
clerk.Bool
clerk.Int64
clerk.JSONRawMessage
Using the helpers above, here's how you can invoke an API operation with a *Params
struct.
domain.Create(context.Background(), &domain.CreateParams{
Name: clerk.String("clerk.com"),
IsSatellite: clerk.Bool(true),
})
You can explicitly pass zero values with clerk.String("")
or clerk.Int64(0)
.
All API operations receive a context.Context
as the first argument.
The v1
version of the Clerk Go SDK supports two HTTP middleware functions that can handle authentication with Clerk.
These are WithSessionV2
and RequireSessionV2
.
The middleware in v1
supports authentication with a bearer token in the HTTP request headers, falling back to cookie
based authentication if the "Authorization" header is missing.
In a similar way, the v2
version of the library also provides two middleware functions that can handle authentication
with Clerk; WithHeaderAuthorization
and RequireHeaderAuthorization
.
As the name implies, the new middleware support only authentication with a bearer token, that needs to be present in the "Authorization" header of the HTTP request.
Cookie based authentication is not supported at all by the v2
version of the library.
Usage has also changed between v1
and v2
, as have the different options that the middleware support.
In v1
, here's how you would use the RequireSessionV2
middleware to get access to the session token claims in HTTP handlers.
import (
"net/http"
"github.com/clerkinc/clerk-sdk-go"
)
func main() {
client, err := clerk.NewClient("sk_live_XXX")
if err != nil {
panic(err)
}
mux := http.NewServeMux()
mux.Handle("/session", clerk.RequireSessionV2(client)(http.HandlerFunc(handleSession)))
http.ListenAndServe(":3000", mux)
}
func handleSession(w http.ResponseWriter, r *http.Request) {
sessionClaims, ok := clerk.SessionFromContext(r.Context())
if ok {
// claims contain session information
} else {
// there is no active session (non-authenticated user)
}
}
Here's the same HTTP server written for v2
. Please note that only header based authentication with a bearer token is
supported by the RequireHeaderAuthorization
middleware. Cookie based authentication is not supported.
import (
"net/http"
"github.com/clerk/clerk-sdk-go/v2"
clerkhttp "github.com/clerk/clerk-sdk-go/v2/http"
)
func main() {
clerk.SetKey("sk_live_XXX")
mux := http.NewServeMux()
mux.Handle("/session", clerkhttp.RequireHeaderAuthorization()(http.HandlerFunc(handleSession)))
http.ListenAndServe(":3000", mux)
}
func handleSession(w http.ResponseWriter, r *http.Request) {
sessionClaims, ok := clerk.SessionClaimsFromContext(r.Context())
if ok {
// claims contain session information
} else {
// there is no active session (non-authenticated user)
}
}
If you're dealing with multiple Clerk API keys, you can pass a jwks.Client
to the RequireHeaderAuthorization
middleware.
import (
"net/http"
"github.com/clerk/clerk-sdk-go/v2"
clerkhttp "github.com/clerk/clerk-sdk-go/v2/http"
"github.com/clerk/clerk-sdk-go/v2/jwks"
)
func main() {
config := &clerk.ClientConfig{}
config.Key = "sk_live_XXX"
client := jwks.NewClient(config)
mux := http.NewServeMux()
withAuth := clerkhttp.RequireHeaderAuthorization(
clerkhttp.JWKSClient(client),
)
mux.Handle("/session", withAuth(http.HandlerFunc(handleSession)))
http.ListenAndServe(":3000", mux)
}
func handleSession(w http.ResponseWriter, r *http.Request) {
sessionClaims, ok := clerk.SessionClaimsFromContext(r.Context())
if ok {
// claims contain session information
} else {
// there is no active session (non-authenticated user)
}
}
All available middleware options are preserved in the v2
version of the library, but they have been renamed.
Name in v1 | Name in v2 |
---|---|
WithAuthorizedParty |
AuthorizedParty and AuthorizedPartyMatches |
WithLeeway |
Leeway |
WithJWTVerificationKey |
JSONWebKey |
WithSatelliteDomain |
Satellite |
WithProxyURL |
ProxyURL |
WithCustomClaims |
CustomClaimsConstructor |
n/a | Clock |
n/a | JWKSClient |
The clerk.VerifyToken
method in version v1
of the Clerk Go SDK has been renamed to jwt.Verify
in v2
.
The method accepts the same parameters, with two important differences.
- The JSON web key with which the token will be verified is a required parameter.
- The method will not cache the JSON web key.
In the v1
version, the clerk.VerifyToken
method would trigger an HTTP request to the Clerk Backend API to
fetch the JSON web key and would cache its value for one hour.
The new jwt.Verify
method that is included in v2
accepts the JSON web key that is used to verify the token. It is up
to the caller to get access to the key and pass it to jwt.Verify
.
Please note that both HTTP middleware functions, WithHeaderAuthorization
and RequireHeaderAuthorization
will cache
the JSON web key by default.
You can fetch the JSON web key with the jwt.GetJSONWebKey
method.
ctx := context.Background()
token := "the-clerk-session-jwt"
decoded, err := jwt.Decode(ctx, &jwt.DecodeParams{Token: token})
if err != nil {
panic(err)
}
// Fetch the JSON web key for your instance.
// It is advised to cache the JSON web key until your instance secret
// key changes.
jwk, err := jwt.GetJSONWebKey(ctx, &jwt.GetJSONWebKeyParams{
KeyID: decoded.KeyID,
})
if err != nil {
panic(err)
}
claims, err := jwt.Verify(ctx, &jwt.VerifyParams{
Token: token,
JWK: jwk,
})
If you don't have access to the JSON web key, you can
Please let us know about your experience upgrading to the v2
version of the Clerk Go SDK.
You can reach us via various support channels.
If you notice any bugs or omissions, feel free to open an issue on Github.