fix examples
This commit is contained in:
parent
3dd0d5fc3a
commit
5c5d716409
6 changed files with 533 additions and 14 deletions
118
example/server/dynamic/login.go
Normal file
118
example/server/dynamic/login.go
Normal file
|
@ -0,0 +1,118 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/caos/oidc/pkg/op"
|
||||
)
|
||||
|
||||
const (
|
||||
queryAuthRequestID = "authRequestID"
|
||||
)
|
||||
|
||||
var (
|
||||
loginTmpl, _ = template.New("login").Parse(`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Login</title>
|
||||
</head>
|
||||
<body style="display: flex; align-items: center; justify-content: center; height: 100vh;">
|
||||
<form method="POST" action="/login/username" style="height: 200px; width: 200px;">
|
||||
|
||||
<input type="hidden" name="id" value="{{.ID}}">
|
||||
|
||||
<div>
|
||||
<label for="username">Username:</label>
|
||||
<input id="username" name="username" style="width: 100%">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="password">Password:</label>
|
||||
<input id="password" name="password" style="width: 100%">
|
||||
</div>
|
||||
|
||||
<p style="color:red; min-height: 1rem;">{{.Error}}</p>
|
||||
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>`)
|
||||
)
|
||||
|
||||
type login struct {
|
||||
authenticate authenticate
|
||||
router *mux.Router
|
||||
callback func(context.Context, string) string
|
||||
}
|
||||
|
||||
func NewLogin(authenticate authenticate, callback func(context.Context, string) string, issuerInterceptor *op.IssuerInterceptor) *login {
|
||||
l := &login{
|
||||
authenticate: authenticate,
|
||||
callback: callback,
|
||||
}
|
||||
l.createRouter(issuerInterceptor)
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *login) createRouter(issuerInterceptor *op.IssuerInterceptor) {
|
||||
l.router = mux.NewRouter()
|
||||
l.router.Path("/username").Methods("GET").HandlerFunc(l.loginHandler)
|
||||
l.router.Path("/username").Methods("POST").HandlerFunc(issuerInterceptor.HandlerFunc(l.checkLoginHandler))
|
||||
}
|
||||
|
||||
type authenticate interface {
|
||||
CheckUsernamePassword(ctx context.Context, username, password, id string) error
|
||||
}
|
||||
|
||||
func (l *login) loginHandler(w http.ResponseWriter, r *http.Request) {
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("cannot parse form:%s", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
//the oidc package will pass the id of the auth request as query parameter
|
||||
//we will use this id through the login process and therefore pass it to the login page
|
||||
renderLogin(w, r.FormValue(queryAuthRequestID), nil)
|
||||
}
|
||||
|
||||
func renderLogin(w http.ResponseWriter, id string, err error) {
|
||||
var errMsg string
|
||||
if err != nil {
|
||||
errMsg = err.Error()
|
||||
}
|
||||
data := &struct {
|
||||
ID string
|
||||
Error string
|
||||
}{
|
||||
ID: id,
|
||||
Error: errMsg,
|
||||
}
|
||||
err = loginTmpl.Execute(w, data)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *login) checkLoginHandler(w http.ResponseWriter, r *http.Request) {
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("cannot parse form:%s", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
username := r.FormValue("username")
|
||||
password := r.FormValue("password")
|
||||
id := r.FormValue("id")
|
||||
err = l.authenticate.CheckUsernamePassword(r.Context(), username, password, id)
|
||||
if err != nil {
|
||||
renderLogin(w, id, err)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, l.callback(r.Context(), id), http.StatusFound)
|
||||
}
|
138
example/server/dynamic/op.go
Normal file
138
example/server/dynamic/op.go
Normal file
|
@ -0,0 +1,138 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/caos/oidc/example/server/internal"
|
||||
"github.com/caos/oidc/pkg/op"
|
||||
)
|
||||
|
||||
const (
|
||||
pathLoggedOut = "/logged-out"
|
||||
)
|
||||
|
||||
var (
|
||||
hostnames = []string{
|
||||
"localhost", //note that calling 127.0.0.1 / ::1 won't work as the hostname does not match
|
||||
"oidc.local", //add this to your hosts file (pointing to 127.0.0.1)
|
||||
//feel free to add more...
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
internal.RegisterClients(
|
||||
internal.NativeClient("native"),
|
||||
internal.WebClient("web", "secret"),
|
||||
internal.WebClient("api", "secret"),
|
||||
)
|
||||
}
|
||||
|
||||
func main() {
|
||||
ctx := context.Background()
|
||||
|
||||
port := "9998"
|
||||
issuers := make([]string, len(hostnames))
|
||||
for i, hostname := range hostnames {
|
||||
issuers[i] = fmt.Sprintf("http://%s:%s", hostname, port)
|
||||
}
|
||||
|
||||
//the OpenID Provider requires a 32-byte key for (token) encryption
|
||||
//be sure to create a proper crypto random key and manage it securely!
|
||||
key := sha256.Sum256([]byte("test"))
|
||||
|
||||
router := mux.NewRouter()
|
||||
|
||||
//for simplicity, we provide a very small default page for users who have signed out
|
||||
router.HandleFunc(pathLoggedOut, func(w http.ResponseWriter, req *http.Request) {
|
||||
_, err := w.Write([]byte("signed out successfully"))
|
||||
if err != nil {
|
||||
log.Printf("error serving logged out page: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
//the OpenIDProvider interface needs a Storage interface handling various checks and state manipulations
|
||||
//this might be the layer for accessing your database
|
||||
//in this example it will be handled in-memory
|
||||
//the NewMultiStorage is able to handle multiple issuers
|
||||
storage := internal.NewMultiStorage(issuers)
|
||||
|
||||
//creation of the OpenIDProvider with the just created in-memory Storage
|
||||
provider, err := newDynamicOP(ctx, storage, key)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
//the provider will only take care of the OpenID Protocol, so there must be some sort of UI for the login process
|
||||
//for the simplicity of the example this means a simple page with username and password field
|
||||
//be sure to provide an IssuerInterceptor with the IssuerFromRequest from the OP so the login can select / and pass it to the storage
|
||||
l := NewLogin(storage, op.AuthCallbackURL(provider), op.NewIssuerInterceptor(provider.IssuerFromRequest))
|
||||
|
||||
//regardless of how many pages / steps there are in the process, the UI must be registered in the router,
|
||||
//so we will direct all calls to /login to the login UI
|
||||
router.PathPrefix("/login/").Handler(http.StripPrefix("/login", l.router))
|
||||
|
||||
//we register the http handler of the OP on the root, so that the discovery endpoint (/.well-known/openid-configuration)
|
||||
//is served on the correct path
|
||||
//
|
||||
//if your issuer ends with a path (e.g. http://localhost:9998/custom/path/),
|
||||
//then you would have to set the path prefix (/custom/path/):
|
||||
//router.PathPrefix("/custom/path/").Handler(http.StripPrefix("/custom/path", provider.HttpHandler()))
|
||||
router.PathPrefix("/").Handler(provider.HttpHandler())
|
||||
|
||||
server := &http.Server{
|
||||
Addr: ":" + port,
|
||||
Handler: router,
|
||||
}
|
||||
err = server.ListenAndServe()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
<-ctx.Done()
|
||||
}
|
||||
|
||||
//newDynamicOP will create an OpenID Provider for localhost on a specified port with a given encryption key
|
||||
//and a predefined default logout uri
|
||||
//it will enable all options (see descriptions)
|
||||
func newDynamicOP(ctx context.Context, storage op.Storage, key [32]byte) (*op.Provider, error) {
|
||||
config := &op.Config{
|
||||
CryptoKey: key,
|
||||
|
||||
//will be used if the end_session endpoint is called without a post_logout_redirect_uri
|
||||
DefaultLogoutRedirectURI: pathLoggedOut,
|
||||
|
||||
//enables code_challenge_method S256 for PKCE (and therefore PKCE in general)
|
||||
CodeMethodS256: true,
|
||||
|
||||
//enables additional client_id/client_secret authentication by form post (not only HTTP Basic Auth)
|
||||
AuthMethodPost: true,
|
||||
|
||||
//enables additional authentication by using private_key_jwt
|
||||
AuthMethodPrivateKeyJWT: true,
|
||||
|
||||
//enables refresh_token grant use
|
||||
GrantTypeRefreshToken: true,
|
||||
|
||||
//enables use of the `request` Object parameter
|
||||
RequestObjectSupported: true,
|
||||
|
||||
//this example has only static texts (in English), so we'll set the here accordingly
|
||||
SupportedUILocales: []language.Tag{language.English},
|
||||
}
|
||||
handler, err := op.NewDynamicOpenIDProvider(ctx, "/", config, storage,
|
||||
//we must explicitly allow the use of the http issuer
|
||||
op.WithAllowInsecure(),
|
||||
//as an example on how to customize an endpoint this will change the authorization_endpoint from /authorize to /auth
|
||||
op.WithCustomAuthEndpoint(op.NewEndpoint("auth")),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return handler, nil
|
||||
}
|
|
@ -6,6 +6,7 @@ import (
|
|||
"crypto/rsa"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
@ -80,7 +81,8 @@ func (s *publicKey) Key() interface{} {
|
|||
return &s.key.PublicKey
|
||||
}
|
||||
|
||||
func NewStorage() *storage {
|
||||
func NewStorage(issuer string) *storage {
|
||||
hostname := strings.Split(strings.Split(issuer, "://")[0], ":")[0]
|
||||
key, _ := rsa.GenerateKey(rand.Reader, 2048)
|
||||
return &storage{
|
||||
authRequests: make(map[string]*AuthRequest),
|
||||
|
@ -91,7 +93,7 @@ func NewStorage() *storage {
|
|||
users: map[string]*User{
|
||||
"id1": {
|
||||
id: "id1",
|
||||
username: "test-user",
|
||||
username: "test-user@" + hostname,
|
||||
password: "verysecure",
|
||||
firstname: "Test",
|
||||
lastname: "User",
|
||||
|
|
260
example/server/internal/storage_dynamic.go
Normal file
260
example/server/internal/storage_dynamic.go
Normal file
|
@ -0,0 +1,260 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
"github.com/caos/oidc/pkg/oidc"
|
||||
"github.com/caos/oidc/pkg/op"
|
||||
)
|
||||
|
||||
type multiStorage struct {
|
||||
issuers map[string]*storage
|
||||
}
|
||||
|
||||
//NewMultiStorage implements the op.Storage interface by wrapping multiple storage structs
|
||||
//and selecting them by the calling issuer
|
||||
func NewMultiStorage(issuers []string) *multiStorage {
|
||||
s := make(map[string]*storage)
|
||||
for _, issuer := range issuers {
|
||||
s[issuer] = NewStorage(issuer)
|
||||
}
|
||||
return &multiStorage{issuers: s}
|
||||
}
|
||||
|
||||
//CheckUsernamePassword implements the `authenticate` interface of the login
|
||||
func (s *multiStorage) CheckUsernamePassword(ctx context.Context, username, password, id string) error {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return storage.CheckUsernamePassword(username, password, id)
|
||||
}
|
||||
|
||||
//CreateAuthRequest implements the op.Storage interface
|
||||
//it will be called after parsing and validation of the authentication request
|
||||
func (s *multiStorage) CreateAuthRequest(ctx context.Context, authReq *oidc.AuthRequest, userID string) (op.AuthRequest, error) {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return storage.CreateAuthRequest(ctx, authReq, userID)
|
||||
}
|
||||
|
||||
//AuthRequestByID implements the op.Storage interface
|
||||
//it will be called after the Login UI redirects back to the OIDC endpoint
|
||||
func (s *multiStorage) AuthRequestByID(ctx context.Context, id string) (op.AuthRequest, error) {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return storage.AuthRequestByID(ctx, id)
|
||||
}
|
||||
|
||||
//AuthRequestByCode implements the op.Storage interface
|
||||
//it will be called after parsing and validation of the token request (in an authorization code flow)
|
||||
func (s *multiStorage) AuthRequestByCode(ctx context.Context, code string) (op.AuthRequest, error) {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return storage.AuthRequestByCode(ctx, code)
|
||||
}
|
||||
|
||||
//SaveAuthCode implements the op.Storage interface
|
||||
//it will be called after the authentication has been successful and before redirecting the user agent to the redirect_uri
|
||||
//(in an authorization code flow)
|
||||
func (s *multiStorage) SaveAuthCode(ctx context.Context, id string, code string) error {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return storage.SaveAuthCode(ctx, id, code)
|
||||
}
|
||||
|
||||
//DeleteAuthRequest implements the op.Storage interface
|
||||
//it will be called after creating the token response (id and access tokens) for a valid
|
||||
//- authentication request (in an implicit flow)
|
||||
//- token request (in an authorization code flow)
|
||||
func (s *multiStorage) DeleteAuthRequest(ctx context.Context, id string) error {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return storage.DeleteAuthRequest(ctx, id)
|
||||
}
|
||||
|
||||
//CreateAccessToken implements the op.Storage interface
|
||||
//it will be called for all requests able to return an access token (Authorization Code Flow, Implicit Flow, JWT Profile, ...)
|
||||
func (s *multiStorage) CreateAccessToken(ctx context.Context, request op.TokenRequest) (string, time.Time, error) {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return "", time.Time{}, err
|
||||
}
|
||||
return storage.CreateAccessToken(ctx, request)
|
||||
}
|
||||
|
||||
//CreateAccessAndRefreshTokens implements the op.Storage interface
|
||||
//it will be called for all requests able to return an access and refresh token (Authorization Code Flow, Refresh Token Request)
|
||||
func (s *multiStorage) CreateAccessAndRefreshTokens(ctx context.Context, request op.TokenRequest, currentRefreshToken string) (accessTokenID string, newRefreshToken string, expiration time.Time, err error) {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return "", "", time.Time{}, err
|
||||
}
|
||||
return storage.CreateAccessAndRefreshTokens(ctx, request, currentRefreshToken)
|
||||
}
|
||||
|
||||
//TokenRequestByRefreshToken implements the op.Storage interface
|
||||
//it will be called after parsing and validation of the refresh token request
|
||||
func (s *multiStorage) TokenRequestByRefreshToken(ctx context.Context, refreshToken string) (op.RefreshTokenRequest, error) {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return storage.TokenRequestByRefreshToken(ctx, refreshToken)
|
||||
}
|
||||
|
||||
//TerminateSession implements the op.Storage interface
|
||||
//it will be called after the user signed out, therefore the access and refresh token of the user of this client must be removed
|
||||
func (s *multiStorage) TerminateSession(ctx context.Context, userID string, clientID string) error {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return storage.TerminateSession(ctx, userID, clientID)
|
||||
}
|
||||
|
||||
//RevokeToken implements the op.Storage interface
|
||||
//it will be called after parsing and validation of the token revocation request
|
||||
func (s *multiStorage) RevokeToken(ctx context.Context, token string, userID string, clientID string) *oidc.Error {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return storage.RevokeToken(ctx, token, userID, clientID)
|
||||
}
|
||||
|
||||
//SigningKey implements the op.Storage interface
|
||||
//it will be called when creating the OpenID Provider
|
||||
func (s *multiStorage) SigningKey(ctx context.Context) (op.SigningKey, error) {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return storage.SigningKey(ctx)
|
||||
}
|
||||
|
||||
//SignatureAlgorithms implements the op.Storage interface
|
||||
//it will be called to get the sign
|
||||
func (s *multiStorage) SignatureAlgorithms(ctx context.Context) ([]jose.SignatureAlgorithm, error) {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return storage.SignatureAlgorithms(ctx)
|
||||
}
|
||||
|
||||
//KeySet implements the op.Storage interface
|
||||
//it will be called to get the current (public) keys, among others for the keys_endpoint or for validating access_tokens on the userinfo_endpoint, ...
|
||||
func (s *multiStorage) KeySet(ctx context.Context) ([]op.Key, error) {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return storage.KeySet(ctx)
|
||||
}
|
||||
|
||||
//GetClientByClientID implements the op.Storage interface
|
||||
//it will be called whenever information (type, redirect_uris, ...) about the client behind the client_id is needed
|
||||
func (s *multiStorage) GetClientByClientID(ctx context.Context, clientID string) (op.Client, error) {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return storage.GetClientByClientID(ctx, clientID)
|
||||
}
|
||||
|
||||
//AuthorizeClientIDSecret implements the op.Storage interface
|
||||
//it will be called for validating the client_id, client_secret on token or introspection requests
|
||||
func (s *multiStorage) AuthorizeClientIDSecret(ctx context.Context, clientID, clientSecret string) error {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return storage.AuthorizeClientIDSecret(ctx, clientID, clientSecret)
|
||||
}
|
||||
|
||||
//SetUserinfoFromScopes implements the op.Storage interface
|
||||
//it will be called for the creation of an id_token, so we'll just pass it to the private function without any further check
|
||||
func (s *multiStorage) SetUserinfoFromScopes(ctx context.Context, userinfo oidc.UserInfoSetter, userID, clientID string, scopes []string) error {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return storage.SetUserinfoFromScopes(ctx, userinfo, userID, clientID, scopes)
|
||||
}
|
||||
|
||||
//SetUserinfoFromToken implements the op.Storage interface
|
||||
//it will be called for the userinfo endpoint, so we read the token and pass the information from that to the private function
|
||||
func (s *multiStorage) SetUserinfoFromToken(ctx context.Context, userinfo oidc.UserInfoSetter, tokenID, subject, origin string) error {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return storage.SetUserinfoFromToken(ctx, userinfo, tokenID, subject, origin)
|
||||
}
|
||||
|
||||
//SetIntrospectionFromToken implements the op.Storage interface
|
||||
//it will be called for the introspection endpoint, so we read the token and pass the information from that to the private function
|
||||
func (s *multiStorage) SetIntrospectionFromToken(ctx context.Context, introspection oidc.IntrospectionResponse, tokenID, subject, clientID string) error {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return storage.SetIntrospectionFromToken(ctx, introspection, tokenID, subject, clientID)
|
||||
}
|
||||
|
||||
//GetPrivateClaimsFromScopes implements the op.Storage interface
|
||||
//it will be called for the creation of a JWT access token to assert claims for custom scopes
|
||||
func (s *multiStorage) GetPrivateClaimsFromScopes(ctx context.Context, userID, clientID string, scopes []string) (claims map[string]interface{}, err error) {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return storage.GetPrivateClaimsFromScopes(ctx, userID, clientID, scopes)
|
||||
}
|
||||
|
||||
//GetKeyByIDAndUserID implements the op.Storage interface
|
||||
//it will be called to validate the signatures of a JWT (JWT Profile Grant and Authentication)
|
||||
func (s *multiStorage) GetKeyByIDAndUserID(ctx context.Context, keyID, userID string) (*jose.JSONWebKey, error) {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return storage.GetKeyByIDAndUserID(ctx, keyID, userID)
|
||||
}
|
||||
|
||||
//ValidateJWTProfileScopes implements the op.Storage interface
|
||||
//it will be called to validate the scopes of a JWT Profile Authorization Grant request
|
||||
func (s *multiStorage) ValidateJWTProfileScopes(ctx context.Context, userID string, scopes []string) ([]string, error) {
|
||||
storage, err := s.storageFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return storage.ValidateJWTProfileScopes(ctx, userID, scopes)
|
||||
}
|
||||
|
||||
//Health implements the op.Storage interface
|
||||
func (s *multiStorage) Health(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *multiStorage) storageFromContext(ctx context.Context) (*storage, *oidc.Error) {
|
||||
storage, ok := s.issuers[op.IssuerFromContext(ctx)]
|
||||
if !ok {
|
||||
|
||||
}
|
||||
return storage, nil
|
||||
}
|
|
@ -7,8 +7,6 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/caos/oidc/pkg/op"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -52,19 +50,19 @@ type login struct {
|
|||
callback func(context.Context, string) string
|
||||
}
|
||||
|
||||
func NewLogin(authenticate authenticate, callback func(context.Context, string) string, issuerInterceptor *op.IssuerInterceptor) *login {
|
||||
func NewLogin(authenticate authenticate, callback func(context.Context, string) string) *login {
|
||||
l := &login{
|
||||
authenticate: authenticate,
|
||||
callback: callback,
|
||||
}
|
||||
l.createRouter(issuerInterceptor)
|
||||
l.createRouter()
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *login) createRouter(issuerInterceptor *op.IssuerInterceptor) {
|
||||
func (l *login) createRouter() {
|
||||
l.router = mux.NewRouter()
|
||||
l.router.Path("/username").Methods("GET").HandlerFunc(l.loginHandler)
|
||||
l.router.Path("/username").Methods("POST").HandlerFunc(issuerInterceptor.HandlerFunc(l.checkLoginHandler))
|
||||
l.router.Path("/username").Methods("POST").HandlerFunc(l.checkLoginHandler)
|
||||
}
|
||||
|
||||
type authenticate interface {
|
||||
|
|
|
@ -3,6 +3,7 @@ package main
|
|||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
|
@ -28,7 +29,10 @@ func init() {
|
|||
func main() {
|
||||
ctx := context.Background()
|
||||
|
||||
//we will run on :9998
|
||||
port := "9998"
|
||||
//which gives us the issuer: //http://localhost:9998/
|
||||
issuer := fmt.Sprintf("http://localhost:%s/", port)
|
||||
|
||||
//the OpenID Provider requires a 32-byte key for (token) encryption
|
||||
//be sure to create a proper crypto random key and manage it securely!
|
||||
|
@ -47,17 +51,17 @@ func main() {
|
|||
//the OpenIDProvider interface needs a Storage interface handling various checks and state manipulations
|
||||
//this might be the layer for accessing your database
|
||||
//in this example it will be handled in-memory
|
||||
storage := internal.NewStorage()
|
||||
storage := internal.NewStorage(issuer)
|
||||
|
||||
//creation of the OpenIDProvider with the just created in-memory Storage
|
||||
provider, err := newOP(ctx, storage, port, key)
|
||||
provider, err := newOP(ctx, storage, issuer, key)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
//the provider will only take care of the OpenID Protocol, so there must be some sort of UI for the login process
|
||||
//for the simplicity of the example this means a simple page with username and password field
|
||||
l := NewLogin(storage, op.AuthCallbackURL(provider), op.NewIssuerInterceptor(provider.IssuerFromRequest))
|
||||
l := NewLogin(storage, op.AuthCallbackURL(provider))
|
||||
|
||||
//regardless of how many pages / steps there are in the process, the UI must be registered in the router,
|
||||
//so we will direct all calls to /login to the login UI
|
||||
|
@ -85,7 +89,7 @@ func main() {
|
|||
//newOP will create an OpenID Provider for localhost on a specified port with a given encryption key
|
||||
//and a predefined default logout uri
|
||||
//it will enable all options (see descriptions)
|
||||
func newOP(ctx context.Context, storage op.Storage, port string, key [32]byte) (*op.Provider, error) {
|
||||
func newOP(ctx context.Context, storage op.Storage, issuer string, key [32]byte) (*op.Provider, error) {
|
||||
config := &op.Config{
|
||||
CryptoKey: key,
|
||||
|
||||
|
@ -110,8 +114,7 @@ func newOP(ctx context.Context, storage op.Storage, port string, key [32]byte) (
|
|||
//this example has only static texts (in English), so we'll set the here accordingly
|
||||
SupportedUILocales: []language.Tag{language.English},
|
||||
}
|
||||
//handler, err := op.NewOpenIDProvider(ctx, fmt.Sprintf("http://localhost:%s/", port), config, storage,
|
||||
handler, err := op.NewDynamicOpenIDProvider(ctx, "/", config, storage,
|
||||
handler, err := op.NewOpenIDProvider(ctx, issuer, config, storage,
|
||||
//we must explicitly allow the use of the http issuer
|
||||
op.WithAllowInsecure(),
|
||||
//as an example on how to customize an endpoint this will change the authorization_endpoint from /authorize to /auth
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue