Go example

This code example shows how a relying party can integrate with GII using Go.

package main

import (
	"fmt"
	"log"
	"math/rand"
	"net/http"
	"os"

	// this example is using CoreOS OpenID Connect Client
	"github.com/coreos/go-oidc"

	"golang.org/x/net/context"
	"golang.org/x/oauth2"
)

func main() {

	giiServerAddress := os.Getenv("GII_SERVER_ADDRESS")
	if giiServerAddress == "" {
		giiServerAddress = "https://demo-api.gii.cloud"
	}

	callbackURL := os.Getenv("RP_CALLBACK_URL")   // e.g. https://example.com/callback
	clientID := os.Getenv("RP_CLIENT_ID")         // e.g. example-id
	clientSecret := os.Getenv("RP_CLIENT_SECRET") // e.g. Sq8uN7aVQuD0

	ctx := context.Background()

	relyingPartyHTTP := http.NewServeMux()

	provider, err := oidc.NewProvider(ctx, giiServerAddress)
	if err != nil {
		log.Fatal(err)
	}
	oidcConfig := &oidc.Config{
		ClientID: clientID,
	}
	verifier := provider.Verifier(oidcConfig)

	config := oauth2.Config{
		ClientID:     clientID,
		ClientSecret: clientSecret,
		Endpoint:     provider.Endpoint(),
		RedirectURL:  callbackURL,
		Scopes:       []string{oidc.ScopeOpenID},
	}

	// state should be random for each enduser browser session
	state := fmt.Sprintf("foobar-%v", rand.Int())

	relyingPartyHTTP.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte(`
			<!DOCTYPE html>
			<html>
				<head><title>Test</title></head>
				<body>
					<form action="login" method="get">
						<button type="submit">Login</button>
					</form>
				</body>
			</html>
		`))
	})

	// redirect the enduser to GIIs `authorization_endpoint` to initialize a login.
	relyingPartyHTTP.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
		http.Redirect(w, r, config.AuthCodeURL(state,
			// use UI created at /api/rp/ui endpoint with friendly ID 'example-ui'
			oauth2.SetAuthURLParam("ui_friendly", "example-ui"),
			// only show Swedish BankID
			oauth2.SetAuthURLParam("identity_provider", "bankid-se")), http.StatusFound)
	})

	// this is the `callbackURL`. GII will navigate the enduser to this URL when the login is complete.
	relyingPartyHTTP.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Query().Get("state") != state {
			http.Error(w, "state did not match", http.StatusBadRequest)
			return
		}
		code := r.URL.Query().Get("code")
		if code == "" {
			http.Error(w, "Failed to get code", http.StatusUnauthorized)
			return
		}

		// call servers token-endpoint to get token with ID "code"
		oauth2Token, err := config.Exchange(ctx, code)
		if err != nil {
			http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusUnauthorized)
			return
		}
		rawIDToken, ok := oauth2Token.Extra("id_token").(string)
		if !ok {
			http.Error(w, "No id_token field in oauth2 token.", http.StatusUnauthorized)
			return
		}
		idToken, err := verifier.Verify(ctx, rawIDToken)
		if err != nil {
			http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusUnauthorized)
			return
		}

		// extract claims
		var claims struct {
			Subject  string `json:"sub"`
			Issuer   string `json:"issuer"`
			Audience string `json:"aud"`
			Nonce    string `json:"nonce"`

			SSN               string `json:"ssn"`
			SSNType           string `json:"ssn_type"`
			Email             string `json:"email"`
			EmailVerified     bool   `json:"email_verified"`
			Picture           string `json:"picture"`
			Name              string `json:"name"`
			PreferredUsername string `json:"preferred_username"`
			GivenName         string `json:"given_name"`
			FamilyName        string `json:"family_name"`
			Birthdate         string `json:"birthdate"`
			Gender            string `json:"gender"`
			PhoneNumber       string `json:"phone_number"`
		}
		err = idToken.Claims(&claims)
		if err != nil {
			http.Error(w, "Failed to get claims: "+err.Error(), http.StatusUnauthorized)
			return
		}

		w.Write([]byte(fmt.Sprintf(`
			<!DOCTYPE html>
			<html>
				<head><title>Test</title></head>
				<body>
					<a href="/">Restart</a> <br/>
					<p>
						Logged in as %s with SSN %s and birthdate %s
					</p>
				</body>
			</html>
		`, claims.Name, claims.SSN, claims.Birthdate)))
	})

	log.Printf("listening on http://localhost:1337/")
	err = http.ListenAndServe("localhost:1337", relyingPartyHTTP)
	if err != nil {
		log.Fatal("Relying Party server failed to start: " + err.Error())
	}
}