Skip to content

Latest commit

 

History

History
130 lines (108 loc) · 3.47 KB

README.md

File metadata and controls

130 lines (108 loc) · 3.47 KB

goidc - authentication with OpenID Connect

Build Status Coverage

This package provides a simple way to authenticate users with OpenID Connect and help you to create an authentication service.

NOTE: This package is still in development and not ready for production use.

  • Support for different OpenID Connect providers(currently tested with Keycloak and Google, but should work with any other providers).
  • Support for different authentication flows (currently tested with Authorization Code Flow, but others in progress).
  • JWKS and PEM key support.
  • Custom storage for users. Default is PostgreSQL, but you can implement your own storage.

Installation

go get github.com/stillya/goidc

Usage

Create a new client

package main

import (
	"context"
	"github.com/stillya/goidc"
	"github.com/stillya/goidc/user"
	"github.com/stillya/goidc/user/postgres"
	"net/http"
	"time"
)

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

	keycloakMapUserFunc := func(u map[string]interface{}) (*user.User, error) {
		return &user.User{
			UserID:     u["sub"].(string),
			Username:   u["preferred_username"].(string),
			Attributes: u,
		}, nil
	}

	googleMapUserFunc := func(u map[string]interface{}) (*user.User, error) {
		return &user.User{
			UserID:     u["sub"].(string),
			Username:   u["name"].(string),
			Attributes: u,
		}, nil
	}

	userStore, err := postgres.NewDB(postgres.Config{
		Host:     "localhost",
		Port:     5432,
		User:     "postgres",
		Password: "postgres",
		Database: "oidc",
	})
	if err != nil {
		panic(err)
	}

	g, err := goidc.NewService(
		goidc.Opts{
			BaseURL:              "http://localhost:8085",
			UseAsymmetricEnc:     true,
			PublicKey:            "_example/testdata/public.jwks",
			PrivateKey:           "_example/testdata/private.jwks",
			Issuer:               "goidc",
			Audience:             "goidc",
			AccessTokenLifetime:  time.Minute * 15,
			RefreshTokenLifetime: time.Hour * 24,
			UserStore:            userStore,
		})

	err = g.AddProvider(ctx,
		goidc.ProviderParams{
			Issuer:       "https://keycloak.com/realms/test",
			ClientID:     "test",
			ClientSecret: "test",
			MapUserFunc:  keycloakMapUserFunc,
			Scopes:       []string{"openid", "profile", "email"},
		}, "keycloak")
	if err != nil {
		panic(err)
	}

	err = g.AddProvider(ctx,
		goidc.ProviderParams{
			Issuer:       "https://accounts.google.com",
			ClientID:     "test",
			ClientSecret: "test",
			MapUserFunc:  googleMapUserFunc,
			Scopes:       []string{"openid", "profile", "email"},
		}, "google")
	if err != nil {
		panic(err)
	}

	http.HandleFunc("/login", g.LoginHandler)
	http.HandleFunc("/callback", g.CallbackHandler)
	http.HandleFunc("/certs", g.PublicKeySetHandler)
	http.HandleFunc("/example", func(w http.ResponseWriter, r *http.Request) {
		accessToken, err := r.Cookie("ACCESS_TOKEN")
		if err != nil {
			w.WriteHeader(http.StatusUnauthorized)
			return
		}
		refreshToken, err := r.Cookie("REFRESH_TOKEN")
		if err != nil {
			w.WriteHeader(http.StatusUnauthorized)
			return
		}
		_, _ = w.Write([]byte("access_token: " + accessToken.Value + "\n"))
		_, _ = w.Write([]byte("refresh_token: " + refreshToken.Value + "\n"))
	})

	err = http.ListenAndServe(":8085", nil)
	if err != nil {
		panic(err)
	}
}