oauth2

package module
v0.0.0-...-ffd17e6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 25, 2024 License: MIT Imports: 6 Imported by: 1

README

Golang OpenID Connect

Forked from https://github.com/go-oauth2/oauth2 this implementation add the OpenID layer on top of the oauth2 protocol.

This implementation aim to be a ready to use authorization and authentification service, implemented only with Authorization code grant type(at this time).

Note

Project under development

Protocol Flow

     +--------+                                             +-------------------------------------+
     |        |---------- Authorization Request ----------> |           Authorization             |
     |        |                                             |               Server                |
     |        |<--------- Authorization Code -------------  | - insure the client is registered   |
     |        |                                             | - if ok deliver an authorizationCode|
     |        |                                             +-------------------------------------+
     |        |
     |        |                                             +------------------------------------+
     |        |-Authorization Code with "openid" as scope-->|           Authorization            |
     |        |                                             |               Server               |
     |        |                                             | - create accessCode(ac)            |
     | client |                                             | - create RefreshCode(rc)           |
     |        |                                             | - create access and refresh jwt    |
     |        |<------- return access jwt(at least)-------- |(including userCredential,ac and rc)|
     |        |                                             | - customize the payload to return |
     |        |                                             +------------------------------------+
     |        |
     |        |                                             +------------------------------------+
     |        |------------ Access Token -----------------> |           Resource                 |
     |        |                                             |            Server                  |
     |        |<----------- Protected Resource ------------ | - valid the jwt                    |
     +--------+                                             +------------------------------------+

Advantage

  • You can still use this package as the go-oauth2/oauth there is no breacking change
  • This package can be use with a mobile app as it can return the Authorization Code(instead of just redirecting)
  • The possibility to crete the jwt with various keyID, secretKey(depending on the user scope for ex)
  • After the jwt(accessToken and refreshToken) has been created, before they are returned to the client the payload may be customized(the refreshToken be saved in DB and not returning to the client for ex)
  • As this package is a fork of go-oauth2/oauth2 all its store implementations are available

Limitation

  • The encoding of the jwt is limited to "HS256"
  • Implemented only with Authorization code grant type

Quick Start

Download and install
go get github.com/djedjethai/go-oauth2-openid
Create file server.go (we use mongo storage)
    import (
	"flag"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/http/httputil"
	// "time"

	// "github.com/go-oauth2/oauth2/v4/generates"
	"server/internal/handlers"

	"github.com/go-oauth2/oauth2/v4/errors"
	"github.com/go-oauth2/oauth2/v4/manage"
	"github.com/go-oauth2/oauth2/v4/models"
	"github.com/go-oauth2/oauth2/v4/server"

	mongo "gopkg.in/go-oauth2/mongo.v3"
)

var (
	dumpvar   bool
	idvar     string
	secretvar string
	domainvar string
	portvar   int
)

// TODO
// right now as a client is register in db, it won't be updated in case of modification
// means the updates won't take effect........ see in the mongo client...

func init() {
	// credential for the client
	flag.BoolVar(&dumpvar, "d", true, "Dump requests and responses")
	flag.StringVar(&idvar, "i", "222222", "The client id being passed in")
	flag.StringVar(&secretvar, "s", "22222222", "The client secret being passed in")
	flag.StringVar(&domainvar, "r", "http://localhost:3000", "The domain of the redirect url")
	flag.IntVar(&portvar, "p", 9096, "the base port for the server")
}

const (
	// credential for the preOrder service
	idPreorder     string = "888888"
	secretPreorder string = "88888888"
	domainPreorder string = "http://localhost:8081"

	dbUser     = "postgres"
	dbHost     = "localhost"
	dbPassword = "password"
	dbDatabase = "users"
	dbSSL      = "disable"
	dbPort     = "5432"
)

