DEV Community

Cover image for Using Clerk to authenticate users in a Go backend
Marcus Kohlberg for Encore

Posted on • Edited on

Using Clerk to authenticate users in a Go backend

In this short guide you will learn how to set up an Encore auth handler that makes use of Clerk in order to add an integrated signup and login experience to your web app with an Encore based backend.

For all the code and instructions of how to clone and run this example locally, see the Clerk Example in the Encore examples repo.

Prerequisites

This guide assumes you already have an Encore app. If you don't, why not create one using these tutorials:

Step 1: Set up the auth handler

In your Encore app, install the following module:

$ go get github.com/clerkinc/clerk-sdk-go/clerk 
Enter fullscreen mode Exit fullscreen mode

Create a folder and naming it auth, this is where our authentication related backend code will live.

It's time to define your auth handler. Create auth/auth.go and paste the following:

package auth import ( "context" "encore.dev/beta/auth" "encore.dev/beta/errs" "github.com/clerkinc/clerk-sdk-go/clerk" ) var secrets struct { ClientSecretKey string } // Service struct definition. // Learn more: encore.dev/docs/primitives/services-and-apis/service-structs // //encore:service type Service struct { client clerk.Client } // initService is automatically called by Encore when the service starts up. func initService() (*Service, error) { client, err := clerk.NewClient(secrets.ClientSecretKey) if err != nil { return nil, err } return &Service{client: client}, nil } type UserData struct { ID string `json:"id"` Username *string `json:"username"` FirstName *string `json:"first_name"` LastName *string `json:"last_name"` ProfileImageURL string `json:"profile_image_url"` PrimaryEmailAddressID *string `json:"primary_email_address_id"` EmailAddresses []clerk.EmailAddress `json:"email_addresses"` } // The `encore:authhandler` annotation tells Encore to run this function for all // incoming API call that requires authentication. // Learn more: encore.dev/docs/develop/auth#the-auth-handler // //encore:authhandler func (s *Service) AuthHandler(ctx context.Context, token string) (auth.UID, *UserData, error) { // verify the session sessClaims, err := s.client.VerifyToken(token) if err != nil { return "", nil, &errs.Error{ Code: errs.Unauthenticated, Message: "invalid token", } } user, err := s.client.Users().Read(sessClaims.Claims.Subject) if err != nil { return "", nil, &errs.Error{ Code: errs.Internal, Message: err.Error(), } } userData := &UserData{ ID: user.ID, Username: user.Username, FirstName: user.FirstName, LastName: user.LastName, ProfileImageURL: user.ProfileImageURL, PrimaryEmailAddressID: user.PrimaryEmailAddressID, EmailAddresses: user.EmailAddresses, } return auth.UID(user.ID), userData, nil } 
Enter fullscreen mode Exit fullscreen mode

Clerk credentials

Create a Clerk account if you haven't already. Then, in the Clerk dashboard, create a new applications.

Next, go to the API Keys page for your app. Copy one of the "Secret keys" (the "Publishable Key" will be used by your frontend).

The Secret key is sensitive and should not be hardcoded in your code/config. Instead, you should store that as an Encore secret.

From your terminal (inside your Encore app directory), run:

$ encore secret set --prod ClientSecretKey 
Enter fullscreen mode Exit fullscreen mode

Now you should do the same for the development secret. The most secure way is to create another secret key (Clerk allows you to have multiple).
Once you have a client secret for development, set it similarly to before:

$ encore secret set --dev ClientSecretKey 
Enter fullscreen mode Exit fullscreen mode

Frontend

Clerk offers a React SDK for the frontend which makes it really simple to integrate
a login/signup flow inside your web app as well as getting the token required to communicate with your Encore backend.

You can use the useAuth hook from @clerk/clerk-react to get the token and send it to your backend.

import { useAuth } from '@clerk/clerk-react'; export default function ExternalDataPage() { const { getToken, isLoaded, isSignedIn } = useAuth(); if (!isLoaded) { // Handle loading state however you like return <div>Loading...</div>; } if (!isSignedIn) { // Handle signed out state however you like return <div>Sign in to view this page</div>; } const fetchDataFromExternalResource = async () => { const token = await getToken(); // Use token to send to Encore backend when fetching data return data; } return <div>...</div>; } 
Enter fullscreen mode Exit fullscreen mode

For a fully working backend + frontend example see the Clerk Example in the Encore examples repo.

Wrapping up

Top comments (9)

Collapse
 
fmerian profile image
fmerian • Edited

thanks for this tutorial, @marcuskohlberg! we just featured it on our latest product update here 🙌

fun fact: Clerk's backends are written in Go and the backend that powers our Dashboard experience is a heavy consumer of our open-source Clerk Go SDK!

Collapse
 
marcuskohlberg profile image
Marcus Kohlberg Encore

Awesome, thanks for sharing!

Collapse
 
ceaser_ak profile image
ceaser

hey @fmerian how do you handler data sync from clerk to the database in golang via webhooks? there aren't many good resources out there which explains this, couldn't find a go example in official repo as well.

Collapse
 
brianmmdev profile image
Brian Morrison II

Give this a look and let me know if you have any questions!

github.com/bmorrisondev/clerk-go-w...

It shows how to parse the user.created and organization.created events into Go structs so you can handle them however you need. You'd need to create the structs for any other events you want to parse, but you can do this pretty easily with ChatGPT and the samples provided in the Webhooks section of our dashboard.

I'm going to push to get something more official but this should at least get you going. Hope it helps!

Collapse
 
fmerian profile image
fmerian
Thread Thread
 
ceaser_ak profile image
ceaser

@brianmmdev ^^ , would love to get a proper official go guide on this

Thread Thread
 
brianmmdev profile image
Brian Morrison II

Hey there! I'll get working on a demo to share! In the meantime, I do have some resources that might help move you in the right direction before I can spin up a more official guide.

I wrote for the PlanetScale blog a while back about receiving webhooks with Go: planetscale.com/blog/sync-user-dat...

Downside is I did not actually verify the webhook (which I sorely regret now 😅). Luckily, this guide shows how to do just that: svix.com/guides/receiving/receive-...

I'll be out of town this coming week but will do my best to get a functional example working!

Thread Thread
 
ceaser_ak profile image
ceaser

Thanks Brian, the code demo helped a lot!

Thread Thread
 
fmerian profile image
fmerian

@brianmmdev is the 🐐