Skip to main content

Integrate authentication into Go

This guide shows how to create a simple Go application and secure it with authentication powered by Ory. The guide provides the setup for using Ory Network, but the code can be used with both Ory Network and self-hosted Ory software.

This guide is perfect for you if:

  1. You have Go installed.
  2. You want to build an app using Go.
  3. You want to give access to your application to signed-in users only.

Before you start, watch this video to see the user flow you're going to implement:

info

You can find the code of the sample application here.

Create Go app

First, create a Go app. Run these commands:

mkdir your-project
cd your-project

touch main.go handler.go middleware.go index.html
go mod init github.com/<your-name>/your-project

Install Ory SDK

Run this command to install the Ory SDK which allows you to interact with Ory APIs:

go get github.com/ory/client-go@master

Create a new Ory project

  1. Create an Ory account at https://console.ory.sh
  2. Create a new project at https://console.ory.sh/projects/create
  3. Go to your project settings
https://console.ory.sh/projects/<id>/settings

Project settings tab

  1. Note down your project credentials (ID, slug, endpoint)
https://console.ory.sh/projects/<id>/settings

Project credentials

Install Ory CLI

Follow this guide to install the Ory CLI on your machine.

Why do I need the Ory CLI

The Ory security model uses HTTP cookies to manage sessions, tokens, and cookies. Because of browser security measures like CORS, Ory APIs must be exposed on the same domain as your application. In the case of this example the application runs on your local machine. The cookie domain is localhost.

info

Use either localhost or 127.0.0.1 consistently. Although technically they refer to the same machine, browsers treat them as different cookie domains.

Ory CLI provides a convenient way to configure and manage projects. Additionally, the CLI provides Ory Tunnel - a tool that ensures cookies match the domain your application is currently on.

To make Ory APIs and your application available on the same domain, Ory Tunnel mirrors Ory endpoints and rewrites cookies to match the correct domain. As a result, the domain of the cookies is set correctly to the domain you run the app on instead of <your-project-slug>.projects.oryapis.com.

By using the Tunnel, you can easily connect the application you're developing locally to Ory Network and consume the APIs without additional configuration or self-hosting any Ory services.

tip

To learn more about the Ory Tunnel, read the dedicated section of the Ory CLI documentation.

Create HTTP server

Use this code to set up a basic server that creates an Ory client, registers a new route for the Go app and uses middleware to validate if the user is allowed to access the application.

Add the code to the main.go file:

./main.go
package main

import (
"errors"
"fmt"
"net/http"
"os"

ory "github.com/ory/client-go"
)

type App struct {
ory *ory.APIClient
tunnelUrl string
}

func main() {
tunnelPort := os.Getenv("TUNNEL_PORT")
if tunnelPort == "" {
tunnelPort = "4000"
}

// Configure Ory client to use tunnel
c := ory.NewConfiguration()
c.Servers = ory.ServerConfigurations{{URL: fmt.Sprintf("http://localhost:%s", tunnelPort)}}

// Store the tunnel URL for redirects
tunnelUrl := fmt.Sprintf("http://localhost:%s", tunnelPort)

app := &App{
ory: ory.NewAPIClient(c),
tunnelUrl: tunnelUrl,
}
mux := http.NewServeMux()

// dashboard
mux.Handle("/", app.sessionMiddleware(app.dashboardHandler))

port := os.Getenv("PORT")
if port == "" {
port = "3000"
}

fmt.Printf("Application launched and running on http://127.0.0.1:%s\n", port)
fmt.Printf("Make sure to run Ory Tunnel in another terminal:\n")
fmt.Printf("npx @ory/cli tunnel --dev http://localhost:%s\n", port)

// start the server
err := http.ListenAndServe(":"+port, mux)
if errors.Is(err, http.ErrServerClosed) {
fmt.Println("Server closed")
return
}
fmt.Printf("Could not start server: %s\n", err)
}

Validate and login

Next, create middleware that checks if the user has a valid session. The session is considered valid when the user is authenticated.

To create the middleware, add the code to the middleware.go file:

./middleware.go

package main

import (
"context"
"errors"
"log"
"net/http"

ory "github.com/ory/client-go"
)

