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
1Install 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
2Set 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!")
}
3Add 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)
}
4Add 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
}
5Test 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
}