Consumer Authentication

/

Quickstarts

/

Quickstarts

/

Go

Go Quickstart

This quickstart guide outlines the steps to integrate Stytch’s Consumer Authentication product into a Go application.

Overview

Stytch offers a Go SDK that can be used either stand-alone, for an entirely backend integration with Stytch, or alongside our frontend SDKs. This guide covers the steps to do an entirely backend integration with Stytch, but if you would like to use Stytch's frontend SDKs for automatic session management and out of the box UI, check out our React, Next.js, Svelte or Vue quickstarts instead.

Want to skip straight to the source code? Check out an example app here.

Getting Started

1
Install Stytch SDK and configure your API Keys

Create a Stytch Consumer Project in your Stytch Dashboard if you haven't already.

Install our Go SDK

go get github.com/stytchauth/stytch-go/v15

Configure your Stytch Project's API keys as environment variables:

STYTCH_PROJECT_ID="YOUR_STYTCH_PROJECT_ID"
STYTCH_SECRET="YOUR_STYTCH_PROJECT_SECRET"
# Use your Project's 'test' or 'live' credentials

2
Set up your app

Set up a basic service and initialize the Stytch client with the environment variables you set in the previous step. Register an HTTP handler for a /login route that takes in the user's email address and initiates the sign-up or login flow by calling Stytch.

package main

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"os"

	gorillaSessions "github.com/gorilla/sessions"
	"github.com/joho/godotenv"
	"github.com/stytchauth/stytch-go/v15/stytch/consumer/magiclinks"
	emailML "github.com/stytchauth/stytch-go/v15/stytch/consumer/magiclinks/email"
	"github.com/stytchauth/stytch-go/v15/stytch/consumer/stytchapi"
)

var ctx = context.Background()

func main() {
	// Load variables from .env file into the environment.
	if err := godotenv.Load(".env.local"); err != nil {
		log.Fatalf("Error loading .env file: %v", err)
	}

	// Instantiate a new API service.
	service := NewAuthService(
		os.Getenv("STYTCH_PROJECT_ID"),
		os.Getenv("STYTCH_SECRET"),
	)

	// Register HTTP handlers.
	mux := http.NewServeMux()
	mux.HandleFunc("/login", service.sendMagicLinkHandler)

	// Start server.
	server := http.Server{
		Addr:    ":3000",
		Handler: mux,
	}
	log.Println("WARNING: For testing purposes only. Not intended for production use...")
	log.Println("Starting server on http://localhost:3000")
	if err := server.ListenAndServe(); err != nil {
		log.Fatal(err)
	}
}

type AuthService struct {
	client *stytchapi.API
	store  *gorillaSessions.CookieStore
}

func NewAuthService(projectId, secret string) *AuthService {
	client, err := stytchapi.NewClient(projectId, secret)
	if err != nil {
		log.Fatalf("Error creating client: %v", err)
	}

	return &AuthService{
		client: client,
		store:  gorillaSessions.NewCookieStore([]byte("your-secret-key")),
	}
}