func main() {
	flag.Parse()

	manager := manage.NewDefaultManager()

	// set connectionTimeout(7s) and the requestsTimeout(5s) // is optional
	storeConfigs := mongo.NewStoreConfig(7, 5)

	mongoConf := mongo.NewConfigNonReplicaSet(
		"mongodb://127.0.0.1:27017",
		"oauth2",   // database name
		"admin",    // username to authenticate with db
		"password", // password to authenticate with db
		"serviceName",
	)

	// use mongodb token store
	manager.MapTokenStorage(
		mongo.NewTokenStore(mongoConf, storeConfigs), // with timeout
	)

	clientStore := mongo.NewClientStore(mongoConf, storeConfigs) // with timeout

	manager.MapClientStorage(clientStore)

	// register the front-end
	clientStore.Create(&models.Client{
		ID:     idvar,
		Secret: secretvar,
		Domain: domainvar,
		UserID: "frontend",
	})

	// register another service
	clientStore.Create(&models.Client{
		ID:     idPreorder,
		Secret: secretPreorder,
		Domain: domainPreorder,
		UserID: "prePost",
	})

	srv := server.NewServer(server.NewConfig(), manager)

    // *** NOTE *** 
	// set the oauth package to work without browser
	// the token will be return as a json payload
	srv.SetModeAPI()

	// handlers will handle all handlers
	handler := handlers.NewHandlers(dumpvar, srv)

    /*
    * set functions which are allowing 
    * 1 - controle of the user Authorization or/and authentication
    * 2 - settings of the jwt keyID, secretKey and user credentials
    * 3 - customize the payload to return to client
    **/
	// 1 - set the authorization staff
	srv.SetUserAuthorizationHandler(handler.UserAuthorizeHandler)

	// 2 - set the openid staff
	srv.SetUserOpenidHandler(handler.UserOpenidHandler)

	// 3 - set the func to, before to send it, customize the token payload
	srv.SetCustomizeTokenPayloadHandler(handler.UserCustomizeTokenPayloadHandler)

	srv.SetInternalErrorHandler(func(err error) (re *errors.Response) {
		log.Println("Internal Error:", err.Error())
		return
	})

	srv.SetResponseErrorHandler(func(re *errors.Response) {
		log.Println("Response Error:", re.Error.Error())
	})

	// Endpoints for the front-end
	// (use this service for the example but a specific users' service may be better in some case)
	http.HandleFunc("/api/v1/auth/signup", handler.SignupHandler)
	http.HandleFunc("/api/v1/auth/signin", handler.SigninHandler)
	http.HandleFunc("/api/v1/auth/signout", handler.SignoutHandler)

	// Endpoints specific to validate the authorization
	http.HandleFunc("/api/v1/auth/oauth/authorize", handler.Authorize)
	http.HandleFunc("/api/v1/auth/oauth/token", handler.Token)

	// Endpoint which validate a client's token and the given permission
	http.HandleFunc("/api/v1/auth/jwtvalidation", handler.JwtValidation)
	http.HandleFunc("/api/v1/auth/jwtgetdata", handler.JwtGetdata)
	http.HandleFunc("/api/v1/auth/permission", handler.ValidPermission)
	http.HandleFunc("/api/v1/auth/refreshopenid", handler.RefreshOpenid)

	log.Printf("Server is running at %d port.\n", portvar)
	log.Printf("Point your OAuth client Auth endpoint to %s:%d%s", "http://localhost", portvar, "/oauth/authorize")
	log.Printf("Point your OAuth client Token endpoint to %s:%d%s", "http://localhost", portvar, "/oauth/token")
	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", portvar), nil))
}


The handlers
    package handlers

import (
	"context"
	"fmt"
	"net/http"
	// "net/url"
	"os"
	"sync"

	"github.com/go-oauth2/oauth2/v4/server"
)

unc NewAuthentication(srv *server.Server) Authentication {

	return Authentication{
		srv:           srv,
		extStore:      make(map[string]interface{}),
		databaseUsers: make(map[string]interface{}),
	}
}

func (a Authentication) Authorize(w http.ResponseWriter, r *http.Request) {

	if carryon := allowCORS(w, r); !carryon {
		return
	}

	if dumpvar {
		dumpRequest(os.Stdout, "authorize", r)
	}

	err := a.srv.HandleAuthorizeRequest(w, r)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
	}

}