func (app *App) sessionMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {
log.Printf("handling middleware request\n")

// This example passes all request.Cookies to `ToSession` function.
//
// However, it is enough to pass only the value of the `ory_session_projectslug` cookie to the endpoint.
cookies := request.Header.Get("Cookie")

// A native app would submit the session token instead of a cookie.
// You can look up session tokens the same way by using the `XSessionToken` setter on the `ToSession` function.

// Look up session.
session, _, err := app.ory.FrontendAPI.ToSession(request.Context()).Cookie(cookies).Execute()
// Check if a session exists and if it is active.
// You could add your own logic here to check if the session is valid for the specific endpoint, e.g. using the `session.AuthenticatedAt` field.
// Redirect to login if session doesn't exist or is inactive
if err != nil || (err == nil && !*session.Active) {
log.Printf("No active session, redirecting to login\n")
// Redirect to the tunnel URL, not the local app
http.Redirect(writer, request, app.tunnelUrl+"/ui/login", http.StatusSeeOther)
return
}

// Add the session details to the context for handlers to access.
ctx := withSession(request.Context(), session)

// Continue to the next handler (the dashboard in the simple example).
next.ServeHTTP(writer, request.WithContext(ctx))
}
}

func withSession(ctx context.Context, v *ory.Session) context.Context {
return context.WithValue(ctx, "req.session", v)
}

func getSession(ctx context.Context) (*ory.Session, error) {
session, ok := ctx.Value("req.session").(*ory.Session)
if !ok || session == nil {
return nil, errors.New("session not found in context")
}
return session, nil
}

The middleware passes the current request cookies to the Ory client to check if the session is valid.

If the session is valid, the user is presented with the protected page. When the session is not valid, which means that the user is not authenticated or the session expired, the request is redirected for login using the Ory Account Experience.

The protected page

As the final step, create a Dashboard page that's presented to signed-in users. This page diplays the user's session data.

To create the dashboardHandler that renders the page with the session data, add this code to the handler.go file:

./handler.go

package main

import (
"bytes"
"encoding/json"
"html/template"
"net/http"
)

func (app *App) dashboardHandler(writer http.ResponseWriter, request *http.Request) {
// Get the session from the context. It was added to the context by the sessionMiddleware.
session, err := getSession(request.Context())
if err != nil {
http.Error(writer, err.Error(), http.StatusInternalServerError)
return
}

// Encode the session data as pretty JSON.
buffer := &bytes.Buffer{}
encoder := json.NewEncoder(buffer)
encoder.SetIndent("", " ")
if err := encoder.Encode(session); err != nil {
http.Error(writer, err.Error(), http.StatusInternalServerError)
return
}

// Render the dashboard template with the session data.
err = dashboardTemplate.ExecuteTemplate(writer, "index.html", buffer.String())
if err != nil {
http.Error(writer, err.Error(), http.StatusInternalServerError)
return
}
}

var dashboardTemplate *template.Template

func init() {
var err error
dashboardTemplate, err = template.New("index.html").Parse(`
<html lang="en">
<head>
<title>Ory Network secured Go web app</title>
</head>
<body>
<h1>Dashboard</h1>
<hr />
<h2>Your Session Data:</h2>
<code>{{ . }}</code>
</body>
</html>
`)
if err != nil {
panic(err)
}
}

Test your application

With all of the pieces in place, it's time to test your application. Follow these steps:

  1. Start your Go app:
go run .
  1. Run the Ory Tunnel to mirror the Ory API endpoints on your application's domain (localhost). Upon first start, the Ory Tunnel will ask you to log into your Ory Console account.
npx @ory/cli tunnel --dev http://localhost:3000
  1. Open http://localhost:4000 to access the application. Since the initial call is made by an unauthenticated user, the middleware doesn't detect a valid session and redirects to the login page of the defined Ory project.

    From there, you can create a new account or sign in using an existing identity. When you sign in, the session becomes valid and the application shows the Dashboard page with the session data.

Go to production

You can use many different approaches to go to production with your application. You can deploy it on Kubernetes, AWS, a VM, a RaspberryPi - the choice is yours! To connect the application to your Ory project, the app and Ory APIs must be available under the same common domain, for example https://ory.example.com and https://www.example.com.

You can easily connect Ory to your subdomain. To do that, add a Custom Domain to your Ory Network project.

With the custom domain set up, you don't need to use Ory Tunnel to interact with Ory APIs. Instead, use the configured custom domain in your SDK calls:

// register a new Ory client with the URL set to the custom domain
// we can also read the URL from the env or a config file
c := ory.NewConfiguration()
c.Servers = ory.ServerConfigurations{{URL: "https://ory.example.org"}}