Go
This tutorial demonstrates how to add user login to a Go web application using Auth0. We recommend that you log in to follow this quickstart with examples configured for your account.
I want to integrate with my app
15 minutesI want to explore a sample app
2 minutesGet a sample configured with your account settings or check it out on Github.
Configure Auth0
Get Your Application Keys
When you signed up for Auth0, a new application was created for you, or you could have created a new one. You will need some details about that application to communicate with Auth0. You can get these details from the Application Settings section in the Auth0 dashboard.
You need the following information:
- Domain
- Client ID
- Client Secret
Configure Callback URLs
A callback URL is a URL in your application where Auth0 redirects the user after they have authenticated. The callback URL for your app must be added to the Allowed Callback URLs field in your Application Settings. If this field is not set, users will be unable to log in to the application and will get an error.
Configure Logout URLs
A logout URL is a URL in your application that Auth0 can return to after the user has been logged out of the authorization server. This is specified in the returnTo
query parameter. The logout URL for your app must be added to the Allowed Logout URLs field in your Application Settings. If this field is not set, users will be unable to log out from the application and will get an error.
Configure Go to Use Auth0
Download dependencies
Start by adding a go.mod
file to list all the dependencies to be used.
// go.mod
module 01-Login
go 1.16
require (
github.com/coreos/go-oidc/v3 v3.1.0
github.com/gin-contrib/sessions v0.0.3
github.com/gin-gonic/gin v1.7.4
github.com/joho/godotenv v1.4.0
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
)
Was this helpful?
We can now make the dependencies available for us by running the following shell command:
go mod download
Was this helpful?
Configure your application
Create a .env
file within the root of your project directory to store the app configuration, and fill in the
environment variables:
# The URL of our Auth0 Tenant Domain.
# If you're using a Custom Domain, be sure to set this to that value instead.
AUTH0_DOMAIN='{yourDomain}'
# Our Auth0 application's Client ID.
AUTH0_CLIENT_ID='{yourClientId}'
# Our Auth0 application's Client Secret.
AUTH0_CLIENT_SECRET='{yourClientSecret}'
# The Callback URL of our application.
AUTH0_CALLBACK_URL='http://localhost:3000/callback'
Was this helpful?
Configure OAuth2 and OpenID Connect packages
Create a file called auth.go
in the platform/authenticator
folder. In this package you'll create a method to
configure and return OAuth2 and
oidc clients, and another one to verify an ID Token.
// platform/authenticator/auth.go
package authenticator
import (
"context"
"errors"
"os"
"github.com/coreos/go-oidc/v3/oidc"
"golang.org/x/oauth2"
)
// Authenticator is used to authenticate our users.
type Authenticator struct {
*oidc.Provider
oauth2.Config
}
// New instantiates the *Authenticator.
func New() (*Authenticator, error) {
provider, err := oidc.NewProvider(
context.Background(),
"https://"+os.Getenv("AUTH0_DOMAIN")+"/",
)
if err != nil {
return nil, err
}
conf := oauth2.Config{
ClientID: os.Getenv("AUTH0_CLIENT_ID"),
ClientSecret: os.Getenv("AUTH0_CLIENT_SECRET"),
RedirectURL: os.Getenv("AUTH0_CALLBACK_URL"),
Endpoint: provider.Endpoint(),
Scopes: []string{oidc.ScopeOpenID, "profile"},
}
return &Authenticator{
Provider: provider,
Config: conf,
}, nil
}
// VerifyIDToken verifies that an *oauth2.Token is a valid *oidc.IDToken.
func (a *Authenticator) VerifyIDToken(ctx context.Context, token *oauth2.Token) (*oidc.IDToken, error) {
rawIDToken, ok := token.Extra("id_token").(string)
if !ok {
return nil, errors.New("no id_token field in oauth2 token")
}
oidcConfig := &oidc.Config{
ClientID: a.ClientID,
}
return a.Verifier(oidcConfig).Verify(ctx, rawIDToken)
}
Was this helpful?
Setting up your application routes
Create a file called router.go
in the platform/router
folder. In this package you'll create a method to configure
and return our routes using github.com/gin-gonic/gin. You will be passing an
instance of Authenticator
to the method, so it can be used within the login
and callback
handlers.
// platform/router/router.go
package router
import (
"encoding/gob"
"net/http"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
"01-Login/platform/authenticator"
"01-Login/platform/middleware"
"01-Login/web/app/callback"
"01-Login/web/app/login"
"01-Login/web/app/logout"
"01-Login/web/app/user"
)
// New registers the routes and returns the router.
func New(auth *authenticator.Authenticator) *gin.Engine {
router := gin.Default()
// To store custom types in our cookies,
// we must first register them using gob.Register
gob.Register(map[string]interface{}{})
store := cookie.NewStore([]byte("secret"))
router.Use(sessions.Sessions("auth-session", store))
router.Static("/public", "web/static")
router.LoadHTMLGlob("web/template/*")
router.GET("/", func(ctx *gin.Context) {
ctx.HTML(http.StatusOK, "home.html", nil)
})
router.GET("/login", login.Handler(auth))
router.GET("/callback", callback.Handler(auth))
router.GET("/user", user.Handler)
router.GET("/logout", logout.Handler)
return router
}
Was this helpful?
Serving your application
Next, let's create our application's entry point main.go
and wire everything up together:
// main.go
package main
import (
"log"
"net/http"
"github.com/joho/godotenv"
"01-Login/platform/authenticator"
"01-Login/platform/router"
)
func main() {
if err := godotenv.Load(); err != nil {
log.Fatalf("Failed to load the env vars: %v", err)
}
auth, err := authenticator.New()
if err != nil {
log.Fatalf("Failed to initialize the authenticator: %v", err)
}
rtr := router.New(auth)
log.Print("Server listening on http://localhost:3000/")
if err := http.ListenAndServe("0.0.0.0:3000", rtr); err != nil {
log.Fatalf("There was an error with the http server: %v", err)
}
}
Was this helpful?
Logging In
Create a file called login.go
in the web/app/login
folder, and add a Handler
function to handle the /login
route.
// web/app/login/login.go
package login
import (
"crypto/rand"
"encoding/base64"
"net/http"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"01-Login/platform/authenticator"
)
// Handler for our login.
func Handler(auth *authenticator.Authenticator) gin.HandlerFunc {
return func(ctx *gin.Context) {
state, err := generateRandomState()
if err != nil {
ctx.String(http.StatusInternalServerError, err.Error())
return
}
// Save the state inside the session.
session := sessions.Default(ctx)
session.Set("state", state)
if err := session.Save(); err != nil {
ctx.String(http.StatusInternalServerError, err.Error())
return
}
ctx.Redirect(http.StatusTemporaryRedirect, auth.AuthCodeURL(state))
}
}
func generateRandomState() (string, error) {
b := make([]byte, 32)
_, err := rand.Read(b)
if err != nil {
return "", err
}
state := base64.StdEncoding.EncodeToString(b)
return state, nil
}
Was this helpful?
Add a link to /login
route in the home.html
template.
<!-- web/template/home.html -->
<div>
<h3>Auth0 Example</h3>
<p>Zero friction identity infrastructure, built for developers</p>
<a href="/login">SignIn</a>
</div>
Was this helpful?
Handling Authentication Callback
Once users have authenticated using Auth0's Universal Login Page, they'll return to the app at the /callback
route that will be handled in the following Handler
function:
// web/app/callback/callback.go
package callback
import (
"net/http"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"01-Login/platform/authenticator"
)
// Handler for our callback.
func Handler(auth *authenticator.Authenticator) gin.HandlerFunc {
return func(ctx *gin.Context) {
session := sessions.Default(ctx)
if ctx.Query("state") != session.Get("state") {
ctx.String(http.StatusBadRequest, "Invalid state parameter.")
return
}
// Exchange an authorization code for a token.
token, err := auth.Exchange(ctx.Request.Context(), ctx.Query("code"))
if err != nil {
ctx.String(http.StatusUnauthorized, "Failed to exchange an authorization code for a token.")
return
}
idToken, err := auth.VerifyIDToken(ctx.Request.Context(), token)
if err != nil {
ctx.String(http.StatusInternalServerError, "Failed to verify ID Token.")
return
}
var profile map[string]interface{}
if err := idToken.Claims(&profile); err != nil {
ctx.String(http.StatusInternalServerError, err.Error())
return
}
session.Set("access_token", token.AccessToken)
session.Set("profile", profile)
if err := session.Save(); err != nil {
ctx.String(http.StatusInternalServerError, err.Error())
return
}
// Redirect to logged in page.
ctx.Redirect(http.StatusTemporaryRedirect, "/user")
}
}
Was this helpful?
Displaying User Information
You can access the user information via the profile
you stored in the session previously.
// web/app/user/user.go
package user
import (
"net/http"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
)
// Handler for our logged-in user page.
func Handler(ctx *gin.Context) {
session := sessions.Default(ctx)
profile := session.Get("profile")
ctx.HTML(http.StatusOK, "user.html", profile)
}
Was this helpful?
<!-- web/template/user.html -->
<div>
<img class="avatar" src="{{ .picture }}"/>
<h2>Welcome {{.nickname}}</h2>
</div>
Was this helpful?
For information about the userinfo hash, see User Profile.
Logging Out
To log the user out, clear the data from the session and redirect the user to the Auth0 logout endpoint. You can find more information about this in the logout documentation.
Create a file called logout.go
in the folder web/app/logout/logout.go
, and add the function Handler
to redirect
the user to Auth0's logout endpoint.
// web/app/logout/logout.go
package logout
import (
"net/http"
"net/url"
"os"
"github.com/gin-gonic/gin"
)
// Handler for our logout.
func Handler(ctx *gin.Context) {
logoutUrl, err := url.Parse("https://" + os.Getenv("AUTH0_DOMAIN") + "/v2/logout")
if err != nil {
ctx.String(http.StatusInternalServerError, err.Error())
return
}
scheme := "http"
if ctx.Request.TLS != nil {
scheme = "https"
}
returnTo, err := url.Parse(scheme + "://" + ctx.Request.Host)
if err != nil {
ctx.String(http.StatusInternalServerError, err.Error())
return
}
parameters := url.Values{}
parameters.Add("returnTo", returnTo.String())
parameters.Add("client_id", os.Getenv("AUTH0_CLIENT_ID"))
logoutUrl.RawQuery = parameters.Encode()
ctx.Redirect(http.StatusTemporaryRedirect, logoutUrl.String())
}
Was this helpful?
Create a file called user.js
in the folder web/static/js
, and add the code to remove the cookie from a logged-in
user.
$(document).ready(function () {
$('.btn-logout').click(function (e) {
Cookies.remove('auth-session');
});
});
Was this helpful?
Optional Steps
Checking if the user is authenticated
Create a middleware that will check if the user is authenticated or not based on the profile
session key:
// platform/middleware/isAuthenticated.go
package middleware
import (
"net/http"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
)
// IsAuthenticated is a middleware that checks if
// the user has already been authenticated previously.
func IsAuthenticated(ctx *gin.Context) {
if sessions.Default(ctx).Get("profile") == nil {
ctx.Redirect(http.StatusSeeOther, "/")
} else {
ctx.Next()
}
}
Was this helpful?
Finally, set up this middleware for any route that needs authentication by adding it to the router.
// platform/router/router.go
router.GET("/user", middleware.IsAuthenticated, user.Handler)
Was this helpful?