func (a Authentication) RefreshOpenid(w http.ResponseWriter, r *http.Request) {

	// see the incoming r
	if dumpvar {
		_ = dumpRequest(os.Stdout, "openidRefresh", r) // Ignore the error
	}

	// pass the request to the openid logic
	_ = a.srv.RefreshOpenidToken(context.TODO(), w, r)

}

// TODO also see where to set the token validity ???
func (a Authentication) UserCustomizeTokenPayloadHandler(r *http.Request, data map[string]interface{}) (error, interface{}) {

	// Do whatever we like with the returned JWT
    // save the the refresh JWT in DB for ex
	fmt.Println("app/authentication.go - UserCustomizeTokenPayloadHandler, refreshTK: ", data["refresh_token"])

    // for ex
	fmt.Println("app/authentication.go - UserCustomizeTokenPayloadHandler, userAccount: ", r.FormValue("email"))

	// return only the access_token for ex
	return nil, data["access_token"]
}

// configure the jwt setting and user credentials to pass into 
func (a Authentication) UserOpenidHandler(w http.ResponseWriter, r *http.Request) (jwtInfo map[string]interface{}, keyID string, secretKey string, encoding string, err error) {
	if dumpvar {
		_ = dumpRequest(os.Stdout, "userOpenidHandler", r) // Ignore the error
	}

	err = nil

	keyID = "theKeyID"
	secretKey = "mySecretKey"
	encoding = "HS256"

	// create the data we like to set into the jwt token
	jwtInfo = make(map[string]interface{})

	jwtInfo["name"] = "Robert"
	jwtInfo["age"] = 35
	jwtInfo["city"] = "London"

	return
}

// UserOpenIDHandler will query the userData itself
// This is link to signin and signup handlers implementation, see the examples
func (a Authentication) UserAuthorizeHandler(w http.ResponseWriter, r *http.Request) (userID string, err error) {

	if dumpvar {
		_ = dumpRequest(os.Stdout, "userAuthorizeHandler", r) // Ignore the error
	}

	clientID := r.Form.Get("client_id")

	switch clientID {
	case "222222":

		a.RLock()
		uid, ok := a.extStore[fmt.Sprintf("LoggedInUserID-%v", r.Form.Get("email"))]
		a.RUnlock()
		if !ok {
			if r.Form == nil {
				r.ParseForm()
			}

			w.WriteHeader(http.StatusOK)
			return
		}

		fmt.Println("Authentication.go see the req, look for user cred: ", r)

		userID = uid.(string)

		a.Lock()
		delete(a.extStore, fmt.Sprintf("LoggedInUserID-%v", r.Form.Get("email")))
		a.Unlock()
		return
	case "888888":

		a.RLock()
		uid, ok := a.extStore[fmt.Sprintf("LoggedInUserID-%v", r.Form.Get("client_id"))]
		a.RUnlock()
		if !ok {
			if r.Form == nil {
				r.ParseForm()
			}

			w.WriteHeader(http.StatusOK)
			return
		}

		userID = uid.(string)

		a.Lock()
		delete(a.extStore, fmt.Sprintf("LoggedInUserID-%v", r.Form.Get("client_id")))
		a.Unlock()
		return
	default:
		userID = ""
		return
	}
}



Open in your web browser

Authorization Request: http://localhost:9096/authorize?client_id=000000&response_type=code

