# Go web application

> Add OneiD to a Go application with coreos/go-oidc, step by step.

Source: https://oltinid.com/docs/quickstarts/go/ · Section: Quickstarts · All OneiD documentation: https://oltinid.com/llms.txt

This quickstart adds OneiD to a server web application with **coreos/go-oidc**. Every file is complete and runs as it is.

> **Tip:** Using an AI coding agent? Give it this page as Markdown (add `.md` to the address) together with the [Connector specification](https://oltinid.com/docs/ai/connector-specification/). See [Build with AI coding agents](https://oltinid.com/docs/ai/build-with-ai/).

## Before you start

- A OneiD address, for example `https://YOUR_ONEID`. The code below uses the demonstration instance `https://auth.oltinid.com`; replace it with your own.
- A client registered for this application (step 1). The code uses the client ID `quickstart`; replace it with yours.
- A user who can sign in to your OneiD. For the demonstration instance, [ask for a demo account](https://oltinid.com/contact/?topic=demo).

## 1. Register the application

Ask your OneiD administrator to register a client with these settings, or register it yourself in the admin console. See [Register an application](https://oltinid.com/docs/get-started/register-an-application/).

| Setting | Value |
|---|---|
| Client type | `public` (no secret) |
| Grant types | `authorization_code` (add `refresh_token` if you request `offline_access`) |
| Redirect URI | `http://localhost:3000/callback` |
| Post-logout redirect URI | `http://localhost:3000` |
| Allowed scopes | `openid profile email` |

## 2. Install

```bash
go get github.com/coreos/go-oidc/v3/oidc golang.org/x/oauth2
```

## 3. Add the code

`main.go`

```go
package main

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

	"github.com/coreos/go-oidc/v3/oidc"
	"golang.org/x/oauth2"
)

func main() {
	ctx := context.Background()

	// Reads the endpoints and signing keys from OneiD. The issuer name ends with a slash.
	provider, err := oidc.NewProvider(ctx, "https://auth.oltinid.com/")
	if err != nil {
		log.Fatal(err)
	}
	verifier := provider.Verifier(&oidc.Config{ClientID: "quickstart"})

	endpoint := provider.Endpoint()
	endpoint.AuthStyle = oauth2.AuthStyleInParams // a public client: no secret, no Basic header
	config := oauth2.Config{
		ClientID:    "quickstart",
		Endpoint:    endpoint,
		RedirectURL: "http://localhost:3000/callback",
		Scopes:      []string{oidc.ScopeOpenID, "profile", "email"},
	}

	http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
		state, pkce := oauth2.GenerateVerifier(), oauth2.GenerateVerifier()
		setCookie(w, "state", state)
		setCookie(w, "pkce", pkce)
		http.Redirect(w, r, config.AuthCodeURL(state, oauth2.S256ChallengeOption(pkce)), http.StatusFound)
	})

	http.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
		state, _ := r.Cookie("state")
		pkce, _ := r.Cookie("pkce")
		if state == nil || pkce == nil || r.URL.Query().Get("state") != state.Value {
			http.Error(w, "state does not match", http.StatusBadRequest)
			return
		}

		token, err := config.Exchange(ctx, r.URL.Query().Get("code"), oauth2.VerifierOption(pkce.Value))
		if err != nil {
			http.Error(w, err.Error(), http.StatusBadGateway)
			return
		}

		// Check the signature, issuer, audience and expiry of the ID token.
		rawIDToken, _ := token.Extra("id_token").(string)
		idToken, err := verifier.Verify(ctx, rawIDToken)
		if err != nil {
			http.Error(w, err.Error(), http.StatusUnauthorized)
			return
		}

		var claims struct {
			Name  string `json:"name"`
			Email string `json:"email"`
		}
		if err := idToken.Claims(&claims); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		fmt.Fprintf(w, "Hello, %s (%s)", claims.Name, claims.Email)
	})

	log.Println("http://localhost:3000/login")
	log.Fatal(http.ListenAndServe("localhost:3000", nil))
}

func setCookie(w http.ResponseWriter, name, value string) {
	http.SetCookie(w, &http.Cookie{Name: name, Value: value, Path: "/", HttpOnly: true, MaxAge: 600, SameSite: http.SameSiteLaxMode})
}
```

## 4. Run it

Run: go run . Then open http://localhost:3000/login.

The example uses a public client so that it runs without a secret. For your own application, use a confidential client and set ClientSecret in the oauth2.Config.

> **Checkpoint:** Open http://localhost:3000/login and the browser goes to OneiD. After you sign in, the page shows Hello, followed by the user’s name and, in parentheses, the email address.

## Common issues

- **OneiD shows an error page about the redirect address** (`invalid_request`). The redirect URI the code sends is not registered on the client exactly as written. Register it, including scheme and port.
- **`invalid_grant` from the token endpoint.** The code was used before or expired. Start the sign-in again; do not reload the callback page.
- More errors and fixes: [Errors and troubleshooting](https://oltinid.com/docs/reference/errors/).

## Learn more

- [Authorization code flow with PKCE](https://oltinid.com/docs/guides/authorization-code-pkce/)
- [Sign-out](https://oltinid.com/docs/guides/logout/)
- [Scopes, claims and roles](https://oltinid.com/docs/guides/scopes-claims-roles/)
- [Refresh tokens](https://oltinid.com/docs/guides/refresh-tokens/)
