Skip to content

Latest commit

 

History

History
404 lines (303 loc) · 12.7 KB

README.md

File metadata and controls

404 lines (303 loc) · 12.7 KB


Clerk Go SDK

The official Clerk Go client library for accessing the Clerk Backend API.

Go Reference chat on Discord documentation twitter

Migrating from v1?

Check out the upgrade guide.

Requirements

  • Go 1.19 or later.

Installation

If you are using Go Modules and have a go.mod file in your project's root, you can import clerk-sdk-go directly.

import (
    "github.com/clerk/clerk-sdk-go/v2"
)

Alternatively, you can go get the package explicitly.

go get -u github.com/clerk/clerk-sdk-go/v2

Usage

For details on how to use this module, see the Go Documentation.

The library has a resource-based structure which follows the way the Clerk Backend API resources are organized. Each API supports specific operations, like Create or List. While operations for each resource vary, a similar pattern is applied throughout the library.

In order to start using API operations the library needs to be configured with your Clerk API secret key. Depending on your use case, there's two ways to use the library; with or without a client.

For most use cases, the API without a client is a better choice.

On the other hand, if you need to set up multiple Clerk API keys, using clients for API operations provides more flexibility.

Let's see both approaches in detail.

Usage without a client

If you only use one API key, you can import the packages required for the APIs you need to interact with and call functions for API operations.

import (
    "github.com/clerk/clerk-sdk-go/v2"
    "github.com/clerk/clerk-sdk-go/v2/$resource$"
)

// Each operation requires a context.Context as the first argument.
ctx := context.Background()

// Set the API key
clerk.SetKey("sk_live_XXX")

// Create
resource, err := $resource$.Create(ctx, &$resource$.CreateParams{})

// Get
resource, err := $resource$.Get(ctx, id)

// Update
resource, err := $resource$.Update(ctx, id, &$resource$.UpdateParams{})

// Delete
resource, err := $resource$.Delete(ctx, id)

// List
list, err := $resource$.List(ctx, &$resource$.ListParams{})
for _, resource := range list.$Resource$s {
    // do something with the resource
}

Usage with a client

If you're dealing with multiple keys, it is recommended to use a client based approach. The API packages for each resource export a Client, which supports all the API's operations. This way you can create as many clients as needed, each with their own API key.

import (
    "github.com/clerk/clerk-sdk-go/v2"
    "github.com/clerk/clerk-sdk-go/v2/$resource$"
)

// Each operation requires a context.Context as the first argument.
ctx := context.Background()

// Initialize a client with an API key
config := &clerk.ClientConfig{}
config.Key = "sk_live_XXX"
client := $resource$.NewClient(config)

// Create
resource, err := client.Create(ctx, &$resource$.CreateParams{})

// Get
resource, err := client.Get(ctx, id)

// Update
resource, err := client.Update(ctx, id, &$resource$.UpdateParams{})

// Delete
resource, err := client.Delete(ctx, id)

// List
list, err := client.List(ctx, &$resource$.ListParams{})
for _, resource := range list.$Resource$s {
    // do something with the resource
}

Here's an example of how the above operations would look like for specific APIs.

import (
    "github.com/clerk/clerk-sdk-go/v2"
    "github.com/clerk/clerk-sdk-go/v2/organization"
    "github.com/clerk/clerk-sdk-go/v2/organizationmembership"
    "github.com/clerk/clerk-sdk-go/v2/user"
)

func main() {
    // Each operation requires a context.Context as the first argument.
    ctx := context.Background()

    // Set the API key
    clerk.SetKey("sk_live_XXX")

    // Create an organization
    org, err := organization.Create(ctx, &organization.CreateParams{
        Name: clerk.String("Clerk Inc"),
    })

    // Update the organization
    org, err = organization.Update(ctx, org.ID, &organization.UpdateParams{
        Slug: clerk.String("clerk"),
    })

    // List organization memberships
    listParams := organizationmembership.ListParams{}
    listParams.Limit = clerk.Int64(10)
    memberships, err := organizationmembership.List(ctx, params)
    if memberships.TotalCount < 0 {
        return
    }
    membership := memberships[0]

    // Get a user
    usr, err := user.Get(ctx, membership.UserID)
}

Accessing API responses

Each resource that is returned by an API operation has a Response field which contains information about the response that was sent from the Clerk Backend API.

The Response contains fields like the the raw HTTP response's headers, the status and the raw response body. See the APIResponse documentation for available fields and methods.

dmn, err := domain.Create(context.Background(), &domain.CreateParams{})
if !dmn.Response.Success() {
    dmn.Response.TraceID
}

Errors

For cases where an API operation returns an error, the library will try to return an APIErrorResponse. The APIErrorResponse type provides information such as the HTTP status code of the response, a list of errors and a trace ID that can be used for debugging.

The APIErrorResponse is an APIResource. You can access the API response for errors as well.

See the APIErrorResponse documentation for available fields and methods.

