Skip to main content

Protect a Page with Login in Go

In this guide, we set up a new Go application and protect its home page with a login page using Ory. It will also add features such as profile settings, registration, and more. You can use this guide with both Ory Cloud and by self-hosting Ory software.

This guide is for you if you:

  1. have Go installed;
  2. want to use Go to build an application;
  3. want to show your app only if the user is logged in.

Before we start, let's take a look at what user flow you will implement with this guide.

Create Go App​

First we create a new Go project:

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 the Ory SDK​

To interact with Ory's APIs we install the Ory SDK:

go get -u github.com/ory/client-go

Why do I Need the Ory CLI?​

The Ory CLI includes useful functionality to manage your Ory Cloud Project. But that is not why we require it in this guide!

Ory's philosophy is to make hard things easy for you. For this reason, Ory has deployed measures against all OWASP Top 10 and implements the OWASP Authentication Cheat Sheet along other mechanisms.

Therefore, Ory manages Anti-CSRF Cookies as well as Ory Session Cookies for you. That however requires that Ory and your application run on the same domain!

If your application runs on http://localhost:3000 then Ory needs to be available on the hostname localhost as well (e.g. http://localhost:3001). That is why we need the Ory CLI, because it has a proxy included which mirrors Ory's API endpoints on the domain of your application.

Ory Proxy mirrors Ory's APIs

Create an HTTP Server​

This is a working example of a basic server which creates an Ory client, registers a new route for our Dashboard and makes use of a middleware to validate if the user is allowed to view the Dashboard.

package main

import (
"fmt"
ory "github.com/ory/client-go"
"net/http"
"os"
)

type App struct {
ory *ory.APIClient
// save the cookies for any upstream calls to the Ory apis
cookies string
// save the session to display it on the dashboard
session *ory.Session
}

func main() {
proxyPort := os.Getenv("PROXY_PORT")
if proxyPort == "" {
proxyPort = "4000"
}

// register a new Ory client with the URL set to the Ory CLI Proxy
// we can also read the URL from the env or a config file
c := ory.NewConfiguration()
c.Servers = ory.ServerConfigurations{{URL: fmt.Sprintf("http://localhost:%s/.ory", proxyPort)}}

app := &App{
ory: ory.NewAPIClient(c),
}
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)
// start the server
http.ListenAndServe(":"+port, mux)
}

Validate and Login​

Next we will create a middleware which will check with your Ory project if the user has a valid session. Notice here that we are taking the current request cookies and passing them along to the Ory client.

If the session is not valid the request is redirected to the Ory project for login. At this point we have not set up any custom UI management and thus will be shown the Ory Managed UI login page.

package main

import (
"log"
"net/http"
)

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

// set the cookies on the ory client
var cookies string
for _, cookie := range request.Cookies() {
cookies += cookie.String() + ";"
}

// check if we have a session
session, _, err := app.ory.V0alpha2Api.ToSession(request.Context()).Cookie(cookies).Execute()
if (err != nil && session == nil) || (err == nil && !*session.Active) {
// this will redirect the user to the managed Ory Login UI
http.Redirect(writer, request, "/.ory/api/kratos/public/self-service/login/browser", http.StatusSeeOther)
return
}
app.cookies = cookies
app.session = session
// continue to the requested page (in our case the Dashboard)
next.ServeHTTP(writer, request)
return
}
}

The protected page​

For the last part we need to add the Dashboard (the page we would like to protect). Add a new handler called dashboardHandler which will render an HTML page with the session data.

package main

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

func (app *App) dashboardHandler() http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {
tmpl, err := template.New("index.html").ParseFiles("index.html")
if err != nil {
http.Error(writer, err.Error(), http.StatusInternalServerError)
return
}
session, err := json.Marshal(app.session)
if err != nil {
http.Error(writer, err.Error(), http.StatusInternalServerError)
return
}
err = tmpl.ExecuteTemplate(writer, "index.html", string(session))
if err != nil {
http.Error(writer, err.Error(), http.StatusInternalServerError)
return
}
}
}
<html lang="en">
<head>
<title>Ory Cloud secured Go web app</title>
</head>
<body>
<h1>Dashboard</h1>
<hr />
<h2>Your Session Data:</h2>
<code>{{ . }}</code>
</body>
</html>

Run your Go App​

Start your HTTP server and access the proxy URL

go run .
# This is a public Ory Cloud Project.
# Don’t submit any personally identifiable information in requests made with this project.
# Sign up for Ory Cloud at
#
# https://console.ory.sh/registration
#
# and create a free Ory Cloud Project to see your own configuration embedded in code samples!
export ORY_SDK_URL=https://playground.projects.oryapis.com
ory proxy http://localhost:3000

Then open http://localhost:4000 in your browser. You are presented with Ory's Sign In page! Let's click on sign up and create your first user!

Go to Production​

Going to production with your app is possible in many ways. Whether you deploy it on Kubernetes, AWS, a VM, or a RaspberryPi is up to you. To get your app working with Ory, your app and Ory must be available under the same common domain (e.g. https://ory.example.com and https://www.example.com).

The easiest way to connect Ory to your domain is to connect Ory to a subdomain of yours. You can do this easily by adding a Custom Domain to your Cloud project!

With the custom domain set up, you do not need the Ory Proxy anymore and will use the configured custom domain in your SDK calls:

// register a new Ory client with the URL set to the Ory CLI Proxy
// 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"}}