diff --git a/.releaserc.js b/.releaserc.js
index 6500ace..7b9f1ce 100644
--- a/.releaserc.js
+++ b/.releaserc.js
@@ -1,5 +1,8 @@
module.exports = {
- branches: ["main"],
+ branches: [
+ {name: "main"},
+ {name: "dynamic-issuer", prerelease: true},
+ ],
plugins: [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
diff --git a/README.md b/README.md
index d0356ce..26a08ec 100644
--- a/README.md
+++ b/README.md
@@ -34,7 +34,7 @@ The most important packages of the library:
/client/app web app / RP demonstrating authorization code flow using various authentication methods (code, PKCE, JWT profile)
/client/github example of the extended OAuth2 library, providing an HTTP client with a reuse token source
/client/service demonstration of JWT Profile Authorization Grant
- /server example of an OpenID Provider implementation including some very basic login UI
+ /server examples of an OpenID Provider implementations (including dynamic) with some very basic login UI
## How To Use It
@@ -44,16 +44,27 @@ Check the `/example` folder where example code for different scenarios is locate
```bash
# start oidc op server
# oidc discovery http://localhost:9998/.well-known/openid-configuration
-go run github.com/zitadel/oidc/example/server
+go run github.com/zitadel/oidc/v2/example/server
# start oidc web client (in a new terminal)
CLIENT_ID=web CLIENT_SECRET=secret ISSUER=http://localhost:9998 SCOPES="openid profile" PORT=9999 go run github.com/zitadel/oidc/example/client/app
```
- open http://localhost:9999/login in your browser
- you will be redirected to op server and the login UI
-- login with user `test-user` and password `verysecure`
+- login with user `test-user@localhost` and password `verysecure`
- the OP will redirect you to the client app, which displays the user info
+for the dynamic issuer, just start it with:
+```bash
+go run github.com/zitadel/oidc/v2/example/server/dynamic
+```
+the oidc web client above will still work, but if you add `oidc.local` (pointing to 127.0.0.1) in your hosts file you can also start it with:
+```bash
+CLIENT_ID=web CLIENT_SECRET=secret ISSUER=http://oidc.local:9998/ SCOPES="openid profile" PORT=9999 go run github.com/zitadel/oidc/v2/example/client/app
+```
+
+> Note: Usernames are suffixed with the hostname (`test-user@localhost` or `test-user@oidc.local`)
+
## Features
| | Code Flow | Implicit Flow | Hybrid Flow | Discovery | PKCE | Token Exchange | mTLS | JWT Profile | Refresh Token | Client Credentials |
diff --git a/example/client/api/api.go b/example/client/api/api.go
index 0ab669d..c475354 100644
--- a/example/client/api/api.go
+++ b/example/client/api/api.go
@@ -12,8 +12,8 @@ import (
"github.com/gorilla/mux"
"github.com/sirupsen/logrus"
- "github.com/zitadel/oidc/pkg/client/rs"
- "github.com/zitadel/oidc/pkg/oidc"
+ "github.com/zitadel/oidc/v2/pkg/client/rs"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
)
const (
diff --git a/example/client/app/app.go b/example/client/app/app.go
index b7f2868..3e5f19c 100644
--- a/example/client/app/app.go
+++ b/example/client/app/app.go
@@ -11,9 +11,9 @@ import (
"github.com/google/uuid"
"github.com/sirupsen/logrus"
- "github.com/zitadel/oidc/pkg/client/rp"
- httphelper "github.com/zitadel/oidc/pkg/http"
- "github.com/zitadel/oidc/pkg/oidc"
+ "github.com/zitadel/oidc/v2/pkg/client/rp"
+ httphelper "github.com/zitadel/oidc/v2/pkg/http"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
)
var (
diff --git a/example/client/github/github.go b/example/client/github/github.go
index feb3e26..57bb3ae 100644
--- a/example/client/github/github.go
+++ b/example/client/github/github.go
@@ -10,9 +10,9 @@ import (
"golang.org/x/oauth2"
githubOAuth "golang.org/x/oauth2/github"
- "github.com/zitadel/oidc/pkg/client/rp"
- "github.com/zitadel/oidc/pkg/client/rp/cli"
- "github.com/zitadel/oidc/pkg/http"
+ "github.com/zitadel/oidc/v2/pkg/client/rp"
+ "github.com/zitadel/oidc/v2/pkg/client/rp/cli"
+ "github.com/zitadel/oidc/v2/pkg/http"
)
var (
diff --git a/example/client/service/service.go b/example/client/service/service.go
index b3819d5..9526174 100644
--- a/example/client/service/service.go
+++ b/example/client/service/service.go
@@ -13,7 +13,7 @@ import (
"github.com/sirupsen/logrus"
"golang.org/x/oauth2"
- "github.com/zitadel/oidc/pkg/client/profile"
+ "github.com/zitadel/oidc/v2/pkg/client/profile"
)
var client = http.DefaultClient
diff --git a/example/doc.go b/example/doc.go
index 7212a7d..fd4f038 100644
--- a/example/doc.go
+++ b/example/doc.go
@@ -5,7 +5,6 @@ Package example contains some example of the various use of this library:
/app web app / RP demonstrating authorization code flow using various authentication methods (code, PKCE, JWT profile)
/github example of the extended OAuth2 library, providing an HTTP client with a reuse token source
/service demonstration of JWT Profile Authorization Grant
-/server example of an OpenID Provider implementation including some very basic login UI
-
+/server examples of an OpenID Provider implementations (including dynamic) with some very basic
*/
package example
diff --git a/example/server/dynamic/login.go b/example/server/dynamic/login.go
new file mode 100644
index 0000000..e7c6e5f
--- /dev/null
+++ b/example/server/dynamic/login.go
@@ -0,0 +1,113 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "html/template"
+ "net/http"
+
+ "github.com/gorilla/mux"
+
+ "github.com/zitadel/oidc/v2/pkg/op"
+)
+
+const (
+ queryAuthRequestID = "authRequestID"
+)
+
+var (
+ loginTmpl, _ = template.New("login").Parse(`
+
+
+
+
+ Login
+
+
+
+
+ `)
+)
+
+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)
+}
diff --git a/example/server/dynamic/op.go b/example/server/dynamic/op.go
new file mode 100644
index 0000000..02c12b2
--- /dev/null
+++ b/example/server/dynamic/op.go
@@ -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/zitadel/oidc/v2/example/server/storage"
+ "github.com/zitadel/oidc/v2/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() {
+ storage.RegisterClients(
+ storage.NativeClient("native"),
+ storage.WebClient("web", "secret"),
+ storage.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 := storage.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
+}
diff --git a/example/server/exampleop/login.go b/example/server/exampleop/login.go
index fd3dead..5da86d1 100644
--- a/example/server/exampleop/login.go
+++ b/example/server/exampleop/login.go
@@ -1,6 +1,7 @@
package exampleop
import (
+ "context"
"fmt"
"html/template"
"net/http"
@@ -44,10 +45,10 @@ var loginTmpl, _ = template.New("login").Parse(`
type login struct {
authenticate authenticate
router *mux.Router
- callback func(string) string
+ callback func(context.Context, string) string
}
-func NewLogin(authenticate authenticate, callback func(string) string) *login {
+func NewLogin(authenticate authenticate, callback func(context.Context, string) string) *login {
l := &login{
authenticate: authenticate,
callback: callback,
@@ -109,5 +110,5 @@ func (l *login) checkLoginHandler(w http.ResponseWriter, r *http.Request) {
renderLogin(w, id, err)
return
}
- http.Redirect(w, r, l.callback(id), http.StatusFound)
+ http.Redirect(w, r, l.callback(r.Context(), id), http.StatusFound)
}
diff --git a/example/server/exampleop/op.go b/example/server/exampleop/op.go
index 4794d8a..d3a450c 100644
--- a/example/server/exampleop/op.go
+++ b/example/server/exampleop/op.go
@@ -5,13 +5,12 @@ import (
"crypto/sha256"
"log"
"net/http"
- "os"
"github.com/gorilla/mux"
"golang.org/x/text/language"
- "github.com/zitadel/oidc/example/server/storage"
- "github.com/zitadel/oidc/pkg/op"
+ "github.com/zitadel/oidc/v2/example/server/storage"
+ "github.com/zitadel/oidc/v2/pkg/op"
)
const (
@@ -35,9 +34,6 @@ type Storage interface {
//
// Use one of the pre-made clients in storage/clients.go or register a new one.
func SetupServer(ctx context.Context, issuer string, storage Storage) *mux.Router {
- // this will allow us to use an issuer with http:// instead of https://
- os.Setenv(op.OidcDevMode, "true")
-
// 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"))
@@ -81,7 +77,6 @@ func SetupServer(ctx context.Context, issuer string, storage Storage) *mux.Route
// it will enable all options (see descriptions)
func newOP(ctx context.Context, storage op.Storage, issuer string, key [32]byte) (op.OpenIDProvider, error) {
config := &op.Config{
- Issuer: issuer,
CryptoKey: key,
// will be used if the end_session endpoint is called without a post_logout_redirect_uri
@@ -105,7 +100,9 @@ func newOP(ctx context.Context, storage op.Storage, issuer 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, 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
op.WithCustomAuthEndpoint(op.NewEndpoint("auth")),
)
diff --git a/example/server/main.go b/example/server/main.go
index 3cfd20d..327e294 100644
--- a/example/server/main.go
+++ b/example/server/main.go
@@ -2,23 +2,28 @@ package main
import (
"context"
+ "fmt"
"log"
"net/http"
- "github.com/zitadel/oidc/example/server/exampleop"
- "github.com/zitadel/oidc/example/server/storage"
+ "github.com/zitadel/oidc/v2/example/server/exampleop"
+ "github.com/zitadel/oidc/v2/example/server/storage"
)
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 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 := storage.NewStorage(storage.NewUserStore())
+ storage := storage.NewStorage(storage.NewUserStore(issuer))
- port := "9998"
- router := exampleop.SetupServer(ctx, "http://localhost:"+port, storage)
+ router := exampleop.SetupServer(ctx, issuer, storage)
server := &http.Server{
Addr: ":" + port,
diff --git a/example/server/storage/client.go b/example/server/storage/client.go
index 0f3a703..bd6ff3c 100644
--- a/example/server/storage/client.go
+++ b/example/server/storage/client.go
@@ -3,8 +3,8 @@ package storage
import (
"time"
- "github.com/zitadel/oidc/pkg/oidc"
- "github.com/zitadel/oidc/pkg/op"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
+ "github.com/zitadel/oidc/v2/pkg/op"
)
var (
@@ -113,14 +113,14 @@ func (c *Client) IsScopeAllowed(scope string) bool {
// IDTokenUserinfoClaimsAssertion allows specifying if claims of scope profile, email, phone and address are asserted into the id_token
// even if an access token if issued which violates the OIDC Core spec
-//(5.4. Requesting Claims using Scope Values: https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims)
+// (5.4. Requesting Claims using Scope Values: https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims)
// some clients though require that e.g. email is always in the id_token when requested even if an access_token is issued
func (c *Client) IDTokenUserinfoClaimsAssertion() bool {
return c.idTokenUserinfoClaimsAssertion
}
// ClockSkew enables clients to instruct the OP to apply a clock skew on the various times and expirations
-//(subtract from issued_at, add to expiration, ...)
+// (subtract from issued_at, add to expiration, ...)
func (c *Client) ClockSkew() time.Duration {
return c.clockSkew
}
@@ -141,7 +141,7 @@ func RegisterClients(registerClients ...*Client) {
// user-defined redirectURIs may include:
// - http://localhost without port specification (e.g. http://localhost/auth/callback)
// - custom protocol (e.g. custom://auth/callback)
-//(the examples will be used as default, if none is provided)
+// (the examples will be used as default, if none is provided)
func NativeClient(id string, redirectURIs ...string) *Client {
if len(redirectURIs) == 0 {
redirectURIs = []string{
@@ -168,7 +168,7 @@ func NativeClient(id string, redirectURIs ...string) *Client {
// WebClient will create a client of type web, which will always use Basic Auth and allow the use of refresh tokens
// user-defined redirectURIs may include:
// - http://localhost with port specification (e.g. http://localhost:9999/auth/callback)
-//(the example will be used as default, if none is provided)
+// (the example will be used as default, if none is provided)
func WebClient(id, secret string, redirectURIs ...string) *Client {
if len(redirectURIs) == 0 {
redirectURIs = []string{
diff --git a/example/server/storage/oidc.go b/example/server/storage/oidc.go
index 91afd90..505ab72 100644
--- a/example/server/storage/oidc.go
+++ b/example/server/storage/oidc.go
@@ -5,9 +5,8 @@ import (
"golang.org/x/text/language"
- "github.com/zitadel/oidc/pkg/op"
-
- "github.com/zitadel/oidc/pkg/oidc"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
+ "github.com/zitadel/oidc/v2/pkg/op"
)
const (
diff --git a/example/server/storage/storage.go b/example/server/storage/storage.go
index 130822e..64bffc8 100644
--- a/example/server/storage/storage.go
+++ b/example/server/storage/storage.go
@@ -12,8 +12,8 @@ import (
"github.com/google/uuid"
"gopkg.in/square/go-jose.v2"
- "github.com/zitadel/oidc/pkg/oidc"
- "github.com/zitadel/oidc/pkg/op"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
+ "github.com/zitadel/oidc/v2/pkg/op"
)
// serviceKey1 is a public key which will be used for the JWT Profile Authorization Grant
@@ -45,9 +45,41 @@ type Storage struct {
}
type signingKey struct {
- ID string
- Algorithm string
- Key *rsa.PrivateKey
+ id string
+ algorithm jose.SignatureAlgorithm
+ key *rsa.PrivateKey
+}
+
+func (s *signingKey) SignatureAlgorithm() jose.SignatureAlgorithm {
+ return s.algorithm
+}
+
+func (s *signingKey) Key() interface{} {
+ return s.key
+}
+
+func (s *signingKey) ID() string {
+ return s.id
+}
+
+type publicKey struct {
+ signingKey
+}
+
+func (s *publicKey) ID() string {
+ return s.id
+}
+
+func (s *publicKey) Algorithm() jose.SignatureAlgorithm {
+ return s.algorithm
+}
+
+func (s *publicKey) Use() string {
+ return "sig"
+}
+
+func (s *publicKey) Key() interface{} {
+ return &s.key.PublicKey
}
func NewStorage(userStore UserStore) *Storage {
@@ -67,9 +99,9 @@ func NewStorage(userStore UserStore) *Storage {
},
},
signingKey: signingKey{
- ID: "id",
- Algorithm: "RS256",
- Key: key,
+ id: uuid.NewString(),
+ algorithm: jose.RS256,
+ key: key,
},
}
}
@@ -288,41 +320,29 @@ func (s *Storage) RevokeToken(ctx context.Context, tokenIDOrToken string, userID
return nil
}
-// GetSigningKey implements the op.Storage interface
+// SigningKey implements the op.Storage interface
// it will be called when creating the OpenID Provider
-func (s *Storage) GetSigningKey(ctx context.Context, keyCh chan<- jose.SigningKey) {
+func (s *Storage) SigningKey(ctx context.Context) (op.SigningKey, error) {
// in this example the signing key is a static rsa.PrivateKey and the algorithm used is RS256
// you would obviously have a more complex implementation and store / retrieve the key from your database as well
- //
- // the idea of the signing key channel is, that you can (with what ever mechanism) rotate your signing key and
- // switch the key of the signer via this channel
- keyCh <- jose.SigningKey{
- Algorithm: jose.SignatureAlgorithm(s.signingKey.Algorithm), // always tell the signer with algorithm to use
- Key: jose.JSONWebKey{
- KeyID: s.signingKey.ID, // always give the key an id so, that it will include it in the token header as `kid` claim
- Key: s.signingKey.Key,
- },
- }
+ return &s.signingKey, nil
}
-// GetKeySet implements the op.Storage interface
+// SignatureAlgorithms implements the op.Storage interface
+// it will be called to get the sign
+func (s *Storage) SignatureAlgorithms(context.Context) ([]jose.SignatureAlgorithm, error) {
+ return []jose.SignatureAlgorithm{s.signingKey.algorithm}, nil
+}
+
+// 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 *Storage) GetKeySet(ctx context.Context) (*jose.JSONWebKeySet, error) {
+func (s *Storage) KeySet(ctx context.Context) ([]op.Key, error) {
// as mentioned above, this example only has a single signing key without key rotation,
// so it will directly use its public key
//
// when using key rotation you typically would store the public keys alongside the private keys in your database
- // and give both of them an expiration date, with the public key having a longer lifetime (e.g. rotate private key every
- return &jose.JSONWebKeySet{
- Keys: []jose.JSONWebKey{
- {
- KeyID: s.signingKey.ID,
- Algorithm: s.signingKey.Algorithm,
- Use: oidc.KeyUseSignature,
- Key: &s.signingKey.Key.PublicKey,
- },
- },
- }, nil
+ //and give both of them an expiration date, with the public key having a longer lifetime
+ return []op.Key{&publicKey{s.signingKey}}, nil
}
// GetClientByClientID implements the op.Storage interface
diff --git a/example/server/storage/storage_dynamic.go b/example/server/storage/storage_dynamic.go
new file mode 100644
index 0000000..ec6a92e
--- /dev/null
+++ b/example/server/storage/storage_dynamic.go
@@ -0,0 +1,260 @@
+package storage
+
+import (
+ "context"
+ "time"
+
+ "gopkg.in/square/go-jose.v2"
+
+ "github.com/zitadel/oidc/v2/pkg/oidc"
+ "github.com/zitadel/oidc/v2/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(NewUserStore(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 nil, oidc.ErrInvalidRequest().WithDescription("invalid issuer")
+ }
+ return storage, nil
+}
diff --git a/example/server/storage/user.go b/example/server/storage/user.go
index 423af59..82c06d0 100644
--- a/example/server/storage/user.go
+++ b/example/server/storage/user.go
@@ -2,6 +2,7 @@ package storage
import (
"crypto/rsa"
+ "strings"
"golang.org/x/text/language"
)
@@ -33,12 +34,13 @@ type userStore struct {
users map[string]*User
}
-func NewUserStore() UserStore {
+func NewUserStore(issuer string) UserStore {
+ hostname := strings.Split(strings.Split(issuer, "://")[1], ":")[0]
return userStore{
users: map[string]*User{
"id1": {
ID: "id1",
- Username: "test-user",
+ Username: "test-user@" + hostname,
Password: "verysecure",
FirstName: "Test",
LastName: "User",
diff --git a/go.mod b/go.mod
index 18ae410..2691e57 100644
--- a/go.mod
+++ b/go.mod
@@ -1,4 +1,4 @@
-module github.com/zitadel/oidc
+module github.com/zitadel/oidc/v2
go 1.16
@@ -15,9 +15,8 @@ require (
github.com/rs/cors v1.8.3
github.com/sirupsen/logrus v1.9.0
github.com/stretchr/testify v1.8.1
- github.com/zitadel/logging v0.3.4
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43
- golang.org/x/text v0.5.0
+ golang.org/x/text v0.6.0
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
gopkg.in/square/go-jose.v2 v2.6.0
)
diff --git a/go.sum b/go.sum
index ec7b82e..c73eb9d 100644
--- a/go.sum
+++ b/go.sum
@@ -131,13 +131,11 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/cors v1.8.3 h1:O+qNyWn7Z+F9M0ILBHgMVPuB1xTOucVd5gtaYyXBpRo=
github.com/rs/cors v1.8.3/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
-github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
-github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -149,8 +147,6 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
-github.com/zitadel/logging v0.3.4 h1:9hZsTjMMTE3X2LUi0xcF9Q9EdLo+FAezeu52ireBbHM=
-github.com/zitadel/logging v0.3.4/go.mod h1:aPpLQhE+v6ocNK0TWrBrd363hZ95KcI17Q1ixAQwZF0=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@@ -252,7 +248,6 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -272,7 +267,6 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220207234003-57398862261d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
@@ -285,8 +279,8 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
-golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
+golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -420,10 +414,7 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
-gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/pkg/client/client.go b/pkg/client/client.go
index 62f1019..eaa1a80 100644
--- a/pkg/client/client.go
+++ b/pkg/client/client.go
@@ -14,9 +14,9 @@ import (
"golang.org/x/oauth2"
"gopkg.in/square/go-jose.v2"
- "github.com/zitadel/oidc/pkg/crypto"
- httphelper "github.com/zitadel/oidc/pkg/http"
- "github.com/zitadel/oidc/pkg/oidc"
+ "github.com/zitadel/oidc/v2/pkg/crypto"
+ httphelper "github.com/zitadel/oidc/v2/pkg/http"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
)
var Encoder = func() httphelper.Encoder {
diff --git a/pkg/client/jwt_profile.go b/pkg/client/jwt_profile.go
index a711de9..1686de6 100644
--- a/pkg/client/jwt_profile.go
+++ b/pkg/client/jwt_profile.go
@@ -5,8 +5,8 @@ import (
"golang.org/x/oauth2"
- "github.com/zitadel/oidc/pkg/http"
- "github.com/zitadel/oidc/pkg/oidc"
+ "github.com/zitadel/oidc/v2/pkg/http"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
)
// JWTProfileExchange handles the oauth2 jwt profile exchange
diff --git a/pkg/client/profile/jwt_profile.go b/pkg/client/profile/jwt_profile.go
index b29fcaa..a934f7d 100644
--- a/pkg/client/profile/jwt_profile.go
+++ b/pkg/client/profile/jwt_profile.go
@@ -7,8 +7,8 @@ import (
"golang.org/x/oauth2"
"gopkg.in/square/go-jose.v2"
- "github.com/zitadel/oidc/pkg/client"
- "github.com/zitadel/oidc/pkg/oidc"
+ "github.com/zitadel/oidc/v2/pkg/client"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
)
// jwtProfileTokenSource implement the oauth2.TokenSource
diff --git a/pkg/client/rp/cli/cli.go b/pkg/client/rp/cli/cli.go
index 6e30e4e..936f319 100644
--- a/pkg/client/rp/cli/cli.go
+++ b/pkg/client/rp/cli/cli.go
@@ -4,9 +4,9 @@ import (
"context"
"net/http"
- "github.com/zitadel/oidc/pkg/client/rp"
- httphelper "github.com/zitadel/oidc/pkg/http"
- "github.com/zitadel/oidc/pkg/oidc"
+ "github.com/zitadel/oidc/v2/pkg/client/rp"
+ httphelper "github.com/zitadel/oidc/v2/pkg/http"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
)
const (
diff --git a/pkg/client/rp/delegation.go b/pkg/client/rp/delegation.go
index a2b1f00..b16a39e 100644
--- a/pkg/client/rp/delegation.go
+++ b/pkg/client/rp/delegation.go
@@ -1,13 +1,13 @@
package rp
import (
- "github.com/zitadel/oidc/pkg/oidc/grants/tokenexchange"
+ "github.com/zitadel/oidc/v2/pkg/oidc/grants/tokenexchange"
)
// DelegationTokenRequest is an implementation of TokenExchangeRequest
// it exchanges an "urn:ietf:params:oauth:token-type:access_token" with an optional
-//"urn:ietf:params:oauth:token-type:access_token" actor token for an
-//"urn:ietf:params:oauth:token-type:access_token" delegation token
+// "urn:ietf:params:oauth:token-type:access_token" actor token for an
+// "urn:ietf:params:oauth:token-type:access_token" delegation token
func DelegationTokenRequest(subjectToken string, opts ...tokenexchange.TokenExchangeOption) *tokenexchange.TokenExchangeRequest {
return tokenexchange.NewTokenExchangeRequest(subjectToken, tokenexchange.AccessTokenType, opts...)
}
diff --git a/pkg/client/rp/integration_test.go b/pkg/client/rp/integration_test.go
index 6f5f489..e29ddd3 100644
--- a/pkg/client/rp/integration_test.go
+++ b/pkg/client/rp/integration_test.go
@@ -15,28 +15,27 @@ import (
"testing"
"time"
- "github.com/zitadel/oidc/example/server/exampleop"
- "github.com/zitadel/oidc/example/server/storage"
-
"github.com/jeremija/gosubmit"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
- "github.com/zitadel/oidc/pkg/client/rp"
- httphelper "github.com/zitadel/oidc/pkg/http"
- "github.com/zitadel/oidc/pkg/oidc"
+ "github.com/zitadel/oidc/v2/example/server/exampleop"
+ "github.com/zitadel/oidc/v2/example/server/storage"
+ "github.com/zitadel/oidc/v2/pkg/client/rp"
+ httphelper "github.com/zitadel/oidc/v2/pkg/http"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
)
func TestRelyingPartySession(t *testing.T) {
t.Log("------- start example OP ------")
ctx := context.Background()
- exampleStorage := storage.NewStorage(storage.NewUserStore())
+ targetURL := "http://local-site"
+ exampleStorage := storage.NewStorage(storage.NewUserStore(targetURL))
var dh deferredHandler
opServer := httptest.NewServer(&dh)
defer opServer.Close()
t.Logf("auth server at %s", opServer.URL)
dh.Handler = exampleop.SetupServer(ctx, opServer.URL, exampleStorage)
- targetURL := "http://local-site"
localURL, err := url.Parse(targetURL + "/login?requestID=1234")
require.NoError(t, err, "local url")
@@ -109,7 +108,7 @@ func TestRelyingPartySession(t *testing.T) {
t.Log("------- post to login form, get redirect to OP ------")
postLoginRedirectURL := fillForm(t, "fill login form", httpClient, form, loginPageURL,
- gosubmit.Set("username", "test-user"),
+ gosubmit.Set("username", "test-user@local-site"),
gosubmit.Set("password", "verysecure"))
t.Logf("Get redirect from %s", postLoginRedirectURL)
diff --git a/pkg/client/rp/jwks.go b/pkg/client/rp/jwks.go
index cc49eb7..3438bd6 100644
--- a/pkg/client/rp/jwks.go
+++ b/pkg/client/rp/jwks.go
@@ -9,8 +9,8 @@ import (
"gopkg.in/square/go-jose.v2"
- httphelper "github.com/zitadel/oidc/pkg/http"
- "github.com/zitadel/oidc/pkg/oidc"
+ httphelper "github.com/zitadel/oidc/v2/pkg/http"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
)
func NewRemoteKeySet(client *http.Client, jwksURL string, opts ...func(*remoteKeySet)) oidc.KeySet {
diff --git a/pkg/client/rp/mock/verifier.mock.go b/pkg/client/rp/mock/verifier.mock.go
index b20db68..9d1daa1 100644
--- a/pkg/client/rp/mock/verifier.mock.go
+++ b/pkg/client/rp/mock/verifier.mock.go
@@ -10,7 +10,7 @@ import (
"github.com/golang/mock/gomock"
- "github.com/zitadel/oidc/pkg/oidc"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
)
// MockVerifier is a mock of Verifier interface
diff --git a/pkg/client/rp/relying_party.go b/pkg/client/rp/relying_party.go
index 86b65da..d2e3cf7 100644
--- a/pkg/client/rp/relying_party.go
+++ b/pkg/client/rp/relying_party.go
@@ -14,9 +14,9 @@ import (
"golang.org/x/oauth2"
"gopkg.in/square/go-jose.v2"
- "github.com/zitadel/oidc/pkg/client"
- httphelper "github.com/zitadel/oidc/pkg/http"
- "github.com/zitadel/oidc/pkg/oidc"
+ "github.com/zitadel/oidc/v2/pkg/client"
+ httphelper "github.com/zitadel/oidc/v2/pkg/http"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
)
const (
@@ -255,7 +255,7 @@ func WithVerifierOpts(opts ...VerifierOption) Option {
// WithClientKey specifies the path to the key.json to be used for the JWT Profile Client Authentication on the token endpoint
//
-//deprecated: use WithJWTProfile(SignerFromKeyPath(path)) instead
+// deprecated: use WithJWTProfile(SignerFromKeyPath(path)) instead
func WithClientKey(path string) Option {
return WithJWTProfile(SignerFromKeyPath(path))
}
@@ -304,7 +304,7 @@ func SignerFromKeyAndKeyID(key []byte, keyID string) SignerFromKey {
// Discover calls the discovery endpoint of the provided issuer and returns the found endpoints
//
-//deprecated: use client.Discover
+// deprecated: use client.Discover
func Discover(issuer string, httpClient *http.Client) (Endpoints, error) {
wellKnown := strings.TrimSuffix(issuer, "/") + oidc.DiscoveryEndpoint
req, err := http.NewRequest("GET", wellKnown, nil)
@@ -323,7 +323,7 @@ func Discover(issuer string, httpClient *http.Client) (Endpoints, error) {
}
// AuthURL returns the auth request url
-//(wrapping the oauth2 `AuthCodeURL`)
+// (wrapping the oauth2 `AuthCodeURL`)
func AuthURL(state string, rp RelyingParty, opts ...AuthURLOpt) string {
authOpts := make([]oauth2.AuthCodeOption, 0)
for _, opt := range opts {
diff --git a/pkg/client/rp/tockenexchange.go b/pkg/client/rp/tockenexchange.go
index 3950fe1..c1ac88d 100644
--- a/pkg/client/rp/tockenexchange.go
+++ b/pkg/client/rp/tockenexchange.go
@@ -5,7 +5,7 @@ import (
"golang.org/x/oauth2"
- "github.com/zitadel/oidc/pkg/oidc/grants/tokenexchange"
+ "github.com/zitadel/oidc/v2/pkg/oidc/grants/tokenexchange"
)
// TokenExchangeRP extends the `RelyingParty` interface for the *draft* oauth2 `Token Exchange`
diff --git a/pkg/client/rp/verifier.go b/pkg/client/rp/verifier.go
index 6b3b3fd..f3db128 100644
--- a/pkg/client/rp/verifier.go
+++ b/pkg/client/rp/verifier.go
@@ -6,7 +6,7 @@ import (
"gopkg.in/square/go-jose.v2"
- "github.com/zitadel/oidc/pkg/oidc"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
)
type IDTokenVerifier interface {
@@ -20,7 +20,7 @@ type IDTokenVerifier interface {
}
// VerifyTokens implement the Token Response Validation as defined in OIDC specification
-//https://openid.net/specs/openid-connect-core-1_0.html#TokenResponseValidation
+// https://openid.net/specs/openid-connect-core-1_0.html#TokenResponseValidation
func VerifyTokens(ctx context.Context, accessToken, idTokenString string, v IDTokenVerifier) (oidc.IDTokenClaims, error) {
idToken, err := VerifyIDToken(ctx, idTokenString, v)
if err != nil {
@@ -33,7 +33,7 @@ func VerifyTokens(ctx context.Context, accessToken, idTokenString string, v IDTo
}
// VerifyIDToken validates the id token according to
-//https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
+// https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
func VerifyIDToken(ctx context.Context, token string, v IDTokenVerifier) (oidc.IDTokenClaims, error) {
claims := oidc.EmptyIDTokenClaims()
@@ -89,7 +89,7 @@ func VerifyIDToken(ctx context.Context, token string, v IDTokenVerifier) (oidc.I
}
// VerifyAccessToken validates the access token according to
-//https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowTokenValidation
+// https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowTokenValidation
func VerifyAccessToken(accessToken, atHash string, sigAlgorithm jose.SignatureAlgorithm) error {
if atHash == "" {
return nil
diff --git a/pkg/client/rs/resource_server.go b/pkg/client/rs/resource_server.go
index b1bc47e..1d9860f 100644
--- a/pkg/client/rs/resource_server.go
+++ b/pkg/client/rs/resource_server.go
@@ -6,9 +6,9 @@ import (
"net/http"
"time"
- "github.com/zitadel/oidc/pkg/client"
- httphelper "github.com/zitadel/oidc/pkg/http"
- "github.com/zitadel/oidc/pkg/oidc"
+ "github.com/zitadel/oidc/v2/pkg/client"
+ httphelper "github.com/zitadel/oidc/v2/pkg/http"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
)
type ResourceServer interface {
diff --git a/pkg/oidc/code_challenge.go b/pkg/oidc/code_challenge.go
index e1e459c..37c1783 100644
--- a/pkg/oidc/code_challenge.go
+++ b/pkg/oidc/code_challenge.go
@@ -3,7 +3,7 @@ package oidc
import (
"crypto/sha256"
- "github.com/zitadel/oidc/pkg/crypto"
+ "github.com/zitadel/oidc/v2/pkg/crypto"
)
const (
diff --git a/pkg/oidc/token.go b/pkg/oidc/token.go
index 784684d..198049d 100644
--- a/pkg/oidc/token.go
+++ b/pkg/oidc/token.go
@@ -9,8 +9,8 @@ import (
"golang.org/x/oauth2"
"gopkg.in/square/go-jose.v2"
- "github.com/zitadel/oidc/pkg/crypto"
- "github.com/zitadel/oidc/pkg/http"
+ "github.com/zitadel/oidc/v2/pkg/crypto"
+ "github.com/zitadel/oidc/v2/pkg/http"
)
const (
diff --git a/pkg/oidc/token_request.go b/pkg/oidc/token_request.go
index ec11057..2b56535 100644
--- a/pkg/oidc/token_request.go
+++ b/pkg/oidc/token_request.go
@@ -214,8 +214,10 @@ type TokenExchangeRequest struct {
}
type ClientCredentialsRequest struct {
- GrantType GrantType `schema:"grant_type"`
- Scope SpaceDelimitedArray `schema:"scope"`
- ClientID string `schema:"client_id"`
- ClientSecret string `schema:"client_secret"`
+ GrantType GrantType `schema:"grant_type"`
+ Scope SpaceDelimitedArray `schema:"scope"`
+ ClientID string `schema:"client_id"`
+ ClientSecret string `schema:"client_secret"`
+ ClientAssertion string `schema:"client_assertion"`
+ ClientAssertionType string `schema:"client_assertion_type"`
}
diff --git a/pkg/oidc/verifier.go b/pkg/oidc/verifier.go
index cc18c80..1757651 100644
--- a/pkg/oidc/verifier.go
+++ b/pkg/oidc/verifier.go
@@ -12,7 +12,7 @@ import (
"gopkg.in/square/go-jose.v2"
- str "github.com/zitadel/oidc/pkg/strings"
+ str "github.com/zitadel/oidc/v2/pkg/strings"
)
type Claims interface {
diff --git a/pkg/op/auth_request.go b/pkg/op/auth_request.go
index d8c960e..b13f642 100644
--- a/pkg/op/auth_request.go
+++ b/pkg/op/auth_request.go
@@ -11,9 +11,9 @@ import (
"github.com/gorilla/mux"
- httphelper "github.com/zitadel/oidc/pkg/http"
- "github.com/zitadel/oidc/pkg/oidc"
- str "github.com/zitadel/oidc/pkg/strings"
+ httphelper "github.com/zitadel/oidc/v2/pkg/http"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
+ str "github.com/zitadel/oidc/v2/pkg/strings"
)
type AuthRequest interface {
@@ -38,10 +38,8 @@ type Authorizer interface {
Storage() Storage
Decoder() httphelper.Decoder
Encoder() httphelper.Encoder
- Signer() Signer
- IDTokenHintVerifier() IDTokenHintVerifier
+ IDTokenHintVerifier(context.Context) IDTokenHintVerifier
Crypto() Crypto
- Issuer() string
RequestObjectSupported() bool
}
@@ -72,8 +70,9 @@ func Authorize(w http.ResponseWriter, r *http.Request, authorizer Authorizer) {
AuthRequestError(w, r, authReq, err, authorizer.Encoder())
return
}
+ ctx := r.Context()
if authReq.RequestParam != "" && authorizer.RequestObjectSupported() {
- authReq, err = ParseRequestObject(r.Context(), authReq, authorizer.Storage(), authorizer.Issuer())
+ authReq, err = ParseRequestObject(ctx, authReq, authorizer.Storage(), IssuerFromContext(ctx))
if err != nil {
AuthRequestError(w, r, authReq, err, authorizer.Encoder())
return
@@ -91,7 +90,7 @@ func Authorize(w http.ResponseWriter, r *http.Request, authorizer Authorizer) {
if validater, ok := authorizer.(AuthorizeValidator); ok {
validation = validater.ValidateAuthRequest
}
- userID, err := validation(r.Context(), authReq, authorizer.Storage(), authorizer.IDTokenHintVerifier())
+ userID, err := validation(ctx, authReq, authorizer.Storage(), authorizer.IDTokenHintVerifier(ctx))
if err != nil {
AuthRequestError(w, r, authReq, err, authorizer.Encoder())
return
@@ -100,12 +99,12 @@ func Authorize(w http.ResponseWriter, r *http.Request, authorizer Authorizer) {
AuthRequestError(w, r, authReq, oidc.ErrRequestNotSupported(), authorizer.Encoder())
return
}
- req, err := authorizer.Storage().CreateAuthRequest(r.Context(), authReq, userID)
+ req, err := authorizer.Storage().CreateAuthRequest(ctx, authReq, userID)
if err != nil {
AuthRequestError(w, r, authReq, oidc.DefaultToServerError(err, "unable to save auth request"), authorizer.Encoder())
return
}
- client, err := authorizer.Storage().GetClientByClientID(r.Context(), req.GetClientID())
+ client, err := authorizer.Storage().GetClientByClientID(ctx, req.GetClientID())
if err != nil {
AuthRequestError(w, r, req, oidc.DefaultToServerError(err, "unable to retrieve client by id"), authorizer.Encoder())
return
diff --git a/pkg/op/auth_request_test.go b/pkg/op/auth_request_test.go
index dc6f655..7a9701b 100644
--- a/pkg/op/auth_request_test.go
+++ b/pkg/op/auth_request_test.go
@@ -13,10 +13,10 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
- httphelper "github.com/zitadel/oidc/pkg/http"
- "github.com/zitadel/oidc/pkg/oidc"
- "github.com/zitadel/oidc/pkg/op"
- "github.com/zitadel/oidc/pkg/op/mock"
+ httphelper "github.com/zitadel/oidc/v2/pkg/http"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
+ "github.com/zitadel/oidc/v2/pkg/op"
+ "github.com/zitadel/oidc/v2/pkg/op/mock"
)
//
diff --git a/pkg/op/client.go b/pkg/op/client.go
index d9f7ab0..e8a3347 100644
--- a/pkg/op/client.go
+++ b/pkg/op/client.go
@@ -3,7 +3,7 @@ package op
import (
"time"
- "github.com/zitadel/oidc/pkg/oidc"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
)
//go:generate go get github.com/dmarkham/enumer
diff --git a/pkg/op/config.go b/pkg/op/config.go
index 82cbb47..c40fa2d 100644
--- a/pkg/op/config.go
+++ b/pkg/op/config.go
@@ -2,20 +2,24 @@ package op
import (
"errors"
+ "net/http"
"net/url"
- "os"
+ "strings"
"golang.org/x/text/language"
)
-const (
- OidcDevMode = "ZITADEL_OIDC_DEV"
- // deprecated: use OidcDevMode (ZITADEL_OIDC_DEV=true)
- devMode = "CAOS_OIDC_DEV"
+var (
+ ErrInvalidIssuerPath = errors.New("no fragments or query allowed for issuer")
+ ErrInvalidIssuerNoIssuer = errors.New("missing issuer")
+ ErrInvalidIssuerURL = errors.New("invalid url for issuer")
+ ErrInvalidIssuerMissingHost = errors.New("host for issuer missing")
+ ErrInvalidIssuerHTTPS = errors.New("scheme for issuer must be `https`")
)
type Configuration interface {
- Issuer() string
+ IssuerFromRequest(r *http.Request) string
+ Insecure() bool
AuthorizationEndpoint() Endpoint
TokenEndpoint() Endpoint
IntrospectionEndpoint() Endpoint
@@ -42,36 +46,74 @@ type Configuration interface {
SupportedUILocales() []language.Tag
}
-func ValidateIssuer(issuer string) error {
+type IssuerFromRequest func(r *http.Request) string
+
+func IssuerFromHost(path string) func(bool) (IssuerFromRequest, error) {
+ return func(allowInsecure bool) (IssuerFromRequest, error) {
+ issuerPath, err := url.Parse(path)
+ if err != nil {
+ return nil, ErrInvalidIssuerURL
+ }
+ if err := ValidateIssuerPath(issuerPath); err != nil {
+ return nil, err
+ }
+ return func(r *http.Request) string {
+ return dynamicIssuer(r.Host, path, allowInsecure)
+ }, nil
+ }
+}
+
+func StaticIssuer(issuer string) func(bool) (IssuerFromRequest, error) {
+ return func(allowInsecure bool) (IssuerFromRequest, error) {
+ if err := ValidateIssuer(issuer, allowInsecure); err != nil {
+ return nil, err
+ }
+ return func(_ *http.Request) string {
+ return issuer
+ }, nil
+ }
+}
+
+func ValidateIssuer(issuer string, allowInsecure bool) error {
if issuer == "" {
- return errors.New("missing issuer")
+ return ErrInvalidIssuerNoIssuer
}
u, err := url.Parse(issuer)
if err != nil {
- return errors.New("invalid url for issuer")
+ return ErrInvalidIssuerURL
}
if u.Host == "" {
- return errors.New("host for issuer missing")
+ return ErrInvalidIssuerMissingHost
}
if u.Scheme != "https" {
- if !devLocalAllowed(u) {
- return errors.New("scheme for issuer must be `https`")
+ if !devLocalAllowed(u, allowInsecure) {
+ return ErrInvalidIssuerHTTPS
}
}
- if u.Fragment != "" || len(u.Query()) > 0 {
- return errors.New("no fragments or query allowed for issuer")
+ return ValidateIssuerPath(u)
+}
+
+func ValidateIssuerPath(issuer *url.URL) error {
+ if issuer.Fragment != "" || len(issuer.Query()) > 0 {
+ return ErrInvalidIssuerPath
}
return nil
}
-func devLocalAllowed(url *url.URL) bool {
- _, b := os.LookupEnv(OidcDevMode)
- if !b {
- // check the old / current env var as well
- _, b = os.LookupEnv(devMode)
- if !b {
- return b
- }
+func devLocalAllowed(url *url.URL, allowInsecure bool) bool {
+ if !allowInsecure {
+ return false
}
return url.Scheme == "http"
}
+
+func dynamicIssuer(issuer, path string, allowInsecure bool) string {
+ schema := "https"
+ if allowInsecure {
+ schema = "http"
+ }
+ if len(path) > 0 && !strings.HasPrefix(path, "/") {
+ path = "/" + path
+ }
+ return schema + "://" + issuer + path
+}
diff --git a/pkg/op/config_test.go b/pkg/op/config_test.go
index 9ff75f1..cfe4e61 100644
--- a/pkg/op/config_test.go
+++ b/pkg/op/config_test.go
@@ -1,13 +1,17 @@
package op
import (
- "os"
+ "net/http/httptest"
+ "net/url"
"testing"
+
+ "github.com/stretchr/testify/assert"
)
func TestValidateIssuer(t *testing.T) {
type args struct {
- issuer string
+ issuer string
+ allowInsecure bool
}
tests := []struct {
name string
@@ -16,65 +20,97 @@ func TestValidateIssuer(t *testing.T) {
}{
{
"missing issuer fails",
- args{""},
+ args{
+ issuer: "",
+ },
true,
},
{
"invalid url for issuer fails",
- args{":issuer"},
- true,
- },
- {
- "invalid url for issuer fails",
- args{":issuer"},
+ args{
+ issuer: ":issuer",
+ },
true,
},
{
"host for issuer missing fails",
- args{"https:///issuer"},
- true,
- },
- {
- "host for not https fails",
- args{"http://issuer.com"},
+ args{
+ issuer: "https:///issuer",
+ },
true,
},
{
"host with fragment fails",
- args{"https://issuer.com/#issuer"},
+ args{
+ issuer: "https://issuer.com/#issuer",
+ },
true,
},
{
"host with query fails",
- args{"https://issuer.com?issuer=me"},
+ args{
+ issuer: "https://issuer.com?issuer=me",
+ },
+ true,
+ },
+ {
+ "host with http fails",
+ args{
+ issuer: "http://issuer.com",
+ },
true,
},
{
"host with https ok",
- args{"https://issuer.com"},
+ args{
+ issuer: "https://issuer.com",
+ },
false,
},
{
- "localhost with http fails",
- args{"http://localhost:9999"},
+ "custom scheme fails",
+ args{
+ issuer: "custom://localhost:9999",
+ },
+ true,
+ },
+ {
+ "http with allowInsecure ok",
+ args{
+ issuer: "http://localhost:9999",
+ allowInsecure: true,
+ },
+ false,
+ },
+ {
+ "https with allowInsecure ok",
+ args{
+ issuer: "https://localhost:9999",
+ allowInsecure: true,
+ },
+ false,
+ },
+ {
+ "custom scheme with allowInsecure fails",
+ args{
+ issuer: "custom://localhost:9999",
+ allowInsecure: true,
+ },
true,
},
}
- // ensure env is not set
- //nolint:errcheck
- os.Unsetenv(OidcDevMode)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- if err := ValidateIssuer(tt.args.issuer); (err != nil) != tt.wantErr {
+ if err := ValidateIssuer(tt.args.issuer, tt.args.allowInsecure); (err != nil) != tt.wantErr {
t.Errorf("ValidateIssuer() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
-func TestValidateIssuerDevLocalAllowed(t *testing.T) {
+func TestValidateIssuerPath(t *testing.T) {
type args struct {
- issuer string
+ issuerPath *url.URL
}
tests := []struct {
name string
@@ -82,17 +118,217 @@ func TestValidateIssuerDevLocalAllowed(t *testing.T) {
wantErr bool
}{
{
- "localhost with http with dev ok",
- args{"http://localhost:9999"},
+ "empty ok",
+ args{func() *url.URL {
+ u, _ := url.Parse("")
+ return u
+ }()},
false,
},
+ {
+ "custom ok",
+ args{func() *url.URL {
+ u, _ := url.Parse("/custom")
+ return u
+ }()},
+ false,
+ },
+ {
+ "fragment fails",
+ args{func() *url.URL {
+ u, _ := url.Parse("#fragment")
+ return u
+ }()},
+ true,
+ },
+ {
+ "query fails",
+ args{func() *url.URL {
+ u, _ := url.Parse("?query=value")
+ return u
+ }()},
+ true,
+ },
}
- //nolint:errcheck
- os.Setenv(OidcDevMode, "true")
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- if err := ValidateIssuer(tt.args.issuer); (err != nil) != tt.wantErr {
- t.Errorf("ValidateIssuer() error = %v, wantErr %v", err, tt.wantErr)
+ if err := ValidateIssuerPath(tt.args.issuerPath); (err != nil) != tt.wantErr {
+ t.Errorf("ValidateIssuerPath() error = %v, wantErr %v", err, tt.wantErr)
+ }
+ })
+ }
+}
+
+func TestIssuerFromHost(t *testing.T) {
+ type args struct {
+ path string
+ allowInsecure bool
+ target string
+ }
+ type res struct {
+ issuer string
+ err error
+ }
+ tests := []struct {
+ name string
+ args args
+ res res
+ }{
+ {
+ "invalid issuer path",
+ args{
+ path: "/#fragment",
+ allowInsecure: false,
+ },
+ res{
+ issuer: "",
+ err: ErrInvalidIssuerPath,
+ },
+ },
+ {
+ "empty path secure",
+ args{
+ path: "",
+ allowInsecure: false,
+ target: "https://issuer.com",
+ },
+ res{
+ issuer: "https://issuer.com",
+ err: nil,
+ },
+ },
+ {
+ "custom path secure",
+ args{
+ path: "/custom/",
+ allowInsecure: false,
+ target: "https://issuer.com",
+ },
+ res{
+ issuer: "https://issuer.com/custom/",
+ err: nil,
+ },
+ },
+ {
+ "custom path no leading slash",
+ args{
+ path: "custom/",
+ allowInsecure: false,
+ target: "https://issuer.com",
+ },
+ res{
+ issuer: "https://issuer.com/custom/",
+ err: nil,
+ },
+ },
+ {
+ "empty path unsecure",
+ args{
+ path: "",
+ allowInsecure: true,
+ target: "http://issuer.com",
+ },
+ res{
+ issuer: "http://issuer.com",
+ err: nil,
+ },
+ },
+ {
+ "custom path unsecure",
+ args{
+ path: "/custom/",
+ allowInsecure: true,
+ target: "http://issuer.com",
+ },
+ res{
+ issuer: "http://issuer.com/custom/",
+ err: nil,
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ issuer, err := IssuerFromHost(tt.args.path)(tt.args.allowInsecure)
+ if tt.res.err == nil {
+ assert.NoError(t, err)
+ req := httptest.NewRequest("", tt.args.target, nil)
+ assert.Equal(t, tt.res.issuer, issuer(req))
+ }
+ if tt.res.err != nil {
+ assert.ErrorIs(t, err, tt.res.err)
+ }
+ })
+ }
+}
+
+func TestStaticIssuer(t *testing.T) {
+ type args struct {
+ issuer string
+ allowInsecure bool
+ }
+ type res struct {
+ issuer string
+ err error
+ }
+ tests := []struct {
+ name string
+ args args
+ res res
+ }{
+ {
+ "invalid issuer",
+ args{
+ issuer: "",
+ allowInsecure: false,
+ },
+ res{
+ issuer: "",
+ err: ErrInvalidIssuerNoIssuer,
+ },
+ },
+ {
+ "empty path secure",
+ args{
+ issuer: "https://issuer.com",
+ allowInsecure: false,
+ },
+ res{
+ issuer: "https://issuer.com",
+ err: nil,
+ },
+ },
+ {
+ "custom path secure",
+ args{
+ issuer: "https://issuer.com/custom/",
+ allowInsecure: false,
+ },
+ res{
+ issuer: "https://issuer.com/custom/",
+ err: nil,
+ },
+ },
+ {
+ "unsecure",
+ args{
+ issuer: "http://issuer.com",
+ allowInsecure: true,
+ },
+ res{
+ issuer: "http://issuer.com",
+ err: nil,
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ issuer, err := StaticIssuer(tt.args.issuer)(tt.args.allowInsecure)
+ if tt.res.err == nil {
+ assert.NoError(t, err)
+ assert.Equal(t, tt.res.issuer, issuer(nil))
+ }
+ if tt.res.err != nil {
+ assert.ErrorIs(t, err, tt.res.err)
}
})
}
diff --git a/pkg/op/context.go b/pkg/op/context.go
new file mode 100644
index 0000000..4406273
--- /dev/null
+++ b/pkg/op/context.go
@@ -0,0 +1,49 @@
+package op
+
+import (
+ "context"
+ "net/http"
+)
+
+type key int
+
+var (
+ issuer key = 0
+)
+
+type IssuerInterceptor struct {
+ issuerFromRequest IssuerFromRequest
+}
+
+//NewIssuerInterceptor will set the issuer into the context
+//by the provided IssuerFromRequest (e.g. returned from StaticIssuer or IssuerFromHost)
+func NewIssuerInterceptor(issuerFromRequest IssuerFromRequest) *IssuerInterceptor {
+ return &IssuerInterceptor{
+ issuerFromRequest: issuerFromRequest,
+ }
+}
+
+func (i *IssuerInterceptor) Handler(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ i.setIssuerCtx(w, r, next)
+ })
+}
+
+func (i *IssuerInterceptor) HandlerFunc(next http.HandlerFunc) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ i.setIssuerCtx(w, r, next)
+ }
+}
+
+//IssuerFromContext reads the issuer from the context (set by an IssuerInterceptor)
+//it will return an empty string if not found
+func IssuerFromContext(ctx context.Context) string {
+ ctxIssuer, _ := ctx.Value(issuer).(string)
+ return ctxIssuer
+}
+
+func (i *IssuerInterceptor) setIssuerCtx(w http.ResponseWriter, r *http.Request, next http.Handler) {
+ ctx := context.WithValue(r.Context(), issuer, i.issuerFromRequest(r))
+ r = r.WithContext(ctx)
+ next.ServeHTTP(w, r)
+}
diff --git a/pkg/op/context_test.go b/pkg/op/context_test.go
new file mode 100644
index 0000000..e6bfcec
--- /dev/null
+++ b/pkg/op/context_test.go
@@ -0,0 +1,76 @@
+package op
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestIssuerInterceptor(t *testing.T) {
+ type fields struct {
+ issuerFromRequest IssuerFromRequest
+ }
+ type args struct {
+ r *http.Request
+ next http.Handler
+ }
+ type res struct {
+ issuer string
+ }
+ tests := []struct {
+ name string
+ fields fields
+ args args
+ res res
+ }{
+ {
+ "empty",
+ fields{
+ func(r *http.Request) string {
+ return ""
+ },
+ },
+ args{},
+ res{
+ issuer: "",
+ },
+ },
+ {
+ "static",
+ fields{
+ func(r *http.Request) string {
+ return "static"
+ },
+ },
+ args{},
+ res{
+ issuer: "static",
+ },
+ },
+ {
+ "host",
+ fields{
+ func(r *http.Request) string {
+ return r.Host
+ },
+ },
+ args{},
+ res{
+ issuer: "issuer.com",
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ i := NewIssuerInterceptor(tt.fields.issuerFromRequest)
+ next := http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
+ assert.Equal(t, tt.res.issuer, IssuerFromContext(r.Context()))
+ })
+ req := httptest.NewRequest("", "https://issuer.com", nil)
+ i.Handler(next).ServeHTTP(nil, req)
+ i.HandlerFunc(next).ServeHTTP(nil, req)
+ })
+ }
+}
diff --git a/pkg/op/crypto.go b/pkg/op/crypto.go
index f14b1de..6786022 100644
--- a/pkg/op/crypto.go
+++ b/pkg/op/crypto.go
@@ -1,7 +1,7 @@
package op
import (
- "github.com/zitadel/oidc/pkg/crypto"
+ "github.com/zitadel/oidc/v2/pkg/crypto"
)
type Crypto interface {
diff --git a/pkg/op/discovery.go b/pkg/op/discovery.go
index 100bfc8..9a25afc 100644
--- a/pkg/op/discovery.go
+++ b/pkg/op/discovery.go
@@ -1,49 +1,17 @@
package op
import (
+ "context"
"net/http"
- httphelper "github.com/zitadel/oidc/pkg/http"
- "github.com/zitadel/oidc/pkg/oidc"
+ "gopkg.in/square/go-jose.v2"
+
+ httphelper "github.com/zitadel/oidc/v2/pkg/http"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
)
-func discoveryHandler(c Configuration, s Signer) func(http.ResponseWriter, *http.Request) {
- return func(w http.ResponseWriter, r *http.Request) {
- Discover(w, CreateDiscoveryConfig(c, s))
- }
-}
-
-func Discover(w http.ResponseWriter, config *oidc.DiscoveryConfiguration) {
- httphelper.MarshalJSON(w, config)
-}
-
-func CreateDiscoveryConfig(c Configuration, s Signer) *oidc.DiscoveryConfiguration {
- return &oidc.DiscoveryConfiguration{
- Issuer: c.Issuer(),
- AuthorizationEndpoint: c.AuthorizationEndpoint().Absolute(c.Issuer()),
- TokenEndpoint: c.TokenEndpoint().Absolute(c.Issuer()),
- IntrospectionEndpoint: c.IntrospectionEndpoint().Absolute(c.Issuer()),
- UserinfoEndpoint: c.UserinfoEndpoint().Absolute(c.Issuer()),
- RevocationEndpoint: c.RevocationEndpoint().Absolute(c.Issuer()),
- EndSessionEndpoint: c.EndSessionEndpoint().Absolute(c.Issuer()),
- JwksURI: c.KeysEndpoint().Absolute(c.Issuer()),
- ScopesSupported: Scopes(c),
- ResponseTypesSupported: ResponseTypes(c),
- GrantTypesSupported: GrantTypes(c),
- SubjectTypesSupported: SubjectTypes(c),
- IDTokenSigningAlgValuesSupported: SigAlgorithms(s),
- RequestObjectSigningAlgValuesSupported: RequestObjectSigAlgorithms(c),
- TokenEndpointAuthMethodsSupported: AuthMethodsTokenEndpoint(c),
- TokenEndpointAuthSigningAlgValuesSupported: TokenSigAlgorithms(c),
- IntrospectionEndpointAuthSigningAlgValuesSupported: IntrospectionSigAlgorithms(c),
- IntrospectionEndpointAuthMethodsSupported: AuthMethodsIntrospectionEndpoint(c),
- RevocationEndpointAuthSigningAlgValuesSupported: RevocationSigAlgorithms(c),
- RevocationEndpointAuthMethodsSupported: AuthMethodsRevocationEndpoint(c),
- ClaimsSupported: SupportedClaims(c),
- CodeChallengeMethodsSupported: CodeChallengeMethods(c),
- UILocalesSupported: c.SupportedUILocales(),
- RequestParameterSupported: c.RequestObjectSupported(),
- }
+type DiscoverStorage interface {
+ SignatureAlgorithms(context.Context) ([]jose.SignatureAlgorithm, error)
}
var DefaultSupportedScopes = []string{
@@ -55,6 +23,46 @@ var DefaultSupportedScopes = []string{
oidc.ScopeOfflineAccess,
}
+func discoveryHandler(c Configuration, s DiscoverStorage) func(http.ResponseWriter, *http.Request) {
+ return func(w http.ResponseWriter, r *http.Request) {
+ Discover(w, CreateDiscoveryConfig(r, c, s))
+ }
+}
+
+func Discover(w http.ResponseWriter, config *oidc.DiscoveryConfiguration) {
+ httphelper.MarshalJSON(w, config)
+}
+
+func CreateDiscoveryConfig(r *http.Request, config Configuration, storage DiscoverStorage) *oidc.DiscoveryConfiguration {
+ issuer := config.IssuerFromRequest(r)
+ return &oidc.DiscoveryConfiguration{
+ Issuer: issuer,
+ AuthorizationEndpoint: config.AuthorizationEndpoint().Absolute(issuer),
+ TokenEndpoint: config.TokenEndpoint().Absolute(issuer),
+ IntrospectionEndpoint: config.IntrospectionEndpoint().Absolute(issuer),
+ UserinfoEndpoint: config.UserinfoEndpoint().Absolute(issuer),
+ RevocationEndpoint: config.RevocationEndpoint().Absolute(issuer),
+ EndSessionEndpoint: config.EndSessionEndpoint().Absolute(issuer),
+ JwksURI: config.KeysEndpoint().Absolute(issuer),
+ ScopesSupported: Scopes(config),
+ ResponseTypesSupported: ResponseTypes(config),
+ GrantTypesSupported: GrantTypes(config),
+ SubjectTypesSupported: SubjectTypes(config),
+ IDTokenSigningAlgValuesSupported: SigAlgorithms(r.Context(), storage),
+ RequestObjectSigningAlgValuesSupported: RequestObjectSigAlgorithms(config),
+ TokenEndpointAuthMethodsSupported: AuthMethodsTokenEndpoint(config),
+ TokenEndpointAuthSigningAlgValuesSupported: TokenSigAlgorithms(config),
+ IntrospectionEndpointAuthSigningAlgValuesSupported: IntrospectionSigAlgorithms(config),
+ IntrospectionEndpointAuthMethodsSupported: AuthMethodsIntrospectionEndpoint(config),
+ RevocationEndpointAuthSigningAlgValuesSupported: RevocationSigAlgorithms(config),
+ RevocationEndpointAuthMethodsSupported: AuthMethodsRevocationEndpoint(config),
+ ClaimsSupported: SupportedClaims(config),
+ CodeChallengeMethodsSupported: CodeChallengeMethods(config),
+ UILocalesSupported: config.SupportedUILocales(),
+ RequestParameterSupported: config.RequestObjectSupported(),
+ }
+}
+
func Scopes(c Configuration) []string {
return DefaultSupportedScopes // TODO: config
}
@@ -87,6 +95,88 @@ func GrantTypes(c Configuration) []oidc.GrantType {
return grantTypes
}
+func SubjectTypes(c Configuration) []string {
+ return []string{"public"} //TODO: config
+}
+
+func SigAlgorithms(ctx context.Context, storage DiscoverStorage) []string {
+ algorithms, err := storage.SignatureAlgorithms(ctx)
+ if err != nil {
+ return nil
+ }
+ algs := make([]string, len(algorithms))
+ for i, algorithm := range algorithms {
+ algs[i] = string(algorithm)
+ }
+ return algs
+}
+
+func RequestObjectSigAlgorithms(c Configuration) []string {
+ if !c.RequestObjectSupported() {
+ return nil
+ }
+ return c.RequestObjectSigningAlgorithmsSupported()
+}
+
+func AuthMethodsTokenEndpoint(c Configuration) []oidc.AuthMethod {
+ authMethods := []oidc.AuthMethod{
+ oidc.AuthMethodNone,
+ oidc.AuthMethodBasic,
+ }
+ if c.AuthMethodPostSupported() {
+ authMethods = append(authMethods, oidc.AuthMethodPost)
+ }
+ if c.AuthMethodPrivateKeyJWTSupported() {
+ authMethods = append(authMethods, oidc.AuthMethodPrivateKeyJWT)
+ }
+ return authMethods
+}
+
+func TokenSigAlgorithms(c Configuration) []string {
+ if !c.AuthMethodPrivateKeyJWTSupported() {
+ return nil
+ }
+ return c.TokenEndpointSigningAlgorithmsSupported()
+}
+
+func IntrospectionSigAlgorithms(c Configuration) []string {
+ if !c.IntrospectionAuthMethodPrivateKeyJWTSupported() {
+ return nil
+ }
+ return c.IntrospectionEndpointSigningAlgorithmsSupported()
+}
+
+func AuthMethodsIntrospectionEndpoint(c Configuration) []oidc.AuthMethod {
+ authMethods := []oidc.AuthMethod{
+ oidc.AuthMethodBasic,
+ }
+ if c.AuthMethodPrivateKeyJWTSupported() {
+ authMethods = append(authMethods, oidc.AuthMethodPrivateKeyJWT)
+ }
+ return authMethods
+}
+
+func RevocationSigAlgorithms(c Configuration) []string {
+ if !c.RevocationAuthMethodPrivateKeyJWTSupported() {
+ return nil
+ }
+ return c.RevocationEndpointSigningAlgorithmsSupported()
+}
+
+func AuthMethodsRevocationEndpoint(c Configuration) []oidc.AuthMethod {
+ authMethods := []oidc.AuthMethod{
+ oidc.AuthMethodNone,
+ oidc.AuthMethodBasic,
+ }
+ if c.AuthMethodPostSupported() {
+ authMethods = append(authMethods, oidc.AuthMethodPost)
+ }
+ if c.AuthMethodPrivateKeyJWTSupported() {
+ authMethods = append(authMethods, oidc.AuthMethodPrivateKeyJWT)
+ }
+ return authMethods
+}
+
func SupportedClaims(c Configuration) []string {
return []string{ // TODO: config
"sub",
@@ -116,59 +206,6 @@ func SupportedClaims(c Configuration) []string {
}
}
-func SigAlgorithms(s Signer) []string {
- return []string{string(s.SignatureAlgorithm())}
-}
-
-func SubjectTypes(c Configuration) []string {
- return []string{"public"} // TODO: config
-}
-
-func AuthMethodsTokenEndpoint(c Configuration) []oidc.AuthMethod {
- authMethods := []oidc.AuthMethod{
- oidc.AuthMethodNone,
- oidc.AuthMethodBasic,
- }
- if c.AuthMethodPostSupported() {
- authMethods = append(authMethods, oidc.AuthMethodPost)
- }
- if c.AuthMethodPrivateKeyJWTSupported() {
- authMethods = append(authMethods, oidc.AuthMethodPrivateKeyJWT)
- }
- return authMethods
-}
-
-func TokenSigAlgorithms(c Configuration) []string {
- if !c.AuthMethodPrivateKeyJWTSupported() {
- return nil
- }
- return c.TokenEndpointSigningAlgorithmsSupported()
-}
-
-func AuthMethodsIntrospectionEndpoint(c Configuration) []oidc.AuthMethod {
- authMethods := []oidc.AuthMethod{
- oidc.AuthMethodBasic,
- }
- if c.AuthMethodPrivateKeyJWTSupported() {
- authMethods = append(authMethods, oidc.AuthMethodPrivateKeyJWT)
- }
- return authMethods
-}
-
-func AuthMethodsRevocationEndpoint(c Configuration) []oidc.AuthMethod {
- authMethods := []oidc.AuthMethod{
- oidc.AuthMethodNone,
- oidc.AuthMethodBasic,
- }
- if c.AuthMethodPostSupported() {
- authMethods = append(authMethods, oidc.AuthMethodPost)
- }
- if c.AuthMethodPrivateKeyJWTSupported() {
- authMethods = append(authMethods, oidc.AuthMethodPrivateKeyJWT)
- }
- return authMethods
-}
-
func CodeChallengeMethods(c Configuration) []oidc.CodeChallengeMethod {
codeMethods := make([]oidc.CodeChallengeMethod, 0, 1)
if c.CodeMethodS256Supported() {
@@ -176,24 +213,3 @@ func CodeChallengeMethods(c Configuration) []oidc.CodeChallengeMethod {
}
return codeMethods
}
-
-func IntrospectionSigAlgorithms(c Configuration) []string {
- if !c.IntrospectionAuthMethodPrivateKeyJWTSupported() {
- return nil
- }
- return c.IntrospectionEndpointSigningAlgorithmsSupported()
-}
-
-func RevocationSigAlgorithms(c Configuration) []string {
- if !c.RevocationAuthMethodPrivateKeyJWTSupported() {
- return nil
- }
- return c.RevocationEndpointSigningAlgorithmsSupported()
-}
-
-func RequestObjectSigAlgorithms(c Configuration) []string {
- if !c.RequestObjectSupported() {
- return nil
- }
- return c.RequestObjectSigningAlgorithmsSupported()
-}
diff --git a/pkg/op/discovery_test.go b/pkg/op/discovery_test.go
index 1d74f75..e1b07dd 100644
--- a/pkg/op/discovery_test.go
+++ b/pkg/op/discovery_test.go
@@ -1,18 +1,19 @@
package op_test
import (
+ "context"
"net/http"
"net/http/httptest"
- "reflect"
"testing"
"github.com/golang/mock/gomock"
+ "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/square/go-jose.v2"
- "github.com/zitadel/oidc/pkg/oidc"
- "github.com/zitadel/oidc/pkg/op"
- "github.com/zitadel/oidc/pkg/op/mock"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
+ "github.com/zitadel/oidc/v2/pkg/op"
+ "github.com/zitadel/oidc/v2/pkg/op/mock"
)
func TestDiscover(t *testing.T) {
@@ -47,8 +48,9 @@ func TestDiscover(t *testing.T) {
func TestCreateDiscoveryConfig(t *testing.T) {
type args struct {
- c op.Configuration
- s op.Signer
+ request *http.Request
+ c op.Configuration
+ s op.DiscoverStorage
}
tests := []struct {
name string
@@ -59,9 +61,8 @@ func TestCreateDiscoveryConfig(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- if got := op.CreateDiscoveryConfig(tt.args.c, tt.args.s); !reflect.DeepEqual(got, tt.want) {
- t.Errorf("CreateDiscoveryConfig() = %v, want %v", got, tt.want)
- }
+ got := op.CreateDiscoveryConfig(tt.args.request, tt.args.c, tt.args.s)
+ assert.Equal(t, tt.want, got)
})
}
}
@@ -83,9 +84,8 @@ func Test_scopes(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- if got := op.Scopes(tt.args.c); !reflect.DeepEqual(got, tt.want) {
- t.Errorf("scopes() = %v, want %v", got, tt.want)
- }
+ got := op.Scopes(tt.args.c)
+ assert.Equal(t, tt.want, got)
})
}
}
@@ -99,13 +99,16 @@ func Test_ResponseTypes(t *testing.T) {
args args
want []string
}{
- // TODO: Add test cases.
+ {
+ "code and implicit flow",
+ args{},
+ []string{"code", "id_token", "id_token token"},
+ },
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- if got := op.ResponseTypes(tt.args.c); !reflect.DeepEqual(got, tt.want) {
- t.Errorf("responseTypes() = %v, want %v", got, tt.want)
- }
+ got := op.ResponseTypes(tt.args.c)
+ assert.Equal(t, tt.want, got)
})
}
}
@@ -117,63 +120,51 @@ func Test_GrantTypes(t *testing.T) {
tests := []struct {
name string
args args
- want []string
- }{
- // TODO: Add test cases.
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- if got := op.GrantTypes(tt.args.c); !reflect.DeepEqual(got, tt.want) {
- t.Errorf("grantTypes() = %v, want %v", got, tt.want)
- }
- })
- }
-}
-
-func TestSupportedClaims(t *testing.T) {
- type args struct {
- c op.Configuration
- }
- tests := []struct {
- name string
- args args
- want []string
- }{
- // TODO: Add test cases.
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- if got := op.SupportedClaims(tt.args.c); !reflect.DeepEqual(got, tt.want) {
- t.Errorf("SupportedClaims() = %v, want %v", got, tt.want)
- }
- })
- }
-}
-
-func Test_SigAlgorithms(t *testing.T) {
- m := mock.NewMockSigner(gomock.NewController(t))
- type args struct {
- s op.Signer
- }
- tests := []struct {
- name string
- args args
- want []string
+ want []oidc.GrantType
}{
{
- "",
- args{func() op.Signer {
- m.EXPECT().SignatureAlgorithm().Return(jose.RS256)
- return m
- }()},
- []string{"RS256"},
+ "code and implicit flow",
+ args{
+ func() op.Configuration {
+ c := mock.NewMockConfiguration(gomock.NewController(t))
+ c.EXPECT().GrantTypeRefreshTokenSupported().Return(false)
+ c.EXPECT().GrantTypeTokenExchangeSupported().Return(false)
+ c.EXPECT().GrantTypeJWTAuthorizationSupported().Return(false)
+ c.EXPECT().GrantTypeClientCredentialsSupported().Return(false)
+ return c
+ }(),
+ },
+ []oidc.GrantType{
+ oidc.GrantTypeCode,
+ oidc.GrantTypeImplicit,
+ },
+ },
+ {
+ "code, implicit flow, refresh token, token exchange, jwt profile, client_credentials",
+ args{
+ func() op.Configuration {
+ c := mock.NewMockConfiguration(gomock.NewController(t))
+ c.EXPECT().GrantTypeRefreshTokenSupported().Return(true)
+ c.EXPECT().GrantTypeTokenExchangeSupported().Return(true)
+ c.EXPECT().GrantTypeJWTAuthorizationSupported().Return(true)
+ c.EXPECT().GrantTypeClientCredentialsSupported().Return(true)
+ return c
+ }(),
+ },
+ []oidc.GrantType{
+ oidc.GrantTypeCode,
+ oidc.GrantTypeImplicit,
+ oidc.GrantTypeRefreshToken,
+ oidc.GrantTypeClientCredentials,
+ oidc.GrantTypeTokenExchange,
+ oidc.GrantTypeBearer,
+ },
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- if got := op.SigAlgorithms(tt.args.s); !reflect.DeepEqual(got, tt.want) {
- t.Errorf("sigAlgorithms() = %v, want %v", got, tt.want)
- }
+ got := op.GrantTypes(tt.args.c)
+ assert.Equal(t, tt.want, got)
})
}
}
@@ -195,9 +186,80 @@ func Test_SubjectTypes(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- if got := op.SubjectTypes(tt.args.c); !reflect.DeepEqual(got, tt.want) {
- t.Errorf("subjectTypes() = %v, want %v", got, tt.want)
- }
+ got := op.SubjectTypes(tt.args.c)
+ assert.Equal(t, tt.want, got)
+ })
+ }
+}
+
+func Test_SigAlgorithms(t *testing.T) {
+ m := mock.NewMockDiscoverStorage(gomock.NewController(t))
+ type args struct {
+ s op.DiscoverStorage
+ }
+ tests := []struct {
+ name string
+ args args
+ want []string
+ }{
+ {
+ "",
+ args{func() op.DiscoverStorage {
+ m.EXPECT().SignatureAlgorithms(gomock.Any()).Return([]jose.SignatureAlgorithm{jose.RS256}, nil)
+ return m
+ }()},
+ []string{"RS256"},
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got := op.SigAlgorithms(context.Background(), tt.args.s)
+ assert.Equal(t, tt.want, got)
+ })
+ }
+}
+
+func Test_RequestObjectSigAlgorithms(t *testing.T) {
+ m := mock.NewMockConfiguration(gomock.NewController(t))
+ type args struct {
+ c op.Configuration
+ }
+ tests := []struct {
+ name string
+ args args
+ want []string
+ }{
+ {
+ "not supported, empty",
+ args{func() op.Configuration {
+ m.EXPECT().RequestObjectSupported().Return(false)
+ return m
+ }()},
+ nil,
+ },
+ {
+ "supported, empty",
+ args{func() op.Configuration {
+ m.EXPECT().RequestObjectSupported().Return(true)
+ m.EXPECT().RequestObjectSigningAlgorithmsSupported().Return(nil)
+ return m
+ }()},
+ nil,
+ },
+ {
+ "supported, list",
+ args{func() op.Configuration {
+ m.EXPECT().RequestObjectSupported().Return(true)
+ m.EXPECT().RequestObjectSigningAlgorithmsSupported().Return([]string{"RS256"})
+ return m
+ }()},
+ []string{"RS256"},
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got := op.RequestObjectSigAlgorithms(tt.args.c)
+ assert.Equal(t, tt.want, got)
})
}
}
@@ -244,9 +306,311 @@ func Test_AuthMethodsTokenEndpoint(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- if got := op.AuthMethodsTokenEndpoint(tt.args.c); !reflect.DeepEqual(got, tt.want) {
- t.Errorf("authMethods() = %v, want %v", got, tt.want)
- }
+ got := op.AuthMethodsTokenEndpoint(tt.args.c)
+ assert.Equal(t, tt.want, got)
+ })
+ }
+}
+
+func Test_TokenSigAlgorithms(t *testing.T) {
+ m := mock.NewMockConfiguration(gomock.NewController(t))
+ type args struct {
+ c op.Configuration
+ }
+ tests := []struct {
+ name string
+ args args
+ want []string
+ }{
+ {
+ "not supported, empty",
+ args{func() op.Configuration {
+ m.EXPECT().AuthMethodPrivateKeyJWTSupported().Return(false)
+ return m
+ }()},
+ nil,
+ },
+ {
+ "supported, empty",
+ args{func() op.Configuration {
+ m.EXPECT().AuthMethodPrivateKeyJWTSupported().Return(true)
+ m.EXPECT().TokenEndpointSigningAlgorithmsSupported().Return(nil)
+ return m
+ }()},
+ nil,
+ },
+ {
+ "supported, list",
+ args{func() op.Configuration {
+ m.EXPECT().AuthMethodPrivateKeyJWTSupported().Return(true)
+ m.EXPECT().TokenEndpointSigningAlgorithmsSupported().Return([]string{"RS256"})
+ return m
+ }()},
+ []string{"RS256"},
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got := op.TokenSigAlgorithms(tt.args.c)
+ assert.Equal(t, tt.want, got)
+ })
+ }
+}
+
+func Test_IntrospectionSigAlgorithms(t *testing.T) {
+ m := mock.NewMockConfiguration(gomock.NewController(t))
+ type args struct {
+ c op.Configuration
+ }
+ tests := []struct {
+ name string
+ args args
+ want []string
+ }{
+ {
+ "not supported, empty",
+ args{func() op.Configuration {
+ m.EXPECT().IntrospectionAuthMethodPrivateKeyJWTSupported().Return(false)
+ return m
+ }()},
+ nil,
+ },
+ {
+ "supported, empty",
+ args{func() op.Configuration {
+ m.EXPECT().IntrospectionAuthMethodPrivateKeyJWTSupported().Return(true)
+ m.EXPECT().IntrospectionEndpointSigningAlgorithmsSupported().Return(nil)
+ return m
+ }()},
+ nil,
+ },
+ {
+ "supported, list",
+ args{func() op.Configuration {
+ m.EXPECT().IntrospectionAuthMethodPrivateKeyJWTSupported().Return(true)
+ m.EXPECT().IntrospectionEndpointSigningAlgorithmsSupported().Return([]string{"RS256"})
+ return m
+ }()},
+ []string{"RS256"},
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got := op.IntrospectionSigAlgorithms(tt.args.c)
+ assert.Equal(t, tt.want, got)
+ })
+ }
+}
+
+func Test_AuthMethodsIntrospectionEndpoint(t *testing.T) {
+ type args struct {
+ c op.Configuration
+ }
+ tests := []struct {
+ name string
+ args args
+ want []oidc.AuthMethod
+ }{
+ {
+ "basic only",
+ args{func() op.Configuration {
+ m := mock.NewMockConfiguration(gomock.NewController(t))
+ m.EXPECT().AuthMethodPrivateKeyJWTSupported().Return(false)
+ return m
+ }()},
+ []oidc.AuthMethod{oidc.AuthMethodBasic},
+ },
+ {
+ "basic and private_key_jwt",
+ args{func() op.Configuration {
+ m := mock.NewMockConfiguration(gomock.NewController(t))
+ m.EXPECT().AuthMethodPrivateKeyJWTSupported().Return(true)
+ return m
+ }()},
+ []oidc.AuthMethod{oidc.AuthMethodBasic, oidc.AuthMethodPrivateKeyJWT},
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got := op.AuthMethodsIntrospectionEndpoint(tt.args.c)
+ assert.Equal(t, tt.want, got)
+ })
+ }
+}
+
+func Test_RevocationSigAlgorithms(t *testing.T) {
+ m := mock.NewMockConfiguration(gomock.NewController(t))
+ type args struct {
+ c op.Configuration
+ }
+ tests := []struct {
+ name string
+ args args
+ want []string
+ }{
+ {
+ "not supported, empty",
+ args{func() op.Configuration {
+ m.EXPECT().RevocationAuthMethodPrivateKeyJWTSupported().Return(false)
+ return m
+ }()},
+ nil,
+ },
+ {
+ "supported, empty",
+ args{func() op.Configuration {
+ m.EXPECT().RevocationAuthMethodPrivateKeyJWTSupported().Return(true)
+ m.EXPECT().RevocationEndpointSigningAlgorithmsSupported().Return(nil)
+ return m
+ }()},
+ nil,
+ },
+ {
+ "supported, list",
+ args{func() op.Configuration {
+ m.EXPECT().RevocationAuthMethodPrivateKeyJWTSupported().Return(true)
+ m.EXPECT().RevocationEndpointSigningAlgorithmsSupported().Return([]string{"RS256"})
+ return m
+ }()},
+ []string{"RS256"},
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got := op.RevocationSigAlgorithms(tt.args.c)
+ assert.Equal(t, tt.want, got)
+ })
+ }
+}
+
+func Test_AuthMethodsRevocationEndpoint(t *testing.T) {
+ type args struct {
+ c op.Configuration
+ }
+ tests := []struct {
+ name string
+ args args
+ want []oidc.AuthMethod
+ }{
+ {
+ "none and basic",
+ args{func() op.Configuration {
+ m := mock.NewMockConfiguration(gomock.NewController(t))
+ m.EXPECT().AuthMethodPostSupported().Return(false)
+ m.EXPECT().AuthMethodPrivateKeyJWTSupported().Return(false)
+ return m
+ }()},
+ []oidc.AuthMethod{oidc.AuthMethodNone, oidc.AuthMethodBasic},
+ },
+ {
+ "none, basic and post",
+ args{func() op.Configuration {
+ m := mock.NewMockConfiguration(gomock.NewController(t))
+ m.EXPECT().AuthMethodPostSupported().Return(true)
+ m.EXPECT().AuthMethodPrivateKeyJWTSupported().Return(false)
+ return m
+ }()},
+ []oidc.AuthMethod{oidc.AuthMethodNone, oidc.AuthMethodBasic, oidc.AuthMethodPost},
+ },
+ {
+ "none, basic, post and private_key_jwt",
+ args{func() op.Configuration {
+ m := mock.NewMockConfiguration(gomock.NewController(t))
+ m.EXPECT().AuthMethodPostSupported().Return(true)
+ m.EXPECT().AuthMethodPrivateKeyJWTSupported().Return(true)
+ return m
+ }()},
+ []oidc.AuthMethod{oidc.AuthMethodNone, oidc.AuthMethodBasic, oidc.AuthMethodPost, oidc.AuthMethodPrivateKeyJWT},
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got := op.AuthMethodsRevocationEndpoint(tt.args.c)
+ assert.Equal(t, tt.want, got)
+ })
+ }
+}
+
+func TestSupportedClaims(t *testing.T) {
+ type args struct {
+ c op.Configuration
+ }
+ tests := []struct {
+ name string
+ args args
+ want []string
+ }{
+ {
+ "scopes",
+ args{},
+ []string{
+ "sub",
+ "aud",
+ "exp",
+ "iat",
+ "iss",
+ "auth_time",
+ "nonce",
+ "acr",
+ "amr",
+ "c_hash",
+ "at_hash",
+ "act",
+ "scopes",
+ "client_id",
+ "azp",
+ "preferred_username",
+ "name",
+ "family_name",
+ "given_name",
+ "locale",
+ "email",
+ "email_verified",
+ "phone_number",
+ "phone_number_verified",
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got := op.SupportedClaims(tt.args.c)
+ assert.Equal(t, tt.want, got)
+ })
+ }
+}
+
+func Test_CodeChallengeMethods(t *testing.T) {
+ type args struct {
+ c op.Configuration
+ }
+ tests := []struct {
+ name string
+ args args
+ want []oidc.CodeChallengeMethod
+ }{
+ {
+ "not supported",
+ args{func() op.Configuration {
+ m := mock.NewMockConfiguration(gomock.NewController(t))
+ m.EXPECT().CodeMethodS256Supported().Return(false)
+ return m
+ }()},
+ []oidc.CodeChallengeMethod{},
+ },
+ {
+ "S256",
+ args{func() op.Configuration {
+ m := mock.NewMockConfiguration(gomock.NewController(t))
+ m.EXPECT().CodeMethodS256Supported().Return(true)
+ return m
+ }()},
+ []oidc.CodeChallengeMethod{oidc.CodeChallengeMethodS256},
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got := op.CodeChallengeMethods(tt.args.c)
+ assert.Equal(t, tt.want, got)
})
}
}
diff --git a/pkg/op/endpoint_test.go b/pkg/op/endpoint_test.go
index 7c8d1ce..50de89c 100644
--- a/pkg/op/endpoint_test.go
+++ b/pkg/op/endpoint_test.go
@@ -3,7 +3,7 @@ package op_test
import (
"testing"
- "github.com/zitadel/oidc/pkg/op"
+ "github.com/zitadel/oidc/v2/pkg/op"
)
func TestEndpoint_Path(t *testing.T) {
diff --git a/pkg/op/error.go b/pkg/op/error.go
index 3c820d6..acca4ab 100644
--- a/pkg/op/error.go
+++ b/pkg/op/error.go
@@ -3,8 +3,8 @@ package op
import (
"net/http"
- httphelper "github.com/zitadel/oidc/pkg/http"
- "github.com/zitadel/oidc/pkg/oidc"
+ httphelper "github.com/zitadel/oidc/v2/pkg/http"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
)
type ErrAuthRequest interface {
diff --git a/pkg/op/keys.go b/pkg/op/keys.go
index a80211e..239ecbd 100644
--- a/pkg/op/keys.go
+++ b/pkg/op/keys.go
@@ -6,11 +6,11 @@ import (
"gopkg.in/square/go-jose.v2"
- httphelper "github.com/zitadel/oidc/pkg/http"
+ httphelper "github.com/zitadel/oidc/v2/pkg/http"
)
type KeyProvider interface {
- GetKeySet(context.Context) (*jose.JSONWebKeySet, error)
+ KeySet(context.Context) ([]Key, error)
}
func keysHandler(k KeyProvider) func(http.ResponseWriter, *http.Request) {
@@ -20,10 +20,23 @@ func keysHandler(k KeyProvider) func(http.ResponseWriter, *http.Request) {
}
func Keys(w http.ResponseWriter, r *http.Request, k KeyProvider) {
- keySet, err := k.GetKeySet(r.Context())
+ keySet, err := k.KeySet(r.Context())
if err != nil {
httphelper.MarshalJSONWithStatus(w, err, http.StatusInternalServerError)
return
}
- httphelper.MarshalJSON(w, keySet)
+ httphelper.MarshalJSON(w, jsonWebKeySet(keySet))
+}
+
+func jsonWebKeySet(keys []Key) *jose.JSONWebKeySet {
+ webKeys := make([]jose.JSONWebKey, len(keys))
+ for i, key := range keys {
+ webKeys[i] = jose.JSONWebKey{
+ KeyID: key.ID(),
+ Algorithm: string(key.Algorithm()),
+ Use: key.Use(),
+ Key: key.Key(),
+ }
+ }
+ return &jose.JSONWebKeySet{Keys: webKeys}
}
diff --git a/pkg/op/keys_test.go b/pkg/op/keys_test.go
index 7618589..2e56b78 100644
--- a/pkg/op/keys_test.go
+++ b/pkg/op/keys_test.go
@@ -11,9 +11,9 @@ import (
"github.com/stretchr/testify/assert"
"gopkg.in/square/go-jose.v2"
- "github.com/zitadel/oidc/pkg/oidc"
- "github.com/zitadel/oidc/pkg/op"
- "github.com/zitadel/oidc/pkg/op/mock"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
+ "github.com/zitadel/oidc/v2/pkg/op"
+ "github.com/zitadel/oidc/v2/pkg/op/mock"
)
func TestKeys(t *testing.T) {
@@ -35,7 +35,7 @@ func TestKeys(t *testing.T) {
args: args{
k: func() op.KeyProvider {
m := mock.NewMockKeyProvider(gomock.NewController(t))
- m.EXPECT().GetKeySet(gomock.Any()).Return(nil, oidc.ErrServerError())
+ m.EXPECT().KeySet(gomock.Any()).Return(nil, oidc.ErrServerError())
return m
}(),
},
@@ -51,39 +51,39 @@ func TestKeys(t *testing.T) {
args: args{
k: func() op.KeyProvider {
m := mock.NewMockKeyProvider(gomock.NewController(t))
- m.EXPECT().GetKeySet(gomock.Any()).Return(nil, nil)
+ m.EXPECT().KeySet(gomock.Any()).Return(nil, nil)
return m
}(),
},
res: res{
statusCode: http.StatusOK,
contentType: "application/json",
+ body: `{"keys":[]}
+`,
},
},
{
name: "list",
args: args{
k: func() op.KeyProvider {
- m := mock.NewMockKeyProvider(gomock.NewController(t))
- m.EXPECT().GetKeySet(gomock.Any()).Return(
- &jose.JSONWebKeySet{Keys: []jose.JSONWebKey{
- {
- Key: &rsa.PublicKey{
- N: big.NewInt(1),
- E: 1,
- },
- KeyID: "id",
- },
- }},
- nil,
- )
+ ctrl := gomock.NewController(t)
+ m := mock.NewMockKeyProvider(ctrl)
+ k := mock.NewMockKey(ctrl)
+ k.EXPECT().Key().Return(&rsa.PublicKey{
+ N: big.NewInt(1),
+ E: 1,
+ })
+ k.EXPECT().ID().Return("id")
+ k.EXPECT().Algorithm().Return(jose.RS256)
+ k.EXPECT().Use().Return("sig")
+ m.EXPECT().KeySet(gomock.Any()).Return([]op.Key{k}, nil)
return m
}(),
},
res: res{
statusCode: http.StatusOK,
contentType: "application/json",
- body: `{"keys":[{"kty":"RSA","kid":"id","n":"AQ","e":"AQ"}]}
+ body: `{"keys":[{"use":"sig","kty":"RSA","kid":"id","alg":"RS256","n":"AQ","e":"AQ"}]}
`,
},
},
diff --git a/pkg/op/mock/authorizer.mock.go b/pkg/op/mock/authorizer.mock.go
index 52f3877..cc913ee 100644
--- a/pkg/op/mock/authorizer.mock.go
+++ b/pkg/op/mock/authorizer.mock.go
@@ -1,15 +1,16 @@
// Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/zitadel/oidc/pkg/op (interfaces: Authorizer)
+// Source: github.com/zitadel/oidc/v2/pkg/op (interfaces: Authorizer)
// Package mock is a generated GoMock package.
package mock
import (
+ context "context"
reflect "reflect"
gomock "github.com/golang/mock/gomock"
- http "github.com/zitadel/oidc/pkg/http"
- op "github.com/zitadel/oidc/pkg/op"
+ http "github.com/zitadel/oidc/v2/pkg/http"
+ op "github.com/zitadel/oidc/v2/pkg/op"
)
// MockAuthorizer is a mock of Authorizer interface.
@@ -78,31 +79,17 @@ func (mr *MockAuthorizerMockRecorder) Encoder() *gomock.Call {
}
// IDTokenHintVerifier mocks base method.
-func (m *MockAuthorizer) IDTokenHintVerifier() op.IDTokenHintVerifier {
+func (m *MockAuthorizer) IDTokenHintVerifier(arg0 context.Context) op.IDTokenHintVerifier {
m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "IDTokenHintVerifier")
+ ret := m.ctrl.Call(m, "IDTokenHintVerifier", arg0)
ret0, _ := ret[0].(op.IDTokenHintVerifier)
return ret0
}
// IDTokenHintVerifier indicates an expected call of IDTokenHintVerifier.
-func (mr *MockAuthorizerMockRecorder) IDTokenHintVerifier() *gomock.Call {
+func (mr *MockAuthorizerMockRecorder) IDTokenHintVerifier(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IDTokenHintVerifier", reflect.TypeOf((*MockAuthorizer)(nil).IDTokenHintVerifier))
-}
-
-// Issuer mocks base method.
-func (m *MockAuthorizer) Issuer() string {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "Issuer")
- ret0, _ := ret[0].(string)
- return ret0
-}
-
-// Issuer indicates an expected call of Issuer.
-func (mr *MockAuthorizerMockRecorder) Issuer() *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Issuer", reflect.TypeOf((*MockAuthorizer)(nil).Issuer))
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IDTokenHintVerifier", reflect.TypeOf((*MockAuthorizer)(nil).IDTokenHintVerifier), arg0)
}
// RequestObjectSupported mocks base method.
@@ -119,20 +106,6 @@ func (mr *MockAuthorizerMockRecorder) RequestObjectSupported() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RequestObjectSupported", reflect.TypeOf((*MockAuthorizer)(nil).RequestObjectSupported))
}
-// Signer mocks base method.
-func (m *MockAuthorizer) Signer() op.Signer {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "Signer")
- ret0, _ := ret[0].(op.Signer)
- return ret0
-}
-
-// Signer indicates an expected call of Signer.
-func (mr *MockAuthorizerMockRecorder) Signer() *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Signer", reflect.TypeOf((*MockAuthorizer)(nil).Signer))
-}
-
// Storage mocks base method.
func (m *MockAuthorizer) Storage() op.Storage {
m.ctrl.T.Helper()
diff --git a/pkg/op/mock/authorizer.mock.impl.go b/pkg/op/mock/authorizer.mock.impl.go
index d4f29d5..3f1d525 100644
--- a/pkg/op/mock/authorizer.mock.impl.go
+++ b/pkg/op/mock/authorizer.mock.impl.go
@@ -8,8 +8,8 @@ import (
"github.com/gorilla/schema"
"gopkg.in/square/go-jose.v2"
- "github.com/zitadel/oidc/pkg/oidc"
- "github.com/zitadel/oidc/pkg/op"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
+ "github.com/zitadel/oidc/v2/pkg/op"
)
func NewAuthorizer(t *testing.T) op.Authorizer {
@@ -20,23 +20,13 @@ func NewAuthorizerExpectValid(t *testing.T, wantErr bool) op.Authorizer {
m := NewAuthorizer(t)
ExpectDecoder(m)
ExpectEncoder(m)
- ExpectSigner(m, t)
+ //ExpectSigner(m, t)
ExpectStorage(m, t)
ExpectVerifier(m, t)
// ExpectErrorHandler(m, t, wantErr)
return m
}
-// func NewAuthorizerExpectDecoderFails(t *testing.T) op.Authorizer {
-// m := NewAuthorizer(t)
-// ExpectDecoderFails(m)
-// ExpectEncoder(m)
-// ExpectSigner(m, t)
-// ExpectStorage(m, t)
-// ExpectErrorHandler(m, t)
-// return m
-// }
-
func ExpectDecoder(a op.Authorizer) {
mockA := a.(*MockAuthorizer)
mockA.EXPECT().Decoder().AnyTimes().Return(schema.NewDecoder())
@@ -47,17 +37,18 @@ func ExpectEncoder(a op.Authorizer) {
mockA.EXPECT().Encoder().AnyTimes().Return(schema.NewEncoder())
}
-func ExpectSigner(a op.Authorizer, t *testing.T) {
- mockA := a.(*MockAuthorizer)
- mockA.EXPECT().Signer().DoAndReturn(
- func() op.Signer {
- return &Sig{}
- })
-}
+//
+//func ExpectSigner(a op.Authorizer, t *testing.T) {
+// mockA := a.(*MockAuthorizer)
+// mockA.EXPECT().Signer().DoAndReturn(
+// func() op.Signer {
+// return &Sig{}
+// })
+//}
func ExpectVerifier(a op.Authorizer, t *testing.T) {
mockA := a.(*MockAuthorizer)
- mockA.EXPECT().IDTokenHintVerifier().DoAndReturn(
+ mockA.EXPECT().IDTokenHintVerifier(gomock.Any()).DoAndReturn(
func() op.IDTokenHintVerifier {
return op.NewIDTokenHintVerifier("", nil)
})
diff --git a/pkg/op/mock/client.go b/pkg/op/mock/client.go
index 3b16e5e..36df84a 100644
--- a/pkg/op/mock/client.go
+++ b/pkg/op/mock/client.go
@@ -5,8 +5,8 @@ import (
"github.com/golang/mock/gomock"
- "github.com/zitadel/oidc/pkg/oidc"
- "github.com/zitadel/oidc/pkg/op"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
+ "github.com/zitadel/oidc/v2/pkg/op"
)
func NewClient(t *testing.T) op.Client {
diff --git a/pkg/op/mock/client.mock.go b/pkg/op/mock/client.mock.go
index cfe3703..e3d19fb 100644
--- a/pkg/op/mock/client.mock.go
+++ b/pkg/op/mock/client.mock.go
@@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/zitadel/oidc/pkg/op (interfaces: Client)
+// Source: github.com/zitadel/oidc/v2/pkg/op (interfaces: Client)
// Package mock is a generated GoMock package.
package mock
@@ -9,8 +9,8 @@ import (
time "time"
gomock "github.com/golang/mock/gomock"
- oidc "github.com/zitadel/oidc/pkg/oidc"
- op "github.com/zitadel/oidc/pkg/op"
+ oidc "github.com/zitadel/oidc/v2/pkg/oidc"
+ op "github.com/zitadel/oidc/v2/pkg/op"
)
// MockClient is a mock of Client interface.
diff --git a/pkg/op/mock/configuration.mock.go b/pkg/op/mock/configuration.mock.go
index e0c90dc..fc3158a 100644
--- a/pkg/op/mock/configuration.mock.go
+++ b/pkg/op/mock/configuration.mock.go
@@ -1,14 +1,15 @@
// Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/zitadel/oidc/pkg/op (interfaces: Configuration)
+// Source: github.com/zitadel/oidc/v2/pkg/op (interfaces: Configuration)
// Package mock is a generated GoMock package.
package mock
import (
+ http "net/http"
reflect "reflect"
gomock "github.com/golang/mock/gomock"
- op "github.com/zitadel/oidc/pkg/op"
+ op "github.com/zitadel/oidc/v2/pkg/op"
language "golang.org/x/text/language"
)
@@ -161,6 +162,20 @@ func (mr *MockConfigurationMockRecorder) GrantTypeTokenExchangeSupported() *gomo
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GrantTypeTokenExchangeSupported", reflect.TypeOf((*MockConfiguration)(nil).GrantTypeTokenExchangeSupported))
}
+// Insecure mocks base method.
+func (m *MockConfiguration) Insecure() bool {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Insecure")
+ ret0, _ := ret[0].(bool)
+ return ret0
+}
+
+// Insecure indicates an expected call of Insecure.
+func (mr *MockConfigurationMockRecorder) Insecure() *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insecure", reflect.TypeOf((*MockConfiguration)(nil).Insecure))
+}
+
// IntrospectionAuthMethodPrivateKeyJWTSupported mocks base method.
func (m *MockConfiguration) IntrospectionAuthMethodPrivateKeyJWTSupported() bool {
m.ctrl.T.Helper()
@@ -203,18 +218,18 @@ func (mr *MockConfigurationMockRecorder) IntrospectionEndpointSigningAlgorithmsS
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IntrospectionEndpointSigningAlgorithmsSupported", reflect.TypeOf((*MockConfiguration)(nil).IntrospectionEndpointSigningAlgorithmsSupported))
}
-// Issuer mocks base method.
-func (m *MockConfiguration) Issuer() string {
+// IssuerFromRequest mocks base method.
+func (m *MockConfiguration) IssuerFromRequest(arg0 *http.Request) string {
m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "Issuer")
+ ret := m.ctrl.Call(m, "IssuerFromRequest", arg0)
ret0, _ := ret[0].(string)
return ret0
}
-// Issuer indicates an expected call of Issuer.
-func (mr *MockConfigurationMockRecorder) Issuer() *gomock.Call {
+// IssuerFromRequest indicates an expected call of IssuerFromRequest.
+func (mr *MockConfigurationMockRecorder) IssuerFromRequest(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Issuer", reflect.TypeOf((*MockConfiguration)(nil).Issuer))
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IssuerFromRequest", reflect.TypeOf((*MockConfiguration)(nil).IssuerFromRequest), arg0)
}
// KeysEndpoint mocks base method.
diff --git a/pkg/op/mock/discovery.mock.go b/pkg/op/mock/discovery.mock.go
new file mode 100644
index 0000000..0c78d52
--- /dev/null
+++ b/pkg/op/mock/discovery.mock.go
@@ -0,0 +1,51 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: github.com/zitadel/oidc/v2/pkg/op (interfaces: DiscoverStorage)
+
+// Package mock is a generated GoMock package.
+package mock
+
+import (
+ context "context"
+ reflect "reflect"
+
+ gomock "github.com/golang/mock/gomock"
+ jose "gopkg.in/square/go-jose.v2"
+)
+
+// MockDiscoverStorage is a mock of DiscoverStorage interface.
+type MockDiscoverStorage struct {
+ ctrl *gomock.Controller
+ recorder *MockDiscoverStorageMockRecorder
+}
+
+// MockDiscoverStorageMockRecorder is the mock recorder for MockDiscoverStorage.
+type MockDiscoverStorageMockRecorder struct {
+ mock *MockDiscoverStorage
+}
+
+// NewMockDiscoverStorage creates a new mock instance.
+func NewMockDiscoverStorage(ctrl *gomock.Controller) *MockDiscoverStorage {
+ mock := &MockDiscoverStorage{ctrl: ctrl}
+ mock.recorder = &MockDiscoverStorageMockRecorder{mock}
+ return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockDiscoverStorage) EXPECT() *MockDiscoverStorageMockRecorder {
+ return m.recorder
+}
+
+// SignatureAlgorithms mocks base method.
+func (m *MockDiscoverStorage) SignatureAlgorithms(arg0 context.Context) ([]jose.SignatureAlgorithm, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "SignatureAlgorithms", arg0)
+ ret0, _ := ret[0].([]jose.SignatureAlgorithm)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// SignatureAlgorithms indicates an expected call of SignatureAlgorithms.
+func (mr *MockDiscoverStorageMockRecorder) SignatureAlgorithms(arg0 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SignatureAlgorithms", reflect.TypeOf((*MockDiscoverStorage)(nil).SignatureAlgorithms), arg0)
+}
diff --git a/pkg/op/mock/generate.go b/pkg/op/mock/generate.go
index c9c7efa..0066571 100644
--- a/pkg/op/mock/generate.go
+++ b/pkg/op/mock/generate.go
@@ -1,8 +1,9 @@
package mock
-//go:generate mockgen -package mock -destination ./storage.mock.go github.com/zitadel/oidc/pkg/op Storage
-//go:generate mockgen -package mock -destination ./authorizer.mock.go github.com/zitadel/oidc/pkg/op Authorizer
-//go:generate mockgen -package mock -destination ./client.mock.go github.com/zitadel/oidc/pkg/op Client
-//go:generate mockgen -package mock -destination ./configuration.mock.go github.com/zitadel/oidc/pkg/op Configuration
-//go:generate mockgen -package mock -destination ./signer.mock.go github.com/zitadel/oidc/pkg/op Signer
-//go:generate mockgen -package mock -destination ./key.mock.go github.com/zitadel/oidc/pkg/op KeyProvider
+//go:generate mockgen -package mock -destination ./storage.mock.go github.com/zitadel/oidc/v2/pkg/op Storage
+//go:generate mockgen -package mock -destination ./authorizer.mock.go github.com/zitadel/oidc/v2/pkg/op Authorizer
+//go:generate mockgen -package mock -destination ./client.mock.go github.com/zitadel/oidc/v2/pkg/op Client
+//go:generate mockgen -package mock -destination ./configuration.mock.go github.com/zitadel/oidc/v2/pkg/op Configuration
+//go:generate mockgen -package mock -destination ./discovery.mock.go github.com/zitadel/oidc/v2/pkg/op DiscoverStorage
+//go:generate mockgen -package mock -destination ./signer.mock.go github.com/zitadel/oidc/v2/pkg/op SigningKey,Key
+//go:generate mockgen -package mock -destination ./key.mock.go github.com/zitadel/oidc/v2/pkg/op KeyProvider
diff --git a/pkg/op/mock/key.mock.go b/pkg/op/mock/key.mock.go
index 56d12dc..8831651 100644
--- a/pkg/op/mock/key.mock.go
+++ b/pkg/op/mock/key.mock.go
@@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/zitadel/oidc/pkg/op (interfaces: KeyProvider)
+// Source: github.com/zitadel/oidc/v2/pkg/op (interfaces: KeyProvider)
// Package mock is a generated GoMock package.
package mock
@@ -9,7 +9,7 @@ import (
reflect "reflect"
gomock "github.com/golang/mock/gomock"
- jose "gopkg.in/square/go-jose.v2"
+ op "github.com/zitadel/oidc/v2/pkg/op"
)
// MockKeyProvider is a mock of KeyProvider interface.
@@ -35,17 +35,17 @@ func (m *MockKeyProvider) EXPECT() *MockKeyProviderMockRecorder {
return m.recorder
}
-// GetKeySet mocks base method.
-func (m *MockKeyProvider) GetKeySet(arg0 context.Context) (*jose.JSONWebKeySet, error) {
+// KeySet mocks base method.
+func (m *MockKeyProvider) KeySet(arg0 context.Context) ([]op.Key, error) {
m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "GetKeySet", arg0)
- ret0, _ := ret[0].(*jose.JSONWebKeySet)
+ ret := m.ctrl.Call(m, "KeySet", arg0)
+ ret0, _ := ret[0].([]op.Key)
ret1, _ := ret[1].(error)
return ret0, ret1
}
-// GetKeySet indicates an expected call of GetKeySet.
-func (mr *MockKeyProviderMockRecorder) GetKeySet(arg0 interface{}) *gomock.Call {
+// KeySet indicates an expected call of KeySet.
+func (mr *MockKeyProviderMockRecorder) KeySet(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetKeySet", reflect.TypeOf((*MockKeyProvider)(nil).GetKeySet), arg0)
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeySet", reflect.TypeOf((*MockKeyProvider)(nil).KeySet), arg0)
}
diff --git a/pkg/op/mock/signer.mock.go b/pkg/op/mock/signer.mock.go
index 42a92fb..78c0efe 100644
--- a/pkg/op/mock/signer.mock.go
+++ b/pkg/op/mock/signer.mock.go
@@ -1,56 +1,69 @@
// Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/zitadel/oidc/pkg/op (interfaces: Signer)
+// Source: github.com/zitadel/oidc/v2/pkg/op (interfaces: SigningKey,Key)
// Package mock is a generated GoMock package.
package mock
import (
- context "context"
reflect "reflect"
gomock "github.com/golang/mock/gomock"
jose "gopkg.in/square/go-jose.v2"
)
-// MockSigner is a mock of Signer interface.
-type MockSigner struct {
+// MockSigningKey is a mock of SigningKey interface.
+type MockSigningKey struct {
ctrl *gomock.Controller
- recorder *MockSignerMockRecorder
+ recorder *MockSigningKeyMockRecorder
}
-// MockSignerMockRecorder is the mock recorder for MockSigner.
-type MockSignerMockRecorder struct {
- mock *MockSigner
+// MockSigningKeyMockRecorder is the mock recorder for MockSigningKey.
+type MockSigningKeyMockRecorder struct {
+ mock *MockSigningKey
}
-// NewMockSigner creates a new mock instance.
-func NewMockSigner(ctrl *gomock.Controller) *MockSigner {
- mock := &MockSigner{ctrl: ctrl}
- mock.recorder = &MockSignerMockRecorder{mock}
+// NewMockSigningKey creates a new mock instance.
+func NewMockSigningKey(ctrl *gomock.Controller) *MockSigningKey {
+ mock := &MockSigningKey{ctrl: ctrl}
+ mock.recorder = &MockSigningKeyMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
-func (m *MockSigner) EXPECT() *MockSignerMockRecorder {
+func (m *MockSigningKey) EXPECT() *MockSigningKeyMockRecorder {
return m.recorder
}
-// Health mocks base method.
-func (m *MockSigner) Health(arg0 context.Context) error {
+// ID mocks base method.
+func (m *MockSigningKey) ID() string {
m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "Health", arg0)
- ret0, _ := ret[0].(error)
+ ret := m.ctrl.Call(m, "ID")
+ ret0, _ := ret[0].(string)
return ret0
}
-// Health indicates an expected call of Health.
-func (mr *MockSignerMockRecorder) Health(arg0 interface{}) *gomock.Call {
+// ID indicates an expected call of ID.
+func (mr *MockSigningKeyMockRecorder) ID() *gomock.Call {
mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Health", reflect.TypeOf((*MockSigner)(nil).Health), arg0)
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ID", reflect.TypeOf((*MockSigningKey)(nil).ID))
+}
+
+// Key mocks base method.
+func (m *MockSigningKey) Key() interface{} {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Key")
+ ret0, _ := ret[0].(interface{})
+ return ret0
+}
+
+// Key indicates an expected call of Key.
+func (mr *MockSigningKeyMockRecorder) Key() *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Key", reflect.TypeOf((*MockSigningKey)(nil).Key))
}
// SignatureAlgorithm mocks base method.
-func (m *MockSigner) SignatureAlgorithm() jose.SignatureAlgorithm {
+func (m *MockSigningKey) SignatureAlgorithm() jose.SignatureAlgorithm {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SignatureAlgorithm")
ret0, _ := ret[0].(jose.SignatureAlgorithm)
@@ -58,21 +71,86 @@ func (m *MockSigner) SignatureAlgorithm() jose.SignatureAlgorithm {
}
// SignatureAlgorithm indicates an expected call of SignatureAlgorithm.
-func (mr *MockSignerMockRecorder) SignatureAlgorithm() *gomock.Call {
+func (mr *MockSigningKeyMockRecorder) SignatureAlgorithm() *gomock.Call {
mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SignatureAlgorithm", reflect.TypeOf((*MockSigner)(nil).SignatureAlgorithm))
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SignatureAlgorithm", reflect.TypeOf((*MockSigningKey)(nil).SignatureAlgorithm))
}
-// Signer mocks base method.
-func (m *MockSigner) Signer() jose.Signer {
+// MockKey is a mock of Key interface.
+type MockKey struct {
+ ctrl *gomock.Controller
+ recorder *MockKeyMockRecorder
+}
+
+// MockKeyMockRecorder is the mock recorder for MockKey.
+type MockKeyMockRecorder struct {
+ mock *MockKey
+}
+
+// NewMockKey creates a new mock instance.
+func NewMockKey(ctrl *gomock.Controller) *MockKey {
+ mock := &MockKey{ctrl: ctrl}
+ mock.recorder = &MockKeyMockRecorder{mock}
+ return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockKey) EXPECT() *MockKeyMockRecorder {
+ return m.recorder
+}
+
+// Algorithm mocks base method.
+func (m *MockKey) Algorithm() jose.SignatureAlgorithm {
m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "Signer")
- ret0, _ := ret[0].(jose.Signer)
+ ret := m.ctrl.Call(m, "Algorithm")
+ ret0, _ := ret[0].(jose.SignatureAlgorithm)
return ret0
}
-// Signer indicates an expected call of Signer.
-func (mr *MockSignerMockRecorder) Signer() *gomock.Call {
+// Algorithm indicates an expected call of Algorithm.
+func (mr *MockKeyMockRecorder) Algorithm() *gomock.Call {
mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Signer", reflect.TypeOf((*MockSigner)(nil).Signer))
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Algorithm", reflect.TypeOf((*MockKey)(nil).Algorithm))
+}
+
+// ID mocks base method.
+func (m *MockKey) ID() string {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "ID")
+ ret0, _ := ret[0].(string)
+ return ret0
+}
+
+// ID indicates an expected call of ID.
+func (mr *MockKeyMockRecorder) ID() *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ID", reflect.TypeOf((*MockKey)(nil).ID))
+}
+
+// Key mocks base method.
+func (m *MockKey) Key() interface{} {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Key")
+ ret0, _ := ret[0].(interface{})
+ return ret0
+}
+
+// Key indicates an expected call of Key.
+func (mr *MockKeyMockRecorder) Key() *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Key", reflect.TypeOf((*MockKey)(nil).Key))
+}
+
+// Use mocks base method.
+func (m *MockKey) Use() string {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Use")
+ ret0, _ := ret[0].(string)
+ return ret0
+}
+
+// Use indicates an expected call of Use.
+func (mr *MockKeyMockRecorder) Use() *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Use", reflect.TypeOf((*MockKey)(nil).Use))
}
diff --git a/pkg/op/mock/storage.mock.go b/pkg/op/mock/storage.mock.go
index 785a643..58cc2a0 100644
--- a/pkg/op/mock/storage.mock.go
+++ b/pkg/op/mock/storage.mock.go
@@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/zitadel/oidc/pkg/op (interfaces: Storage)
+// Source: github.com/zitadel/oidc/v2/pkg/op (interfaces: Storage)
// Package mock is a generated GoMock package.
package mock
@@ -10,8 +10,8 @@ import (
time "time"
gomock "github.com/golang/mock/gomock"
- oidc "github.com/zitadel/oidc/pkg/oidc"
- op "github.com/zitadel/oidc/pkg/op"
+ oidc "github.com/zitadel/oidc/v2/pkg/oidc"
+ op "github.com/zitadel/oidc/v2/pkg/op"
jose "gopkg.in/square/go-jose.v2"
)
@@ -174,21 +174,6 @@ func (mr *MockStorageMockRecorder) GetKeyByIDAndUserID(arg0, arg1, arg2 interfac
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetKeyByIDAndUserID", reflect.TypeOf((*MockStorage)(nil).GetKeyByIDAndUserID), arg0, arg1, arg2)
}
-// GetKeySet mocks base method.
-func (m *MockStorage) GetKeySet(arg0 context.Context) (*jose.JSONWebKeySet, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "GetKeySet", arg0)
- ret0, _ := ret[0].(*jose.JSONWebKeySet)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// GetKeySet indicates an expected call of GetKeySet.
-func (mr *MockStorageMockRecorder) GetKeySet(arg0 interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetKeySet", reflect.TypeOf((*MockStorage)(nil).GetKeySet), arg0)
-}
-
// GetPrivateClaimsFromScopes mocks base method.
func (m *MockStorage) GetPrivateClaimsFromScopes(arg0 context.Context, arg1, arg2 string, arg3 []string) (map[string]interface{}, error) {
m.ctrl.T.Helper()
@@ -204,18 +189,6 @@ func (mr *MockStorageMockRecorder) GetPrivateClaimsFromScopes(arg0, arg1, arg2,
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPrivateClaimsFromScopes", reflect.TypeOf((*MockStorage)(nil).GetPrivateClaimsFromScopes), arg0, arg1, arg2, arg3)
}
-// GetSigningKey mocks base method.
-func (m *MockStorage) GetSigningKey(arg0 context.Context, arg1 chan<- jose.SigningKey) {
- m.ctrl.T.Helper()
- m.ctrl.Call(m, "GetSigningKey", arg0, arg1)
-}
-
-// GetSigningKey indicates an expected call of GetSigningKey.
-func (mr *MockStorageMockRecorder) GetSigningKey(arg0, arg1 interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSigningKey", reflect.TypeOf((*MockStorage)(nil).GetSigningKey), arg0, arg1)
-}
-
// Health mocks base method.
func (m *MockStorage) Health(arg0 context.Context) error {
m.ctrl.T.Helper()
@@ -230,6 +203,21 @@ func (mr *MockStorageMockRecorder) Health(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Health", reflect.TypeOf((*MockStorage)(nil).Health), arg0)
}
+// KeySet mocks base method.
+func (m *MockStorage) KeySet(arg0 context.Context) ([]op.Key, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "KeySet", arg0)
+ ret0, _ := ret[0].([]op.Key)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// KeySet indicates an expected call of KeySet.
+func (mr *MockStorageMockRecorder) KeySet(arg0 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeySet", reflect.TypeOf((*MockStorage)(nil).KeySet), arg0)
+}
+
// RevokeToken mocks base method.
func (m *MockStorage) RevokeToken(arg0 context.Context, arg1, arg2, arg3 string) *oidc.Error {
m.ctrl.T.Helper()
@@ -300,6 +288,36 @@ func (mr *MockStorageMockRecorder) SetUserinfoFromToken(arg0, arg1, arg2, arg3,
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetUserinfoFromToken", reflect.TypeOf((*MockStorage)(nil).SetUserinfoFromToken), arg0, arg1, arg2, arg3, arg4)
}
+// SignatureAlgorithms mocks base method.
+func (m *MockStorage) SignatureAlgorithms(arg0 context.Context) ([]jose.SignatureAlgorithm, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "SignatureAlgorithms", arg0)
+ ret0, _ := ret[0].([]jose.SignatureAlgorithm)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// SignatureAlgorithms indicates an expected call of SignatureAlgorithms.
+func (mr *MockStorageMockRecorder) SignatureAlgorithms(arg0 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SignatureAlgorithms", reflect.TypeOf((*MockStorage)(nil).SignatureAlgorithms), arg0)
+}
+
+// SigningKey mocks base method.
+func (m *MockStorage) SigningKey(arg0 context.Context) (op.SigningKey, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "SigningKey", arg0)
+ ret0, _ := ret[0].(op.SigningKey)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// SigningKey indicates an expected call of SigningKey.
+func (mr *MockStorageMockRecorder) SigningKey(arg0 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SigningKey", reflect.TypeOf((*MockStorage)(nil).SigningKey), arg0)
+}
+
// TerminateSession mocks base method.
func (m *MockStorage) TerminateSession(arg0 context.Context, arg1, arg2 string) error {
m.ctrl.T.Helper()
diff --git a/pkg/op/mock/storage.mock.impl.go b/pkg/op/mock/storage.mock.impl.go
index 946cee0..9269f89 100644
--- a/pkg/op/mock/storage.mock.impl.go
+++ b/pkg/op/mock/storage.mock.impl.go
@@ -6,13 +6,10 @@ import (
"testing"
"time"
- "github.com/zitadel/oidc/pkg/oidc"
-
- "gopkg.in/square/go-jose.v2"
-
"github.com/golang/mock/gomock"
- "github.com/zitadel/oidc/pkg/op"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
+ "github.com/zitadel/oidc/v2/pkg/op"
)
func NewStorage(t *testing.T) op.Storage {
@@ -41,13 +38,13 @@ func NewMockStorageAny(t *testing.T) op.Storage {
func NewMockStorageSigningKeyInvalid(t *testing.T) op.Storage {
m := NewStorage(t)
- ExpectSigningKeyInvalid(m)
+ //ExpectSigningKeyInvalid(m)
return m
}
func NewMockStorageSigningKey(t *testing.T) op.Storage {
m := NewStorage(t)
- ExpectSigningKey(m)
+ //ExpectSigningKey(m)
return m
}
@@ -85,24 +82,6 @@ func ExpectValidClientID(s op.Storage) {
})
}
-func ExpectSigningKeyInvalid(s op.Storage) {
- mockS := s.(*MockStorage)
- mockS.EXPECT().GetSigningKey(gomock.Any(), gomock.Any()).DoAndReturn(
- func(_ context.Context, keyCh chan<- jose.SigningKey) {
- keyCh <- jose.SigningKey{}
- },
- )
-}
-
-func ExpectSigningKey(s op.Storage) {
- mockS := s.(*MockStorage)
- mockS.EXPECT().GetSigningKey(gomock.Any(), gomock.Any()).DoAndReturn(
- func(_ context.Context, keyCh chan<- jose.SigningKey) {
- keyCh <- jose.SigningKey{Algorithm: jose.HS256, Key: []byte("key")}
- },
- )
-}
-
type ConfClient struct {
id string
appType op.ApplicationType
diff --git a/pkg/op/op.go b/pkg/op/op.go
index d85dcd6..acedcb6 100644
--- a/pkg/op/op.go
+++ b/pkg/op/op.go
@@ -12,8 +12,8 @@ import (
"golang.org/x/text/language"
"gopkg.in/square/go-jose.v2"
- httphelper "github.com/zitadel/oidc/pkg/http"
- "github.com/zitadel/oidc/pkg/oidc"
+ httphelper "github.com/zitadel/oidc/v2/pkg/http"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
)
const (
@@ -29,78 +29,79 @@ const (
defaultKeysEndpoint = "keys"
)
-var DefaultEndpoints = &endpoints{
- Authorization: NewEndpoint(defaultAuthorizationEndpoint),
- Token: NewEndpoint(defaultTokenEndpoint),
- Introspection: NewEndpoint(defaultIntrospectEndpoint),
- Userinfo: NewEndpoint(defaultUserinfoEndpoint),
- Revocation: NewEndpoint(defaultRevocationEndpoint),
- EndSession: NewEndpoint(defaultEndSessionEndpoint),
- JwksURI: NewEndpoint(defaultKeysEndpoint),
-}
+var (
+ DefaultEndpoints = &endpoints{
+ Authorization: NewEndpoint(defaultAuthorizationEndpoint),
+ Token: NewEndpoint(defaultTokenEndpoint),
+ Introspection: NewEndpoint(defaultIntrospectEndpoint),
+ Userinfo: NewEndpoint(defaultUserinfoEndpoint),
+ Revocation: NewEndpoint(defaultRevocationEndpoint),
+ EndSession: NewEndpoint(defaultEndSessionEndpoint),
+ JwksURI: NewEndpoint(defaultKeysEndpoint),
+ }
+
+ defaultCORSOptions = cors.Options{
+ AllowCredentials: true,
+ AllowedHeaders: []string{
+ "Origin",
+ "Accept",
+ "Accept-Language",
+ "Authorization",
+ "Content-Type",
+ "X-Requested-With",
+ },
+ AllowedMethods: []string{
+ http.MethodGet,
+ http.MethodHead,
+ http.MethodPost,
+ },
+ ExposedHeaders: []string{
+ "Location",
+ "Content-Length",
+ },
+ AllowOriginFunc: func(_ string) bool {
+ return true
+ },
+ }
+)
type OpenIDProvider interface {
Configuration
Storage() Storage
Decoder() httphelper.Decoder
Encoder() httphelper.Encoder
- IDTokenHintVerifier() IDTokenHintVerifier
- AccessTokenVerifier() AccessTokenVerifier
+ IDTokenHintVerifier(context.Context) IDTokenHintVerifier
+ AccessTokenVerifier(context.Context) AccessTokenVerifier
Crypto() Crypto
DefaultLogoutRedirectURI() string
- Signer() Signer
Probes() []ProbesFn
HttpHandler() http.Handler
}
type HttpInterceptor func(http.Handler) http.Handler
-var defaultCORSOptions = cors.Options{
- AllowCredentials: true,
- AllowedHeaders: []string{
- "Origin",
- "Accept",
- "Accept-Language",
- "Authorization",
- "Content-Type",
- "X-Requested-With",
- },
- AllowedMethods: []string{
- http.MethodGet,
- http.MethodHead,
- http.MethodPost,
- },
- ExposedHeaders: []string{
- "Location",
- "Content-Length",
- },
- AllowOriginFunc: func(_ string) bool {
- return true
- },
-}
-
func CreateRouter(o OpenIDProvider, interceptors ...HttpInterceptor) *mux.Router {
- intercept := buildInterceptor(interceptors...)
router := mux.NewRouter()
router.Use(cors.New(defaultCORSOptions).Handler)
+ router.Use(intercept(o.IssuerFromRequest, interceptors...))
router.HandleFunc(healthEndpoint, healthHandler)
router.HandleFunc(readinessEndpoint, readyHandler(o.Probes()))
- router.HandleFunc(oidc.DiscoveryEndpoint, discoveryHandler(o, o.Signer()))
- router.Handle(o.AuthorizationEndpoint().Relative(), intercept(authorizeHandler(o)))
- router.NewRoute().Path(authCallbackPath(o)).Queries("id", "{id}").Handler(intercept(authorizeCallbackHandler(o)))
- router.Handle(o.TokenEndpoint().Relative(), intercept(tokenHandler(o)))
+ router.HandleFunc(oidc.DiscoveryEndpoint, discoveryHandler(o, o.Storage()))
+ router.HandleFunc(o.AuthorizationEndpoint().Relative(), authorizeHandler(o))
+ router.NewRoute().Path(authCallbackPath(o)).Queries("id", "{id}").HandlerFunc(authorizeCallbackHandler(o))
+ router.HandleFunc(o.TokenEndpoint().Relative(), tokenHandler(o))
router.HandleFunc(o.IntrospectionEndpoint().Relative(), introspectionHandler(o))
router.HandleFunc(o.UserinfoEndpoint().Relative(), userinfoHandler(o))
router.HandleFunc(o.RevocationEndpoint().Relative(), revocationHandler(o))
- router.Handle(o.EndSessionEndpoint().Relative(), intercept(endSessionHandler(o)))
+ router.HandleFunc(o.EndSessionEndpoint().Relative(), endSessionHandler(o))
router.HandleFunc(o.KeysEndpoint().Relative(), keysHandler(o.Storage()))
return router
}
// AuthCallbackURL builds the url for the redirect (with the requestID) after a successful login
-func AuthCallbackURL(o OpenIDProvider) func(string) string {
- return func(requestID string) string {
- return o.AuthorizationEndpoint().Absolute(o.Issuer()) + authCallbackPathSuffix + "?id=" + requestID
+func AuthCallbackURL(o OpenIDProvider) func(context.Context, string) string {
+ return func(ctx context.Context, requestID string) string {
+ return o.AuthorizationEndpoint().Absolute(IssuerFromContext(ctx)) + authCallbackPathSuffix + "?id=" + requestID
}
}
@@ -109,7 +110,6 @@ func authCallbackPath(o OpenIDProvider) string {
}
type Config struct {
- Issuer string
CryptoKey [32]byte
DefaultLogoutRedirectURI string
CodeMethodS256 bool
@@ -133,29 +133,34 @@ type endpoints struct {
// NewOpenIDProvider creates a provider. The provider provides (with HttpHandler())
// a http.Router that handles a suite of endpoints (some paths can be overridden):
-// /healthz
-// /ready
-// /.well-known/openid-configuration
-// /oauth/token
-// /oauth/introspect
-// /callback
-// /authorize
-// /userinfo
-// /revoke
-// /end_session
-// /keys
+//
+// /healthz
+// /ready
+// /.well-known/openid-configuration
+// /oauth/token
+// /oauth/introspect
+// /callback
+// /authorize
+// /userinfo
+// /revoke
+// /end_session
+// /keys
+//
// This does not include login. Login is handled with a redirect that includes the
// request ID. The redirect for logins is specified per-client by Client.LoginURL().
// Successful logins should mark the request as authorized and redirect back to to
// op.AuthCallbackURL(provider) which is probably /callback. On the redirect back
// to the AuthCallbackURL, the request id should be passed as the "id" parameter.
-func NewOpenIDProvider(ctx context.Context, config *Config, storage Storage, opOpts ...Option) (OpenIDProvider, error) {
- err := ValidateIssuer(config.Issuer)
- if err != nil {
- return nil, err
- }
+func NewOpenIDProvider(ctx context.Context, issuer string, config *Config, storage Storage, opOpts ...Option) (*Provider, error) {
+ return newProvider(ctx, config, storage, StaticIssuer(issuer), opOpts...)
+}
- o := &openidProvider{
+func NewDynamicOpenIDProvider(ctx context.Context, path string, config *Config, storage Storage, opOpts ...Option) (*Provider, error) {
+ return newProvider(ctx, config, storage, IssuerFromHost(path), opOpts...)
+}
+
+func newProvider(ctx context.Context, config *Config, storage Storage, issuer func(bool) (IssuerFromRequest, error), opOpts ...Option) (_ *Provider, err error) {
+ o := &Provider{
config: config,
storage: storage,
endpoints: DefaultEndpoints,
@@ -168,9 +173,10 @@ func NewOpenIDProvider(ctx context.Context, config *Config, storage Storage, opO
}
}
- keyCh := make(chan jose.SigningKey)
- go storage.GetSigningKey(ctx, keyCh)
- o.signer = NewSigner(ctx, storage, keyCh)
+ o.issuer, err = issuer(o.insecure)
+ if err != nil {
+ return nil, err
+ }
o.httpHandler = CreateRouter(o, o.interceptors...)
@@ -182,22 +188,17 @@ func NewOpenIDProvider(ctx context.Context, config *Config, storage Storage, opO
o.crypto = NewAESCrypto(config.CryptoKey)
// Avoid potential race conditions by calling these early
- _ = o.AccessTokenVerifier() // sets accessTokenVerifier
- _ = o.IDTokenHintVerifier() // sets idTokenHintVerifier
- _ = o.JWTProfileVerifier() // sets jwtProfileVerifier
- _ = o.openIDKeySet() // sets keySet
+ _ = o.openIDKeySet() // sets keySet
return o, nil
}
-type openidProvider struct {
+type Provider struct {
config *Config
+ issuer IssuerFromRequest
+ insecure bool
endpoints *endpoints
storage Storage
- signer Signer
- idTokenHintVerifier IDTokenHintVerifier
- jwtProfileVerifier JWTProfileVerifier
- accessTokenVerifier AccessTokenVerifier
keySet *openIDKeySet
crypto Crypto
httpHandler http.Handler
@@ -209,159 +210,149 @@ type openidProvider struct {
idTokenHintVerifierOpts []IDTokenHintVerifierOpt
}
-func (o *openidProvider) Issuer() string {
- return o.config.Issuer
+func (o *Provider) IssuerFromRequest(r *http.Request) string {
+ return o.issuer(r)
}
-func (o *openidProvider) AuthorizationEndpoint() Endpoint {
+func (o *Provider) Insecure() bool {
+ return o.insecure
+}
+
+func (o *Provider) AuthorizationEndpoint() Endpoint {
return o.endpoints.Authorization
}
-func (o *openidProvider) TokenEndpoint() Endpoint {
+func (o *Provider) TokenEndpoint() Endpoint {
return o.endpoints.Token
}
-func (o *openidProvider) IntrospectionEndpoint() Endpoint {
+func (o *Provider) IntrospectionEndpoint() Endpoint {
return o.endpoints.Introspection
}
-func (o *openidProvider) UserinfoEndpoint() Endpoint {
+func (o *Provider) UserinfoEndpoint() Endpoint {
return o.endpoints.Userinfo
}
-func (o *openidProvider) RevocationEndpoint() Endpoint {
+func (o *Provider) RevocationEndpoint() Endpoint {
return o.endpoints.Revocation
}
-func (o *openidProvider) EndSessionEndpoint() Endpoint {
+func (o *Provider) EndSessionEndpoint() Endpoint {
return o.endpoints.EndSession
}
-func (o *openidProvider) KeysEndpoint() Endpoint {
+func (o *Provider) KeysEndpoint() Endpoint {
return o.endpoints.JwksURI
}
-func (o *openidProvider) AuthMethodPostSupported() bool {
+func (o *Provider) AuthMethodPostSupported() bool {
return o.config.AuthMethodPost
}
-func (o *openidProvider) CodeMethodS256Supported() bool {
+func (o *Provider) CodeMethodS256Supported() bool {
return o.config.CodeMethodS256
}
-func (o *openidProvider) AuthMethodPrivateKeyJWTSupported() bool {
+func (o *Provider) AuthMethodPrivateKeyJWTSupported() bool {
return o.config.AuthMethodPrivateKeyJWT
}
-func (o *openidProvider) TokenEndpointSigningAlgorithmsSupported() []string {
+func (o *Provider) TokenEndpointSigningAlgorithmsSupported() []string {
return []string{"RS256"}
}
-func (o *openidProvider) GrantTypeRefreshTokenSupported() bool {
+func (o *Provider) GrantTypeRefreshTokenSupported() bool {
return o.config.GrantTypeRefreshToken
}
-func (o *openidProvider) GrantTypeTokenExchangeSupported() bool {
+func (o *Provider) GrantTypeTokenExchangeSupported() bool {
return false
}
-func (o *openidProvider) GrantTypeJWTAuthorizationSupported() bool {
+func (o *Provider) GrantTypeJWTAuthorizationSupported() bool {
return true
}
-func (o *openidProvider) GrantTypeClientCredentialsSupported() bool {
+func (o *Provider) IntrospectionAuthMethodPrivateKeyJWTSupported() bool {
+ return true
+}
+
+func (o *Provider) IntrospectionEndpointSigningAlgorithmsSupported() []string {
+ return []string{"RS256"}
+}
+
+func (o *Provider) GrantTypeClientCredentialsSupported() bool {
_, ok := o.storage.(ClientCredentialsStorage)
return ok
}
-func (o *openidProvider) IntrospectionAuthMethodPrivateKeyJWTSupported() bool {
+func (o *Provider) RevocationAuthMethodPrivateKeyJWTSupported() bool {
return true
}
-func (o *openidProvider) IntrospectionEndpointSigningAlgorithmsSupported() []string {
+func (o *Provider) RevocationEndpointSigningAlgorithmsSupported() []string {
return []string{"RS256"}
}
-func (o *openidProvider) RevocationAuthMethodPrivateKeyJWTSupported() bool {
- return true
-}
-
-func (o *openidProvider) RevocationEndpointSigningAlgorithmsSupported() []string {
- return []string{"RS256"}
-}
-
-func (o *openidProvider) RequestObjectSupported() bool {
+func (o *Provider) RequestObjectSupported() bool {
return o.config.RequestObjectSupported
}
-func (o *openidProvider) RequestObjectSigningAlgorithmsSupported() []string {
+func (o *Provider) RequestObjectSigningAlgorithmsSupported() []string {
return []string{"RS256"}
}
-func (o *openidProvider) SupportedUILocales() []language.Tag {
+func (o *Provider) SupportedUILocales() []language.Tag {
return o.config.SupportedUILocales
}
-func (o *openidProvider) Storage() Storage {
+func (o *Provider) Storage() Storage {
return o.storage
}
-func (o *openidProvider) Decoder() httphelper.Decoder {
+func (o *Provider) Decoder() httphelper.Decoder {
return o.decoder
}
-func (o *openidProvider) Encoder() httphelper.Encoder {
+func (o *Provider) Encoder() httphelper.Encoder {
return o.encoder
}
-func (o *openidProvider) IDTokenHintVerifier() IDTokenHintVerifier {
- if o.idTokenHintVerifier == nil {
- o.idTokenHintVerifier = NewIDTokenHintVerifier(o.Issuer(), o.openIDKeySet(), o.idTokenHintVerifierOpts...)
- }
- return o.idTokenHintVerifier
+func (o *Provider) IDTokenHintVerifier(ctx context.Context) IDTokenHintVerifier {
+ return NewIDTokenHintVerifier(IssuerFromContext(ctx), o.openIDKeySet(), o.idTokenHintVerifierOpts...)
}
-func (o *openidProvider) JWTProfileVerifier() JWTProfileVerifier {
- if o.jwtProfileVerifier == nil {
- o.jwtProfileVerifier = NewJWTProfileVerifier(o.Storage(), o.Issuer(), 1*time.Hour, time.Second)
- }
- return o.jwtProfileVerifier
+func (o *Provider) JWTProfileVerifier(ctx context.Context) JWTProfileVerifier {
+ return NewJWTProfileVerifier(o.Storage(), IssuerFromContext(ctx), 1*time.Hour, time.Second)
}
-func (o *openidProvider) AccessTokenVerifier() AccessTokenVerifier {
- if o.accessTokenVerifier == nil {
- o.accessTokenVerifier = NewAccessTokenVerifier(o.Issuer(), o.openIDKeySet(), o.accessTokenVerifierOpts...)
- }
- return o.accessTokenVerifier
+func (o *Provider) AccessTokenVerifier(ctx context.Context) AccessTokenVerifier {
+ return NewAccessTokenVerifier(IssuerFromContext(ctx), o.openIDKeySet(), o.accessTokenVerifierOpts...)
}
-func (o *openidProvider) openIDKeySet() oidc.KeySet {
+func (o *Provider) openIDKeySet() oidc.KeySet {
if o.keySet == nil {
o.keySet = &openIDKeySet{o.Storage()}
}
return o.keySet
}
-func (o *openidProvider) Crypto() Crypto {
+func (o *Provider) Crypto() Crypto {
return o.crypto
}
-func (o *openidProvider) DefaultLogoutRedirectURI() string {
+func (o *Provider) DefaultLogoutRedirectURI() string {
return o.config.DefaultLogoutRedirectURI
}
-func (o *openidProvider) Signer() Signer {
- return o.signer
-}
-
-func (o *openidProvider) Probes() []ProbesFn {
+func (o *Provider) Probes() []ProbesFn {
return []ProbesFn{
- ReadySigner(o.Signer()),
ReadyStorage(o.Storage()),
}
}
-func (o *openidProvider) HttpHandler() http.Handler {
+func (o *Provider) HttpHandler() http.Handler {
return o.httpHandler
}
@@ -372,22 +363,31 @@ type openIDKeySet struct {
// VerifySignature implements the oidc.KeySet interface
// providing an implementation for the keys stored in the OP Storage interface
func (o *openIDKeySet) VerifySignature(ctx context.Context, jws *jose.JSONWebSignature) ([]byte, error) {
- keySet, err := o.Storage.GetKeySet(ctx)
+ keySet, err := o.Storage.KeySet(ctx)
if err != nil {
return nil, fmt.Errorf("error fetching keys: %w", err)
}
keyID, alg := oidc.GetKeyIDAndAlg(jws)
- key, err := oidc.FindMatchingKey(keyID, oidc.KeyUseSignature, alg, keySet.Keys...)
+ key, err := oidc.FindMatchingKey(keyID, oidc.KeyUseSignature, alg, jsonWebKeySet(keySet).Keys...)
if err != nil {
return nil, fmt.Errorf("invalid signature: %w", err)
}
return jws.Verify(&key)
}
-type Option func(o *openidProvider) error
+type Option func(o *Provider) error
+
+// WithAllowInsecure allows the use of http (instead of https) for issuers
+// this is not recommended for production use and violates the OIDC specification
+func WithAllowInsecure() Option {
+ return func(o *Provider) error {
+ o.insecure = true
+ return nil
+ }
+}
func WithCustomAuthEndpoint(endpoint Endpoint) Option {
- return func(o *openidProvider) error {
+ return func(o *Provider) error {
if err := endpoint.Validate(); err != nil {
return err
}
@@ -397,7 +397,7 @@ func WithCustomAuthEndpoint(endpoint Endpoint) Option {
}
func WithCustomTokenEndpoint(endpoint Endpoint) Option {
- return func(o *openidProvider) error {
+ return func(o *Provider) error {
if err := endpoint.Validate(); err != nil {
return err
}
@@ -407,7 +407,7 @@ func WithCustomTokenEndpoint(endpoint Endpoint) Option {
}
func WithCustomIntrospectionEndpoint(endpoint Endpoint) Option {
- return func(o *openidProvider) error {
+ return func(o *Provider) error {
if err := endpoint.Validate(); err != nil {
return err
}
@@ -417,7 +417,7 @@ func WithCustomIntrospectionEndpoint(endpoint Endpoint) Option {
}
func WithCustomUserinfoEndpoint(endpoint Endpoint) Option {
- return func(o *openidProvider) error {
+ return func(o *Provider) error {
if err := endpoint.Validate(); err != nil {
return err
}
@@ -427,7 +427,7 @@ func WithCustomUserinfoEndpoint(endpoint Endpoint) Option {
}
func WithCustomRevocationEndpoint(endpoint Endpoint) Option {
- return func(o *openidProvider) error {
+ return func(o *Provider) error {
if err := endpoint.Validate(); err != nil {
return err
}
@@ -437,7 +437,7 @@ func WithCustomRevocationEndpoint(endpoint Endpoint) Option {
}
func WithCustomEndSessionEndpoint(endpoint Endpoint) Option {
- return func(o *openidProvider) error {
+ return func(o *Provider) error {
if err := endpoint.Validate(); err != nil {
return err
}
@@ -447,7 +447,7 @@ func WithCustomEndSessionEndpoint(endpoint Endpoint) Option {
}
func WithCustomKeysEndpoint(endpoint Endpoint) Option {
- return func(o *openidProvider) error {
+ return func(o *Provider) error {
if err := endpoint.Validate(); err != nil {
return err
}
@@ -457,7 +457,7 @@ func WithCustomKeysEndpoint(endpoint Endpoint) Option {
}
func WithCustomEndpoints(auth, token, userInfo, revocation, endSession, keys Endpoint) Option {
- return func(o *openidProvider) error {
+ return func(o *Provider) error {
o.endpoints.Authorization = auth
o.endpoints.Token = token
o.endpoints.Userinfo = userInfo
@@ -469,38 +469,32 @@ func WithCustomEndpoints(auth, token, userInfo, revocation, endSession, keys End
}
func WithHttpInterceptors(interceptors ...HttpInterceptor) Option {
- return func(o *openidProvider) error {
+ return func(o *Provider) error {
o.interceptors = append(o.interceptors, interceptors...)
return nil
}
}
func WithAccessTokenVerifierOpts(opts ...AccessTokenVerifierOpt) Option {
- return func(o *openidProvider) error {
+ return func(o *Provider) error {
o.accessTokenVerifierOpts = opts
return nil
}
}
func WithIDTokenHintVerifierOpts(opts ...IDTokenHintVerifierOpt) Option {
- return func(o *openidProvider) error {
+ return func(o *Provider) error {
o.idTokenHintVerifierOpts = opts
return nil
}
}
-func buildInterceptor(interceptors ...HttpInterceptor) func(http.HandlerFunc) http.Handler {
- return func(handlerFunc http.HandlerFunc) http.Handler {
- handler := handlerFuncToHandler(handlerFunc)
+func intercept(i IssuerFromRequest, interceptors ...HttpInterceptor) func(handler http.Handler) http.Handler {
+ issuerInterceptor := NewIssuerInterceptor(i)
+ return func(handler http.Handler) http.Handler {
for i := len(interceptors) - 1; i >= 0; i-- {
handler = interceptors[i](handler)
}
- return handler
+ return cors.New(defaultCORSOptions).Handler(issuerInterceptor.Handler(handler))
}
}
-
-func handlerFuncToHandler(handlerFunc http.HandlerFunc) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- handlerFunc(w, r)
- })
-}
diff --git a/pkg/op/probes.go b/pkg/op/probes.go
index 7b80fb4..a56c92b 100644
--- a/pkg/op/probes.go
+++ b/pkg/op/probes.go
@@ -5,7 +5,7 @@ import (
"errors"
"net/http"
- httphelper "github.com/zitadel/oidc/pkg/http"
+ httphelper "github.com/zitadel/oidc/v2/pkg/http"
)
type ProbesFn func(context.Context) error
@@ -31,15 +31,6 @@ func Readiness(w http.ResponseWriter, r *http.Request, probes ...ProbesFn) {
ok(w)
}
-func ReadySigner(s Signer) ProbesFn {
- return func(ctx context.Context) error {
- if s == nil {
- return errors.New("no signer")
- }
- return s.Health(ctx)
- }
-}
-
func ReadyStorage(s Storage) ProbesFn {
return func(ctx context.Context) error {
if s == nil {
diff --git a/pkg/op/session.go b/pkg/op/session.go
index c4984fc..e1cc595 100644
--- a/pkg/op/session.go
+++ b/pkg/op/session.go
@@ -5,14 +5,14 @@ import (
"net/http"
"net/url"
- httphelper "github.com/zitadel/oidc/pkg/http"
- "github.com/zitadel/oidc/pkg/oidc"
+ httphelper "github.com/zitadel/oidc/v2/pkg/http"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
)
type SessionEnder interface {
Decoder() httphelper.Decoder
Storage() Storage
- IDTokenHintVerifier() IDTokenHintVerifier
+ IDTokenHintVerifier(context.Context) IDTokenHintVerifier
DefaultLogoutRedirectURI() string
}
@@ -59,7 +59,7 @@ func ValidateEndSessionRequest(ctx context.Context, req *oidc.EndSessionRequest,
RedirectURI: ender.DefaultLogoutRedirectURI(),
}
if req.IdTokenHint != "" {
- claims, err := VerifyIDTokenHint(ctx, req.IdTokenHint, ender.IDTokenHintVerifier())
+ claims, err := VerifyIDTokenHint(ctx, req.IdTokenHint, ender.IDTokenHintVerifier(ctx))
if err != nil {
return nil, oidc.ErrInvalidRequest().WithDescription("id_token_hint invalid").WithParent(err)
}
diff --git a/pkg/op/signer.go b/pkg/op/signer.go
index 828876e..22ef8ca 100644
--- a/pkg/op/signer.go
+++ b/pkg/op/signer.go
@@ -1,88 +1,38 @@
package op
import (
- "context"
"errors"
- "sync"
- "github.com/zitadel/logging"
"gopkg.in/square/go-jose.v2"
)
-type Signer interface {
- Health(ctx context.Context) error
- Signer() jose.Signer
+var (
+ ErrSignerCreationFailed = errors.New("signer creation failed")
+)
+
+type SigningKey interface {
SignatureAlgorithm() jose.SignatureAlgorithm
+ Key() interface{}
+ ID() string
}
-type tokenSigner struct {
- signer jose.Signer
- storage AuthStorage
- alg jose.SignatureAlgorithm
- lock sync.RWMutex
-}
-
-func NewSigner(ctx context.Context, storage AuthStorage, keyCh <-chan jose.SigningKey) Signer {
- s := &tokenSigner{
- storage: storage,
- }
-
- select {
- case <-ctx.Done():
- return nil
- case key := <-keyCh:
- s.exchangeSigningKey(key)
- }
- go s.refreshSigningKey(ctx, keyCh)
-
- return s
-}
-
-func (s *tokenSigner) Health(_ context.Context) error {
- if s.signer == nil {
- return errors.New("no signer")
- }
- if string(s.alg) == "" {
- return errors.New("no signing algorithm")
- }
- return nil
-}
-
-func (s *tokenSigner) Signer() jose.Signer {
- s.lock.RLock()
- defer s.lock.RUnlock()
- return s.signer
-}
-
-func (s *tokenSigner) refreshSigningKey(ctx context.Context, keyCh <-chan jose.SigningKey) {
- for {
- select {
- case <-ctx.Done():
- return
- case key := <-keyCh:
- s.exchangeSigningKey(key)
- }
- }
-}
-
-func (s *tokenSigner) exchangeSigningKey(key jose.SigningKey) {
- s.lock.Lock()
- defer s.lock.Unlock()
- s.alg = key.Algorithm
- if key.Algorithm == "" || key.Key == nil {
- s.signer = nil
- logging.Warn("signer has no key")
- return
- }
- var err error
- s.signer, err = jose.NewSigner(key, &jose.SignerOptions{})
+func SignerFromKey(key SigningKey) (jose.Signer, error) {
+ signer, err := jose.NewSigner(jose.SigningKey{
+ Algorithm: key.SignatureAlgorithm(),
+ Key: &jose.JSONWebKey{
+ Key: key.Key(),
+ KeyID: key.ID(),
+ },
+ }, &jose.SignerOptions{})
if err != nil {
- logging.New().WithError(err).Error("error creating signer")
- return
+ return nil, ErrSignerCreationFailed //TODO: log / wrap error?
}
- logging.Info("signer exchanged signing key")
+ return signer, nil
}
-func (s *tokenSigner) SignatureAlgorithm() jose.SignatureAlgorithm {
- return s.alg
+type Key interface {
+ ID() string
+ Algorithm() jose.SignatureAlgorithm
+ Use() string
+ Key() interface{}
}
diff --git a/pkg/op/storage.go b/pkg/op/storage.go
index 153cd21..b040b72 100644
--- a/pkg/op/storage.go
+++ b/pkg/op/storage.go
@@ -7,7 +7,7 @@ import (
"gopkg.in/square/go-jose.v2"
- "github.com/zitadel/oidc/pkg/oidc"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
)
type AuthStorage interface {
@@ -47,8 +47,14 @@ type AuthStorage interface {
// tokenOrTokenID will be the refresh token, not its ID.
RevokeToken(ctx context.Context, tokenOrTokenID string, userID string, clientID string) *oidc.Error
- GetSigningKey(context.Context, chan<- jose.SigningKey)
- GetKeySet(context.Context) (*jose.JSONWebKeySet, error)
+ SigningKey(context.Context) (SigningKey, error)
+ SignatureAlgorithms(context.Context) ([]jose.SignatureAlgorithm, error)
+ KeySet(context.Context) ([]Key, error)
+}
+
+type ClientCredentialsStorage interface {
+ ClientCredentials(ctx context.Context, clientID, clientSecret string) (Client, error)
+ ClientCredentialsTokenRequest(ctx context.Context, clientID string, scopes []string) (TokenRequest, error)
}
// CanRefreshTokenInfo is an optional additional interface that Storage can support.
@@ -62,10 +68,6 @@ type CanRefreshTokenInfo interface {
var ErrInvalidRefreshToken = errors.New("invalid_refresh_token")
-type ClientCredentialsStorage interface {
- ClientCredentialsTokenRequest(ctx context.Context, clientID string, scopes []string) (TokenRequest, error)
-}
-
type OPStorage interface {
GetClientByClientID(ctx context.Context, clientID string) (Client, error)
AuthorizeClientIDSecret(ctx context.Context, clientID, clientSecret string) error
@@ -80,6 +82,12 @@ type OPStorage interface {
ValidateJWTProfileScopes(ctx context.Context, userID string, scopes []string) ([]string, error)
}
+// JWTProfileTokenStorage is an additional, optional storage to implement
+// implementing it, allows specifying the [AccessTokenType] of the access_token returned form the JWT Profile TokenRequest
+type JWTProfileTokenStorage interface {
+ JWTProfileTokenType(ctx context.Context, request TokenRequest) (AccessTokenType, error)
+}
+
// Storage is a required parameter for NewOpenIDProvider(). In addition to the
// embedded interfaces below, if the passed Storage implements ClientCredentialsStorage
// then the grant type "client_credentials" will be supported. In that case, the access
diff --git a/pkg/op/token.go b/pkg/op/token.go
index 3a72261..4d3e620 100644
--- a/pkg/op/token.go
+++ b/pkg/op/token.go
@@ -4,14 +4,12 @@ import (
"context"
"time"
- "github.com/zitadel/oidc/pkg/crypto"
- "github.com/zitadel/oidc/pkg/oidc"
- "github.com/zitadel/oidc/pkg/strings"
+ "github.com/zitadel/oidc/v2/pkg/crypto"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
+ "github.com/zitadel/oidc/v2/pkg/strings"
)
type TokenCreator interface {
- Issuer() string
- Signer() Signer
Storage() Storage
Crypto() Crypto
}
@@ -22,6 +20,13 @@ type TokenRequest interface {
GetScopes() []string
}
+type AccessTokenClient interface {
+ GetID() string
+ ClockSkew() time.Duration
+ RestrictAdditionalAccessTokenScopes() func(scopes []string) []string
+ GrantTypes() []oidc.GrantType
+}
+
func CreateTokenResponse(ctx context.Context, request IDTokenRequest, client Client, creator TokenCreator, createAccessToken bool, code, refreshToken string) (*oidc.AccessTokenResponse, error) {
var accessToken, newRefreshToken string
var validity time.Duration
@@ -32,7 +37,7 @@ func CreateTokenResponse(ctx context.Context, request IDTokenRequest, client Cli
return nil, err
}
}
- idToken, err := CreateIDToken(ctx, creator.Issuer(), request, client.IDTokenLifetime(), accessToken, code, creator.Storage(), creator.Signer(), client)
+ idToken, err := CreateIDToken(ctx, IssuerFromContext(ctx), request, client.IDTokenLifetime(), accessToken, code, creator.Storage(), client)
if err != nil {
return nil, err
}
@@ -57,7 +62,7 @@ func CreateTokenResponse(ctx context.Context, request IDTokenRequest, client Cli
}, nil
}
-func createTokens(ctx context.Context, tokenRequest TokenRequest, storage Storage, refreshToken string, client Client) (id, newRefreshToken string, exp time.Time, err error) {
+func createTokens(ctx context.Context, tokenRequest TokenRequest, storage Storage, refreshToken string, client AccessTokenClient) (id, newRefreshToken string, exp time.Time, err error) {
if needsRefreshToken(tokenRequest, client) {
return storage.CreateAccessAndRefreshTokens(ctx, tokenRequest, refreshToken)
}
@@ -65,7 +70,7 @@ func createTokens(ctx context.Context, tokenRequest TokenRequest, storage Storag
return
}
-func needsRefreshToken(tokenRequest TokenRequest, client Client) bool {
+func needsRefreshToken(tokenRequest TokenRequest, client AccessTokenClient) bool {
switch req := tokenRequest.(type) {
case AuthRequest:
return strings.Contains(req.GetScopes(), oidc.ScopeOfflineAccess) && req.GetResponseType() == oidc.ResponseTypeCode && ValidateGrantType(client, oidc.GrantTypeRefreshToken)
@@ -76,7 +81,7 @@ func needsRefreshToken(tokenRequest TokenRequest, client Client) bool {
}
}
-func CreateAccessToken(ctx context.Context, tokenRequest TokenRequest, accessTokenType AccessTokenType, creator TokenCreator, client Client, refreshToken string) (accessToken, newRefreshToken string, validity time.Duration, err error) {
+func CreateAccessToken(ctx context.Context, tokenRequest TokenRequest, accessTokenType AccessTokenType, creator TokenCreator, client AccessTokenClient, refreshToken string) (accessToken, newRefreshToken string, validity time.Duration, err error) {
id, newRefreshToken, exp, err := createTokens(ctx, tokenRequest, creator.Storage(), refreshToken, client)
if err != nil {
return "", "", 0, err
@@ -87,7 +92,7 @@ func CreateAccessToken(ctx context.Context, tokenRequest TokenRequest, accessTok
}
validity = exp.Add(clockSkew).Sub(time.Now().UTC())
if accessTokenType == AccessTokenTypeJWT {
- accessToken, err = CreateJWT(ctx, creator.Issuer(), tokenRequest, exp, id, creator.Signer(), client, creator.Storage())
+ accessToken, err = CreateJWT(ctx, IssuerFromContext(ctx), tokenRequest, exp, id, client, creator.Storage())
return
}
accessToken, err = CreateBearerToken(id, tokenRequest.GetSubject(), creator.Crypto())
@@ -98,7 +103,7 @@ func CreateBearerToken(tokenID, subject string, crypto Crypto) (string, error) {
return crypto.Encrypt(tokenID + ":" + subject)
}
-func CreateJWT(ctx context.Context, issuer string, tokenRequest TokenRequest, exp time.Time, id string, signer Signer, client Client, storage Storage) (string, error) {
+func CreateJWT(ctx context.Context, issuer string, tokenRequest TokenRequest, exp time.Time, id string, client AccessTokenClient, storage Storage) (string, error) {
claims := oidc.NewAccessTokenClaims(issuer, tokenRequest.GetSubject(), tokenRequest.GetAudience(), exp, id, client.GetID(), client.ClockSkew())
if client != nil {
restrictedScopes := client.RestrictAdditionalAccessTokenScopes()(tokenRequest.GetScopes())
@@ -108,7 +113,15 @@ func CreateJWT(ctx context.Context, issuer string, tokenRequest TokenRequest, ex
}
claims.SetPrivateClaims(privateClaims)
}
- return crypto.Sign(claims, signer.Signer())
+ signingKey, err := storage.SigningKey(ctx)
+ if err != nil {
+ return "", err
+ }
+ signer, err := SignerFromKey(signingKey)
+ if err != nil {
+ return "", err
+ }
+ return crypto.Sign(claims, signer)
}
type IDTokenRequest interface {
@@ -120,7 +133,7 @@ type IDTokenRequest interface {
GetSubject() string
}
-func CreateIDToken(ctx context.Context, issuer string, request IDTokenRequest, validity time.Duration, accessToken, code string, storage Storage, signer Signer, client Client) (string, error) {
+func CreateIDToken(ctx context.Context, issuer string, request IDTokenRequest, validity time.Duration, accessToken, code string, storage Storage, client Client) (string, error) {
exp := time.Now().UTC().Add(client.ClockSkew()).Add(validity)
var acr, nonce string
if authRequest, ok := request.(AuthRequest); ok {
@@ -129,8 +142,12 @@ func CreateIDToken(ctx context.Context, issuer string, request IDTokenRequest, v
}
claims := oidc.NewIDTokenClaims(issuer, request.GetSubject(), request.GetAudience(), exp, request.GetAuthTime(), nonce, acr, request.GetAMR(), request.GetClientID(), client.ClockSkew())
scopes := client.RestrictAdditionalIdTokenScopes()(request.GetScopes())
+ signingKey, err := storage.SigningKey(ctx)
+ if err != nil {
+ return "", err
+ }
if accessToken != "" {
- atHash, err := oidc.ClaimHash(accessToken, signer.SignatureAlgorithm())
+ atHash, err := oidc.ClaimHash(accessToken, signingKey.SignatureAlgorithm())
if err != nil {
return "", err
}
@@ -148,14 +165,17 @@ func CreateIDToken(ctx context.Context, issuer string, request IDTokenRequest, v
claims.SetUserinfo(userInfo)
}
if code != "" {
- codeHash, err := oidc.ClaimHash(code, signer.SignatureAlgorithm())
+ codeHash, err := oidc.ClaimHash(code, signingKey.SignatureAlgorithm())
if err != nil {
return "", err
}
claims.SetCodeHash(codeHash)
}
-
- return crypto.Sign(claims, signer.Signer())
+ signer, err := SignerFromKey(signingKey)
+ if err != nil {
+ return "", err
+ }
+ return crypto.Sign(claims, signer)
}
func removeUserinfoScopes(scopes []string) []string {
diff --git a/pkg/op/token_client_credentials.go b/pkg/op/token_client_credentials.go
index 3787667..fc31d57 100644
--- a/pkg/op/token_client_credentials.go
+++ b/pkg/op/token_client_credentials.go
@@ -5,8 +5,8 @@ import (
"net/http"
"net/url"
- httphelper "github.com/zitadel/oidc/pkg/http"
- "github.com/zitadel/oidc/pkg/oidc"
+ httphelper "github.com/zitadel/oidc/v2/pkg/http"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
)
// ClientCredentialsExchange handles the OAuth 2.0 client_credentials grant, including
@@ -63,15 +63,15 @@ func ParseClientCredentialsRequest(r *http.Request, decoder httphelper.Decoder)
return request, nil
}
-// ValidateClientCredentialsRequest validates the refresh_token request parameters including authorization check of the client
-// and returns the data representing the original auth request corresponding to the refresh_token
+// ValidateClientCredentialsRequest validates the client_credentials request parameters including authorization check of the client
+// and returns a TokenRequest and Client implementation to be used in the client_credentials response, resp. creation of the corresponding access_token.
func ValidateClientCredentialsRequest(ctx context.Context, request *oidc.ClientCredentialsRequest, exchanger Exchanger) (TokenRequest, Client, error) {
storage, ok := exchanger.Storage().(ClientCredentialsStorage)
if !ok {
return nil, nil, oidc.ErrUnsupportedGrantType().WithDescription("client_credentials grant not supported")
}
- client, err := AuthorizeClientCredentialsClient(ctx, request, exchanger)
+ client, err := AuthorizeClientCredentialsClient(ctx, request, storage)
if err != nil {
return nil, nil, err
}
@@ -84,12 +84,8 @@ func ValidateClientCredentialsRequest(ctx context.Context, request *oidc.ClientC
return tokenRequest, client, nil
}
-func AuthorizeClientCredentialsClient(ctx context.Context, request *oidc.ClientCredentialsRequest, exchanger Exchanger) (Client, error) {
- if err := AuthorizeClientIDSecret(ctx, request.ClientID, request.ClientSecret, exchanger.Storage()); err != nil {
- return nil, err
- }
-
- client, err := exchanger.Storage().GetClientByClientID(ctx, request.ClientID)
+func AuthorizeClientCredentialsClient(ctx context.Context, request *oidc.ClientCredentialsRequest, storage ClientCredentialsStorage) (Client, error) {
+ client, err := storage.ClientCredentials(ctx, request.ClientID, request.ClientSecret)
if err != nil {
return nil, oidc.ErrInvalidClient().WithParent(err)
}
@@ -102,7 +98,7 @@ func AuthorizeClientCredentialsClient(ctx context.Context, request *oidc.ClientC
}
func CreateClientCredentialsTokenResponse(ctx context.Context, tokenRequest TokenRequest, creator TokenCreator, client Client) (*oidc.AccessTokenResponse, error) {
- accessToken, _, validity, err := CreateAccessToken(ctx, tokenRequest, AccessTokenTypeJWT, creator, client, "")
+ accessToken, _, validity, err := CreateAccessToken(ctx, tokenRequest, client.AccessTokenType(), creator, client, "")
if err != nil {
return nil, err
}
diff --git a/pkg/op/token_code.go b/pkg/op/token_code.go
index ec48233..565a477 100644
--- a/pkg/op/token_code.go
+++ b/pkg/op/token_code.go
@@ -4,8 +4,8 @@ import (
"context"
"net/http"
- httphelper "github.com/zitadel/oidc/pkg/http"
- "github.com/zitadel/oidc/pkg/oidc"
+ httphelper "github.com/zitadel/oidc/v2/pkg/http"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
)
// CodeExchange handles the OAuth 2.0 authorization_code grant, including
diff --git a/pkg/op/token_intospection.go b/pkg/op/token_intospection.go
index f402c8b..dfc8954 100644
--- a/pkg/op/token_intospection.go
+++ b/pkg/op/token_intospection.go
@@ -1,24 +1,25 @@
package op
import (
+ "context"
"errors"
"net/http"
"net/url"
- httphelper "github.com/zitadel/oidc/pkg/http"
- "github.com/zitadel/oidc/pkg/oidc"
+ httphelper "github.com/zitadel/oidc/v2/pkg/http"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
)
type Introspector interface {
Decoder() httphelper.Decoder
Crypto() Crypto
Storage() Storage
- AccessTokenVerifier() AccessTokenVerifier
+ AccessTokenVerifier(context.Context) AccessTokenVerifier
}
type IntrospectorJWTProfile interface {
Introspector
- JWTProfileVerifier() JWTProfileVerifier
+ JWTProfileVerifier(context.Context) JWTProfileVerifier
}
func introspectionHandler(introspector Introspector) func(http.ResponseWriter, *http.Request) {
@@ -62,7 +63,7 @@ func ParseTokenIntrospectionRequest(r *http.Request, introspector Introspector)
return "", "", errors.New("unable to parse request")
}
if introspectorJWTProfile, ok := introspector.(IntrospectorJWTProfile); ok && req.ClientAssertion != "" {
- profile, err := VerifyJWTAssertion(r.Context(), req.ClientAssertion, introspectorJWTProfile.JWTProfileVerifier())
+ profile, err := VerifyJWTAssertion(r.Context(), req.ClientAssertion, introspectorJWTProfile.JWTProfileVerifier(r.Context()))
if err == nil {
return req.Token, profile.Issuer, nil
}
diff --git a/pkg/op/token_jwt_profile.go b/pkg/op/token_jwt_profile.go
index eb21517..23bac9a 100644
--- a/pkg/op/token_jwt_profile.go
+++ b/pkg/op/token_jwt_profile.go
@@ -5,13 +5,13 @@ import (
"net/http"
"time"
- httphelper "github.com/zitadel/oidc/pkg/http"
- "github.com/zitadel/oidc/pkg/oidc"
+ httphelper "github.com/zitadel/oidc/v2/pkg/http"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
)
type JWTAuthorizationGrantExchanger interface {
Exchanger
- JWTProfileVerifier() JWTProfileVerifier
+ JWTProfileVerifier(context.Context) JWTProfileVerifier
}
// JWTProfile handles the OAuth 2.0 JWT Profile Authorization Grant https://tools.ietf.org/html/rfc7523#section-2.1
@@ -21,7 +21,7 @@ func JWTProfile(w http.ResponseWriter, r *http.Request, exchanger JWTAuthorizati
RequestError(w, r, err)
}
- tokenRequest, err := VerifyJWTAssertion(r.Context(), profileRequest.Assertion, exchanger.JWTProfileVerifier())
+ tokenRequest, err := VerifyJWTAssertion(r.Context(), profileRequest.Assertion, exchanger.JWTProfileVerifier(r.Context()))
if err != nil {
RequestError(w, r, err)
return
@@ -53,27 +53,65 @@ func ParseJWTProfileGrantRequest(r *http.Request, decoder httphelper.Decoder) (*
return tokenReq, nil
}
-// CreateJWTTokenResponse creates
+// CreateJWTTokenResponse creates an access_token response for a JWT Profile Grant request
+// by default the access_token is an opaque string, but can be specified by implementing the JWTProfileTokenStorage interface
func CreateJWTTokenResponse(ctx context.Context, tokenRequest TokenRequest, creator TokenCreator) (*oidc.AccessTokenResponse, error) {
- id, exp, err := creator.Storage().CreateAccessToken(ctx, tokenRequest)
- if err != nil {
- return nil, err
- }
- accessToken, err := CreateBearerToken(id, tokenRequest.GetSubject(), creator.Crypto())
- if err != nil {
- return nil, err
+ // return an opaque token as default to not break current implementations
+ tokenType := AccessTokenTypeBearer
+
+ // the current CreateAccessToken function, esp. CreateJWT requires an implementation of an AccessTokenClient
+ client := &jwtProfileClient{
+ id: tokenRequest.GetSubject(),
}
+ // by implementing the JWTProfileTokenStorage the storage can specify the AccessTokenType to be returned
+ tokenStorage, ok := creator.Storage().(JWTProfileTokenStorage)
+ if ok {
+ var err error
+ tokenType, err = tokenStorage.JWTProfileTokenType(ctx, tokenRequest)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ accessToken, _, validity, err := CreateAccessToken(ctx, tokenRequest, tokenType, creator, client, "")
+ if err != nil {
+ return nil, err
+ }
return &oidc.AccessTokenResponse{
AccessToken: accessToken,
TokenType: oidc.BearerToken,
- ExpiresIn: uint64(exp.Sub(time.Now().UTC()).Seconds()),
+ ExpiresIn: uint64(validity.Seconds()),
}, nil
}
// ParseJWTProfileRequest has been renamed to ParseJWTProfileGrantRequest
//
-//deprecated: use ParseJWTProfileGrantRequest
+// deprecated: use ParseJWTProfileGrantRequest
func ParseJWTProfileRequest(r *http.Request, decoder httphelper.Decoder) (*oidc.JWTProfileGrantRequest, error) {
return ParseJWTProfileGrantRequest(r, decoder)
}
+
+type jwtProfileClient struct {
+ id string
+}
+
+func (j *jwtProfileClient) GetID() string {
+ return j.id
+}
+
+func (j *jwtProfileClient) ClockSkew() time.Duration {
+ return 0
+}
+
+func (j *jwtProfileClient) RestrictAdditionalAccessTokenScopes() func(scopes []string) []string {
+ return func(scopes []string) []string {
+ return scopes
+ }
+}
+
+func (j *jwtProfileClient) GrantTypes() []oidc.GrantType {
+ return []oidc.GrantType{
+ oidc.GrantTypeBearer,
+ }
+}
diff --git a/pkg/op/token_refresh.go b/pkg/op/token_refresh.go
index 7251eeb..148d2a4 100644
--- a/pkg/op/token_refresh.go
+++ b/pkg/op/token_refresh.go
@@ -6,9 +6,9 @@ import (
"net/http"
"time"
- httphelper "github.com/zitadel/oidc/pkg/http"
- "github.com/zitadel/oidc/pkg/oidc"
- "github.com/zitadel/oidc/pkg/strings"
+ httphelper "github.com/zitadel/oidc/v2/pkg/http"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
+ "github.com/zitadel/oidc/v2/pkg/strings"
)
type RefreshTokenRequest interface {
diff --git a/pkg/op/token_request.go b/pkg/op/token_request.go
index 6ccd489..190e812 100644
--- a/pkg/op/token_request.go
+++ b/pkg/op/token_request.go
@@ -5,15 +5,13 @@ import (
"net/http"
"net/url"
- httphelper "github.com/zitadel/oidc/pkg/http"
- "github.com/zitadel/oidc/pkg/oidc"
+ httphelper "github.com/zitadel/oidc/v2/pkg/http"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
)
type Exchanger interface {
- Issuer() string
Storage() Storage
Decoder() httphelper.Decoder
- Signer() Signer
Crypto() Crypto
AuthMethodPostSupported() bool
AuthMethodPrivateKeyJWTSupported() bool
@@ -122,7 +120,7 @@ func AuthorizeCodeChallenge(tokenReq *oidc.AccessTokenRequest, challenge *oidc.C
// AuthorizePrivateJWTKey authorizes a client by validating the client_assertion's signature with a previously
// registered public key (JWT Profile)
func AuthorizePrivateJWTKey(ctx context.Context, clientAssertion string, exchanger JWTAuthorizationGrantExchanger) (Client, error) {
- jwtReq, err := VerifyJWTAssertion(ctx, clientAssertion, exchanger.JWTProfileVerifier())
+ jwtReq, err := VerifyJWTAssertion(ctx, clientAssertion, exchanger.JWTProfileVerifier(ctx))
if err != nil {
return nil, err
}
@@ -136,8 +134,8 @@ func AuthorizePrivateJWTKey(ctx context.Context, clientAssertion string, exchang
return client, nil
}
-// ValidateGrantType ensures that the requested grant_type is allowed by the Client
-func ValidateGrantType(client Client, grantType oidc.GrantType) bool {
+// ValidateGrantType ensures that the requested grant_type is allowed by the client
+func ValidateGrantType(client interface{ GrantTypes() []oidc.GrantType }, grantType oidc.GrantType) bool {
if client == nil {
return false
}
diff --git a/pkg/op/token_revocation.go b/pkg/op/token_revocation.go
index 9dd0295..7dbd4a7 100644
--- a/pkg/op/token_revocation.go
+++ b/pkg/op/token_revocation.go
@@ -7,22 +7,22 @@ import (
"net/url"
"strings"
- httphelper "github.com/zitadel/oidc/pkg/http"
- "github.com/zitadel/oidc/pkg/oidc"
+ httphelper "github.com/zitadel/oidc/v2/pkg/http"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
)
type Revoker interface {
Decoder() httphelper.Decoder
Crypto() Crypto
Storage() Storage
- AccessTokenVerifier() AccessTokenVerifier
+ AccessTokenVerifier(context.Context) AccessTokenVerifier
AuthMethodPrivateKeyJWTSupported() bool
AuthMethodPostSupported() bool
}
type RevokerJWTProfile interface {
Revoker
- JWTProfileVerifier() JWTProfileVerifier
+ JWTProfileVerifier(context.Context) JWTProfileVerifier
}
func revocationHandler(revoker Revoker) func(http.ResponseWriter, *http.Request) {
@@ -87,7 +87,7 @@ func ParseTokenRevocationRequest(r *http.Request, revoker Revoker) (token, token
if !ok || !revoker.AuthMethodPrivateKeyJWTSupported() {
return "", "", "", oidc.ErrInvalidClient().WithDescription("auth_method private_key_jwt not supported")
}
- profile, err := VerifyJWTAssertion(r.Context(), req.ClientAssertion, revokerJWTProfile.JWTProfileVerifier())
+ profile, err := VerifyJWTAssertion(r.Context(), req.ClientAssertion, revokerJWTProfile.JWTProfileVerifier(r.Context()))
if err == nil {
return req.Token, req.TokenTypeHint, profile.Issuer, nil
}
@@ -151,7 +151,7 @@ func getTokenIDAndSubjectForRevocation(ctx context.Context, userinfoProvider Use
}
return splitToken[0], splitToken[1], true
}
- accessTokenClaims, err := VerifyAccessToken(ctx, accessToken, userinfoProvider.AccessTokenVerifier())
+ accessTokenClaims, err := VerifyAccessToken(ctx, accessToken, userinfoProvider.AccessTokenVerifier(ctx))
if err != nil {
return "", "", false
}
diff --git a/pkg/op/userinfo.go b/pkg/op/userinfo.go
index 4bd03e2..cb8f0ae 100644
--- a/pkg/op/userinfo.go
+++ b/pkg/op/userinfo.go
@@ -6,15 +6,15 @@ import (
"net/http"
"strings"
- httphelper "github.com/zitadel/oidc/pkg/http"
- "github.com/zitadel/oidc/pkg/oidc"
+ httphelper "github.com/zitadel/oidc/v2/pkg/http"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
)
type UserinfoProvider interface {
Decoder() httphelper.Decoder
Crypto() Crypto
Storage() Storage
- AccessTokenVerifier() AccessTokenVerifier
+ AccessTokenVerifier(context.Context) AccessTokenVerifier
}
func userinfoHandler(userinfoProvider UserinfoProvider) func(http.ResponseWriter, *http.Request) {
@@ -81,7 +81,7 @@ func getTokenIDAndSubject(ctx context.Context, userinfoProvider UserinfoProvider
}
return splitToken[0], splitToken[1], true
}
- accessTokenClaims, err := VerifyAccessToken(ctx, accessToken, userinfoProvider.AccessTokenVerifier())
+ accessTokenClaims, err := VerifyAccessToken(ctx, accessToken, userinfoProvider.AccessTokenVerifier(ctx))
if err != nil {
return "", "", false
}
diff --git a/pkg/op/verifier_access_token.go b/pkg/op/verifier_access_token.go
index 1729c23..1d53adb 100644
--- a/pkg/op/verifier_access_token.go
+++ b/pkg/op/verifier_access_token.go
@@ -4,7 +4,7 @@ import (
"context"
"time"
- "github.com/zitadel/oidc/pkg/oidc"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
)
type AccessTokenVerifier interface {
diff --git a/pkg/op/verifier_id_token_hint.go b/pkg/op/verifier_id_token_hint.go
index d36bbd8..9320106 100644
--- a/pkg/op/verifier_id_token_hint.go
+++ b/pkg/op/verifier_id_token_hint.go
@@ -4,7 +4,7 @@ import (
"context"
"time"
- "github.com/zitadel/oidc/pkg/oidc"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
)
type IDTokenHintVerifier interface {
@@ -73,7 +73,7 @@ func NewIDTokenHintVerifier(issuer string, keySet oidc.KeySet, opts ...IDTokenHi
}
// VerifyIDTokenHint validates the id token according to
-//https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
+// https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
func VerifyIDTokenHint(ctx context.Context, token string, v IDTokenHintVerifier) (oidc.IDTokenClaims, error) {
claims := oidc.EmptyIDTokenClaims()
diff --git a/pkg/op/verifier_jwt_profile.go b/pkg/op/verifier_jwt_profile.go
index 0215e84..9befb64 100644
--- a/pkg/op/verifier_jwt_profile.go
+++ b/pkg/op/verifier_jwt_profile.go
@@ -8,7 +8,7 @@ import (
"gopkg.in/square/go-jose.v2"
- "github.com/zitadel/oidc/pkg/oidc"
+ "github.com/zitadel/oidc/v2/pkg/oidc"
)
type JWTProfileVerifier interface {