_, err := user.List(context.Background(), &user.ListParams{})
if apiErr, ok := err.(*clerk.APIErrorResponse); ok {
    apiErr.TraceID
    apiErr.Error()
    apiErr.Response.RawJSON
}

HTTP Middleware

The library provides two functions that can be used for adding authentication with Clerk to HTTP handlers.

Both middleware functions support header based authentication with a bearer token. The token is parsed, verified and its claims are extracted as SessionClaims.

The claims will then be made available in the http.Request.Context for the next handler in the chain. The library provides a helper for accessing the claims from the context, SessionClaimsFromContext.

The two middleware functions are WithHeaderAuthorization and RequireHeaderAuthorization. Their difference is that the RequireHeaderAuthorization calls WithHeaderAuthorization under the hood, but responds with HTTP 403 Forbidden if it fails to detect valid session claims.

Let's see an example of how the middleware can be used.

import (
	"fmt"
	"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.HandleFunc("/", publicRoute)
	protectedHandler := http.HandlerFunc(protectedRoute)
	mux.Handle(
		"/protected",
		clerkhttp.WithHeaderAuthorization()(protectedHandler),
	)

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

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

func protectedRoute(w http.ResponseWriter, r *http.Request) {
	claims, ok := clerk.SessionClaimsFromContext(r.Context())
	if !ok {
		w.WriteHeader(http.StatusUnauthorized)
		w.Write([]byte(`{"access": "unauthorized"}`))
		return
	}
	fmt.Fprintf(w, `{"user_id": "%s"}`, claims.Subject)
}

Both WithHeaderAuthorization and RequireHeaderAuthorization can be customized. They accept various options as functional arguments.

For a comprehensive list of available options check the AuthorizationParams documentation.

Testing

There are various ways to mock the library in your test suite.

Usage without client

If you're using the library without instantiating clients for APIs, you can set the package's Backend with a custom configuration.

  1. Stub out the HTTP client's transport
func TestWithCustomTransport(t *testing.T) {
    clerk.SetBackend(clerk.NewBackend(&clerk.BackendConfig{
        HTTPClient: &http.Client{
            Transport: mockRoundTripper,
        },
    }))
}

type mockRoundTripper struct {}
// Implement the http.RoundTripper interface.
func (r *mockRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
    // Construct and return the http.Response.
}
  1. Use a httptest.Server

Similar to the custom http.Transport approach, you can use the net/http/httptest package's utilities and provide the http.Client to the package's Backend.

func TestWithMockServer(t *testing.T) {
    ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Write the response.
    }))
    defer ts.Close()
    clerk.SetBackend(clerk.NewBackend(&clerk.BackendConfig{
        HTTPClient: ts.Client(),
        URL: &ts.URL,
    }))
}
  1. Implement your own Backend

You can implement your own Backend and set it as the package's default Backend.

func TestWithCustomBackend(t *testing.T) {
    clerk.SetBackend(&customBackend{})
}

type customBackend struct {}
// Implement the Backend interface
func (b *customBackend) Call(ctx context.Context, r *clerk.APIRequest, reader clerk.ResponseReader) error {
    // Construct a clerk.APIResponse and use the reader's Read method.
    reader.Read(&clerk.APIResponse{})
}

Usage with client

If you're already using the library by instantiating clients for API operations, or you need to ensure your test suite can safely run in parallel, you can simply pass a custom http.Client to your clients.

  1. Stub out the HTTP client's transport
func TestWithCustomTransport(t *testing.T) {
    config := &clerk.ClientConfig{}
    config.HTTPClient = &http.Client{
        Transport: mockRoundTripper,
    }
    client := user.NewClient(config)
}

type mockRoundTripper struct {}
// Implement the http.RoundTripper interface.
func (r *mockRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
    // Construct and return the http.Response.
}
  1. Use a httptest.Server

Similar to the custom http.Transport approach, you can use the net/http/httptest package's utilities and provide the http.Client to the API client.

func TestWithMockServer(t *testing.T) {
    ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Write the response.
    }))
    defer ts.Close()
    config := &clerk.ClientConfig{}
    config.HTTPClient = ts.Client()
    config.URL = &ts.URL
    client := user.NewClient(config)
}
  1. Implement your own Backend

You can implement your own Backend and set it as the API client's Backend.

func TestWithCustomBackend(t *testing.T) {
    client := user.NewClient(&clerk.ClientConfig{})
    client.Backend = &customBackend{}
}

type customBackend struct {}
// Implement the Backend interface
func (b *customBackend) Call(ctx context.Context, r *clerk.APIRequest, reader *clerk.ResponseReader) error {
    // Construct a clerk.APIResponse and use the reader's Read method.
    reader.Read(&clerk.APIResponse{})
}

Development

Contributions are welcome. If you submit a pull request please keep in mind that

  1. Code must be go fmt compliant.
  2. All packages, types and functions should be documented.
  3. Ensure that go test ./... succeeds. Ideally, your pull request should include tests.
  4. If your pull request introduces a new API or API operation, run go generate ./... to generate the necessary API functions.

License

This SDK is licensed under the MIT license found in the LICENSE file.