func (s *AuthService) sendMagicLinkHandler(w http.ResponseWriter, r *http.Request) {
	if err := r.ParseForm(); err != nil {
		log.Printf("Error parsing form: %v\n", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	email := r.Form.Get("email")
	if email == "" {
		http.Error(w, "Email is required", http.StatusBadRequest)
		return
	}

	_, err := s.client.MagicLinks.Email.LoginOrCreate(
		ctx,
		&emailML.LoginOrCreateParams{
			Email: email,
		})
	if err != nil {
		log.Printf("Error sending email: %v\n", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusOK)
	fmt.Fprintln(w, "Successfully sent magic link email!")
}

3
Add a route to handle redirect callback and get session

When a user completes an authentication flow in Stytch, we will call the Redirect URL specified in your Stytch Dashboard with a token used to securely complete the auth flow. By default the redirect URL is set tohttp://localhost:3000/authenticate.

You can read more about redirect URLs and possible token types in this guide.

import (
	// New imports
	"github.com/stytchauth/stytch-go/v15/stytch/consumer/sessions"
)
func main() {
	// Add new HTTP handler
	mux.HandleFunc("/authenticate", service.authenticateHandler)
}

func (s *AuthService) authenticateHandler(w http.ResponseWriter, r *http.Request) {
	tokenType := r.URL.Query().Get("stytch_token_type")
	token := r.URL.Query().Get("token")

	if tokenType != "magic_links" {
		log.Printf("Error: unrecognized token type %s\n", tokenType)
		http.Error(w, fmt.Sprintf("Unrecognized token type %s", tokenType), http.StatusBadRequest)
		return
	}

	resp, err := s.client.MagicLinks.Authenticate(ctx, &magiclinks.AuthenticateParams{
		Token:                  token,
		SessionDurationMinutes: 60,
	})
	if err != nil {
		log.Printf("Error authenticating: %v\n", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	session, err := s.store.Get(r, "stytch_session")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	session.Values["token"] = resp.SessionToken
	session.Save(r, w)

    w.WriteHeader(http.StatusOK)
    fmt.Fprintf(w, "Welcome %s!", resp.User.Emails[0].Email)
}

4
Add session protected route

Add a helper method that returns the session user information, and use it to gate any protected route that should only be accessed by an authenticated user.

import (
	// Add new imports
	"github.com/stytchauth/stytch-go/v15/stytch/consumer/users"
)

func main() {
	// Add new HTTP handler
	mux.HandleFunc("/", service.indexHandler)
}

func (s *AuthService) indexHandler(w http.ResponseWriter, r *http.Request) {

	user := s.getAuthenticatedUser(w, r)
	if user == nil {
		w.WriteHeader(http.StatusOK)
		fmt.Fprintln(w, "Please log in to see this page")
		return
	}

	w.WriteHeader(http.StatusOK)
	fmt.Fprintf(w, "Welcome %s!", user.Emails[0].Email)
}

func (s *AuthService) getAuthenticatedUser(w http.ResponseWriter, r *http.Request) *users.User {
	session, err := s.store.Get(r, "stytch_session")
	if err != nil || session == nil {
		return nil
	}

	token, ok := session.Values["token"].(string)
	if !ok || token == "" {
		return nil
	}

	resp, err := s.client.Sessions.Authenticate(
		context.Background(),
		&sessions.AuthenticateParams{
			SessionToken: token,
		})
	if err != nil {
		delete(session.Values, "token")
		session.Save(r, w)
		return nil
	}
	session.Values["token"] = resp.SessionToken
	session.Save(r, w)

	return &resp.User
}

5
Test your application

Run your application

go run .

Send a POST request to the /login endpoint with your email address to initiate the sign-up or login flow, and then click on the email magic link you receive in your inbox to finish logging in.

What's next

Check out our product-specific guides to add another authentication flows like OAuth or check out our migration guide to learn how to do a zero-downtime migration to Stytch.

Completed Example

Check out the example app here for an example with a basic UI.

package main

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"os"

	gorillaSessions "github.com/gorilla/sessions"
	"github.com/joho/godotenv"
	"github.com/stytchauth/stytch-go/v15/stytch/consumer/magiclinks"
	emailML "github.com/stytchauth/stytch-go/v15/stytch/consumer/magiclinks/email"
	"github.com/stytchauth/stytch-go/v15/stytch/consumer/sessions"
	"github.com/stytchauth/stytch-go/v15/stytch/consumer/stytchapi"
	"github.com/stytchauth/stytch-go/v15/stytch/consumer/users"
)

var ctx = context.Background()

func main() {
	// Load variables from .env file into the environment.
	if err := godotenv.Load(".env.local"); err != nil {
		log.Fatalf("Error loading .env file: %v", err)
	}

	// Instantiate a new API service.
	service := NewAuthService(
		os.Getenv("STYTCH_PROJECT_ID"),
		os.Getenv("STYTCH_SECRET"),
	)

	// Register HTTP handlers.
	mux := http.NewServeMux()
	mux.HandleFunc("/", service.indexHandler)
	mux.HandleFunc("/login", service.sendMagicLinkHandler)
	mux.HandleFunc("/authenticate", service.authenticateHandler)
	mux.HandleFunc("/logout", service.logoutHandler)

	// Start server.
	server := http.Server{
		Addr:    ":3000",
		Handler: mux,
	}
	log.Println("WARNING: For testing purposes only. Not intended for production use...")
	log.Println("Starting server on http://localhost:3000")
	if err := server.ListenAndServe(); err != nil {
		log.Fatal(err)
	}
}

type AuthService struct {
	client *stytchapi.API
	store  *gorillaSessions.CookieStore
}

func NewAuthService(projectId, secret string) *AuthService {
	client, err := stytchapi.NewClient(projectId, secret)
	if err != nil {
		log.Fatalf("Error creating client: %v", err)
	}

	return &AuthService{
		client: client,
		store:  gorillaSessions.NewCookieStore([]byte("your-secret-key")),
	}
}

func (s *AuthService) sendMagicLinkHandler(w http.ResponseWriter, r *http.Request) {
	if err := r.ParseForm(); err != nil {
		log.Printf("Error parsing form: %v\n", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	email := r.Form.Get("email")
	if email == "" {
		http.Error(w, "Email is required", http.StatusBadRequest)
		return
	}

	_, err := s.client.MagicLinks.Email.LoginOrCreate(
		ctx,
		&emailML.LoginOrCreateParams{
			Email: email,
		})
	if err != nil {
		log.Printf("Error sending email: %v\n", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusOK)
	fmt.Fprintln(w, "Successfully sent magic link email!")
}

func (s *AuthService) authenticateHandler(w http.ResponseWriter, r *http.Request) {
	tokenType := r.URL.Query().Get("stytch_token_type")
	token := r.URL.Query().Get("token")

	if tokenType != "magic_links" {
		log.Printf("Error: unrecognized token type %s\n", tokenType)
		http.Error(w, fmt.Sprintf("Unrecognized token type %s", tokenType), http.StatusBadRequest)
		return
	}

	resp, err := s.client.MagicLinks.Authenticate(ctx, &magiclinks.AuthenticateParams{
		Token:                  token,
		SessionDurationMinutes: 60,
	})
	if err != nil {
		log.Printf("Error authenticating: %v\n", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	session, err := s.store.Get(r, "stytch_session")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	session.Values["token"] = resp.SessionToken
	session.Save(r, w)

	s.indexHandler(w, r)
	return
}

func (s *AuthService) indexHandler(w http.ResponseWriter, r *http.Request) {

	user := s.getAuthenticatedUser(w, r)
	if user == nil {
		w.WriteHeader(http.StatusOK)
		fmt.Fprintln(w, "Please log in to see this page")
		return
	}

	w.WriteHeader(http.StatusOK)
	fmt.Fprintf(w, "Welcome %s!", user.Emails[0].Email)
}

func (s *AuthService) logoutHandler(w http.ResponseWriter, r *http.Request) {
	session, _ := s.store.Get(r, "stytch_session")
	delete(session.Values, "token")
	_ = s.store.Save(r, w, session)
	s.indexHandler(w, r)
	return
}

func (s *AuthService) getAuthenticatedUser(w http.ResponseWriter, r *http.Request) *users.User {
	session, err := s.store.Get(r, "stytch_session")
	if err != nil || session == nil {
		return nil
	}

	token, ok := session.Values["token"].(string)
	if !ok || token == "" {
		return nil
	}

	resp, err := s.client.Sessions.Authenticate(
		context.Background(),
		&sessions.AuthenticateParams{
			SessionToken: token,
		})
	if err != nil {
		delete(session.Values, "token")
		session.Save(r, w)
		return nil
	}
	session.Values["token"] = resp.SessionToken
	session.Save(r, w)

	return &resp.User
}