Grant Token Request: [http://localhost:9096/token?grant_type=client_credentials&client_id=000000&client_secret=999999&scope=read](http://localhost:9096/token?grant_type=client_credentials&client_id=000000&client_secret=999999&scope=read, openid)

Example(under development)

Store Implements

Handy Utilities

MIT License

Copyright (c) 2016 Lyric Copyright (c) 2023 Jerome Bidault

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type AccessGenerate

type AccessGenerate interface {
	Token(ctx context.Context, data *GenerateBasic, isGenRefresh bool) (access, refresh string, err error)
}

AccessGenerate generate the access and refresh tokens interface

type AuthorizeGenerate

type AuthorizeGenerate interface {
	Token(ctx context.Context, data *GenerateBasic) (code string, err error)
}

AuthorizeGenerate generate the authorization code interface

type ClientInfo

type ClientInfo interface {
	GetID() string
	GetSecret() string
	GetDomain() string
	IsPublic() bool
	GetUserID() string
}

ClientInfo the client information model interface

type ClientPasswordVerifier

type ClientPasswordVerifier interface {
	VerifyPassword(string) bool
}

ClientPasswordVerifier the password handler interface

type ClientStore

type ClientStore interface {
	// according to the ID for the client information
	GetByID(ctx context.Context, id string) (ClientInfo, error)

	// NOTE
	// Remove a client if needed
	RemoveByID(id string) (err error)

	// NOTE
	// Add or update a client jwt(specific to svc clients)
	UpsertClientJWToken(ctx context.Context, id, JWToken string) (err error)
}

ClientStore the client information storage interface

type CodeChallengeMethod

type CodeChallengeMethod string

CodeChallengeMethod PCKE method

const (
	// CodeChallengePlain PCKE Method
	CodeChallengePlain CodeChallengeMethod = "plain"
	// CodeChallengeS256 PCKE Method
	CodeChallengeS256 CodeChallengeMethod = "S256"
)

func (CodeChallengeMethod) String

func (ccm CodeChallengeMethod) String() string

func (CodeChallengeMethod) Validate

func (ccm CodeChallengeMethod) Validate(cc, ver string) bool

Validate code challenge

type GenerateBasic

type GenerateBasic struct {
	Client    ClientInfo
	UserID    string
	CreateAt  time.Time
	TokenInfo TokenInfo
	Request   *http.Request
}

GenerateBasic provide the basis of the generated token data

type GrantType

type GrantType string

GrantType authorization model

const (
	AuthorizationCode   GrantType = "authorization_code"
	PasswordCredentials GrantType = "password"
	ClientCredentials   GrantType = "client_credentials"
	Refreshing          GrantType = "refresh_token"
	Implicit            GrantType = "__implicit"
)

define authorization model

func (GrantType) String

func (gt GrantType) String() string

type JWTAccessGenerate

type JWTAccessGenerate interface {
	CreateJWTAccessGenerate(kid string, key []byte, meth ...string) JWTAccessGenerate
	GenerateOpenidJWToken(ctx context.Context, tokenInfo TokenInfo, isGenRefresh bool, openidInfo OpenidInfo) (string, string, error)
	ValidOpenidJWToken(ctx context.Context, tokenSecret string) error
	GetdataOpenidJWToken(ctx context.Context, tokenSecret string) (map[string]interface{}, error)
	GetdataAdminOpenidJWToken(ctx context.Context, tokenSecret string) (map[string]interface{}, error)
	// GetTokensOpenidJWToken(ctx context.Context, tokenSecret string) (error, map[string]interface{})
	// GetOauthTokensFromOpenidJWToken(ctx context.Context, tokenSecret string) (OpenidInfo, string, string, error)
	Token(ctx context.Context, data *GenerateBasic, isGenRefresh bool) (string, string, error)
}

type Manager

type Manager interface {
	// get the client information
	GetClient(ctx context.Context, clientID string) (cli ClientInfo, err error)

	// generate the authorization token(code)
	GenerateAuthToken(ctx context.Context, rt ResponseType, tgr *TokenGenerateRequest) (authToken TokenInfo, err error)

	// generate the access token
	GenerateAccessToken(ctx context.Context, gt GrantType, tgr *TokenGenerateRequest) (accessToken TokenInfo, err error)

	// refreshing an access token
	RefreshAccessToken(ctx context.Context, tgr *TokenGenerateRequest) (accessToken TokenInfo, err error)

	// use the access token to delete the token information
	RemoveAccessToken(ctx context.Context, access string) (err error)

	// use the refresh token to delete the token information
	RemoveRefreshToken(ctx context.Context, refresh string) (err error)

	// according to the access token for corresponding token information
	LoadAccessToken(ctx context.Context, access string) (ti TokenInfo, err error)

	// according to the refresh token for corresponding token information
	LoadRefreshToken(ctx context.Context, refresh string) (ti TokenInfo, err error)

	CreateJWTAccessGenerate(keyID string, secretKey []byte, signInMethod ...string) JWTAccessGenerate

	DeleteAuthorizationCode(ctx context.Context, code string) error

	// RefreshTokens refresh access and refresh JWT tokens
	RefreshTokens(ctx context.Context, refresh string) (TokenInfo, error)

	// use the access token to delete all the tokens information
	RemoveAllTokensByAccessToken(ctx context.Context, access string) (err error)

	// use the refresh token to delete all the tokens information
	RemoveAllTokensByRefreshToken(ctx context.Context, refresh string) (err error)

	// UpsertJWTClient upsert a jwtoken to the client
	UpsertClientJWToken(ctx context.Context, id, JWToken string) error
}

Manager authorization management interface

type OpenidInfo

type OpenidInfo map[string]interface{}

type ResponseType

type ResponseType string

ResponseType the type of authorization request

const (
	Code  ResponseType = "code"
	Token ResponseType = "token"
)

define the type of authorization request

func (ResponseType) String

func (rt ResponseType) String() string

type TokenGenerateRequest

type TokenGenerateRequest struct {
	ClientID            string
	ClientSecret        string
	UserID              string
	RedirectURI         string
	Scope               string
	Role                string
	Code                string
	CodeChallenge       string
	CodeChallengeMethod CodeChallengeMethod
	Refresh             string
	CodeVerifier        string
	AccessTokenExp      time.Duration
	Request             *http.Request
}

TokenGenerateRequest provide to generate the token request parameters

type TokenInfo

type TokenInfo interface {
	New() TokenInfo

	GetClientID() string
	SetClientID(string)
	GetUserID() string
	SetUserID(string)
	GetRedirectURI() string
	SetRedirectURI(string)
	GetScope() string
	SetScope(string)

	GetCode() string
	SetCode(string)
	GetCodeCreateAt() time.Time
	SetCodeCreateAt(time.Time)
	GetCodeExpiresIn() time.Duration
	SetCodeExpiresIn(time.Duration)
	GetCodeChallenge() string
	SetCodeChallenge(string)
	GetCodeChallengeMethod() CodeChallengeMethod
	SetCodeChallengeMethod(CodeChallengeMethod)
	//
	SetRole(string)
	GetRole() string

	GetAccess() string
	SetAccess(string)
	GetAccessCreateAt() time.Time
	SetAccessCreateAt(time.Time)
	GetAccessExpiresIn() time.Duration
	SetAccessExpiresIn(time.Duration)

	GetRefresh() string
	SetRefresh(string)
	GetRefreshCreateAt() time.Time
	SetRefreshCreateAt(time.Time)
	GetRefreshExpiresIn() time.Duration
	SetRefreshExpiresIn(time.Duration)
}

TokenInfo the token information model interface

type TokenStore

type TokenStore interface {
	// create and store the new token information
	Create(ctx context.Context, info TokenInfo) error

	// delete the authorization code
	RemoveByCode(ctx context.Context, code string) error

	// use the access token to delete the token information
	RemoveByAccess(ctx context.Context, access string) error

	// use the refresh token to delete the token information
	RemoveByRefresh(ctx context.Context, refresh string) error

	// NOTE
	// use the access token to delete all the tokens
	RemoveAllTokensByAccess(ctx context.Context, access string) error

	// NOTE
	// use the refresh token to delete all the tokens
	RemoveAllTokensByRefresh(ctx context.Context, refresh string) error

	// use the authorization code for token information data
	GetByCode(ctx context.Context, code string) (TokenInfo, error)

	// use the access token for token information data
	GetByAccess(ctx context.Context, access string) (TokenInfo, error)

	// use the refresh token for token information data
	GetByRefresh(ctx context.Context, refresh string) (TokenInfo, error)
}

TokenStore the token information storage interface

Directories

Path Synopsis
example

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL