-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
2e13d2d
commit 8367fb0
Showing
15 changed files
with
720 additions
and
137 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package jwt | ||
|
||
import ( | ||
"context" | ||
"github.com/golang-jwt/jwt/v5" | ||
) | ||
|
||
// KeyfuncProvider is a pluggable provider of JWT verifying key functions. | ||
type KeyfuncProvider interface { | ||
// GetKeyfunc gets the JWT verifying key function for an issuer. | ||
GetKeyfunc(ctx context.Context) (jwt.Keyfunc, error) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package okta | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"github.com/MicahParks/jwkset" | ||
"github.com/MicahParks/keyfunc/v3" | ||
"github.com/golang-jwt/jwt/v5" | ||
"github.com/sovietaced/okta-jwt-verifier/metadata" | ||
"net/http" | ||
"net/url" | ||
"sync" | ||
) | ||
|
||
type Options struct { | ||
httpClient *http.Client | ||
} | ||
|
||
func WithHttpClient(httpClient *http.Client) Option { | ||
return func(mo *Options) { | ||
mo.httpClient = httpClient | ||
} | ||
} | ||
|
||
func defaultOptions() *Options { | ||
opts := &Options{} | ||
return opts | ||
} | ||
|
||
// Option for the OktaMetadataProvider | ||
type Option func(*Options) | ||
|
||
type KeyfuncProvider struct { | ||
mp metadata.Provider | ||
httpClient *http.Client | ||
storageMap map[string]jwkset.Storage | ||
|
||
storageMutex sync.Mutex | ||
} | ||
|
||
func NewKeyfuncProvider(mp metadata.Provider, options ...Option) *KeyfuncProvider { | ||
opts := defaultOptions() | ||
for _, option := range options { | ||
option(opts) | ||
} | ||
|
||
return &KeyfuncProvider{mp: mp, httpClient: opts.httpClient, storageMap: map[string]jwkset.Storage{}} | ||
} | ||
|
||
func (kp *KeyfuncProvider) GetKeyfunc(ctx context.Context) (jwt.Keyfunc, error) { | ||
md, err := kp.mp.GetMetadata(ctx) | ||
if err != nil { | ||
return nil, fmt.Errorf("getting metadata: %w", err) | ||
} | ||
|
||
storage, err := kp.getOrCreateStorage(md.JwksUri) | ||
if err != nil { | ||
return nil, fmt.Errorf("getting storage: %w", err) | ||
} | ||
|
||
k, err := keyfunc.New(keyfunc.Options{Storage: storage}) | ||
if err != nil { | ||
return nil, fmt.Errorf("creating keyfunc: %w", err) | ||
} | ||
|
||
return k.Keyfunc, nil | ||
} | ||
|
||
func (kp *KeyfuncProvider) getOrCreateStorage(jwksUri string) (jwkset.Storage, error) { | ||
storage, exists := kp.storageMap[jwksUri] | ||
if exists { | ||
return storage, nil | ||
} | ||
|
||
// Acquire a lock | ||
kp.storageMutex.Lock() | ||
defer kp.storageMutex.Unlock() | ||
|
||
// Check again to protect against races | ||
storage, exists = kp.storageMap[jwksUri] | ||
if exists { | ||
return storage, nil | ||
} | ||
|
||
u, err := url.Parse(jwksUri) | ||
if err != nil { | ||
return nil, fmt.Errorf("parsing jwks uri: %w", err) | ||
} | ||
|
||
storage, err = jwkset.NewStorageFromHTTP(u, jwkset.HTTPClientStorageOptions{Client: kp.httpClient}) | ||
if err != nil { | ||
return nil, fmt.Errorf("creating http storage: %w", err) | ||
} | ||
|
||
kp.storageMap[jwksUri] = storage | ||
return storage, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package okta | ||
|
||
import ( | ||
"context" | ||
"crypto/rand" | ||
"crypto/rsa" | ||
"github.com/golang-jwt/jwt/v5" | ||
"github.com/sovietaced/okta-jwt-verifier/jwt/okta/oktatest" | ||
"github.com/sovietaced/okta-jwt-verifier/metadata" | ||
"github.com/stretchr/testify/require" | ||
"testing" | ||
) | ||
|
||
func TestKeyfuncProvider(t *testing.T) { | ||
|
||
// Generate RSA key. | ||
pk, err := rsa.GenerateKey(rand.Reader, 2048) | ||
require.NoError(t, err) | ||
|
||
ctx := context.Background() | ||
|
||
t.Run("get keyfunc", func(t *testing.T) { | ||
uri := oktatest.ServeJwks(t, ctx, pk) | ||
|
||
mp := &oktatest.StaticMetadataProvider{ | ||
Md: metadata.Metadata{ | ||
JwksUri: uri, | ||
}, | ||
} | ||
|
||
kp := NewKeyfuncProvider(mp) | ||
|
||
keyfunc, err := kp.GetKeyfunc(ctx) | ||
require.NoError(t, err) | ||
validateKeyfunc(t, keyfunc, pk) | ||
}) | ||
|
||
t.Run("get keyfunc and verify cached", func(t *testing.T) { | ||
|
||
}) | ||
|
||
t.Run("get keyfunc and validate tracing", func(t *testing.T) { | ||
|
||
}) | ||
|
||
t.Run("get keyfunc and metadata provider errors", func(t *testing.T) { | ||
|
||
}) | ||
|
||
t.Run("get keyfunc and jwks uri is invalid", func(t *testing.T) { | ||
|
||
}) | ||
} | ||
|
||
func validateKeyfunc(t *testing.T, keyfunc jwt.Keyfunc, pk *rsa.PrivateKey) { | ||
token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{}) | ||
token.Header["kid"] = oktatest.KID | ||
tokenString, err := token.SignedString(pk) | ||
require.NoError(t, err) | ||
|
||
_, err = jwt.Parse(tokenString, keyfunc) | ||
require.NoError(t, err) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package oktatest | ||
|
||
import ( | ||
"context" | ||
"crypto/rsa" | ||
"github.com/MicahParks/jwkset" | ||
"github.com/sovietaced/okta-jwt-verifier/metadata" | ||
"github.com/stretchr/testify/require" | ||
"net/http" | ||
"net/http/httptest" | ||
"testing" | ||
) | ||
|
||
const ( | ||
KID = "test" | ||
) | ||
|
||
type StaticMetadataProvider struct { | ||
Md metadata.Metadata | ||
} | ||
|
||
func (smp *StaticMetadataProvider) GetMetadata(ctx context.Context) (metadata.Metadata, error) { | ||
return smp.Md, nil | ||
} | ||
|
||
func ServeJwks(t *testing.T, ctx context.Context, priv *rsa.PrivateKey) string { | ||
serverStore := jwkset.NewMemoryStorage() | ||
md := jwkset.JWKMetadataOptions{ | ||
KID: KID, | ||
} | ||
jwkOptions := jwkset.JWKOptions{ | ||
Metadata: md, | ||
} | ||
jwk, err := jwkset.NewJWKFromKey(priv, jwkOptions) | ||
require.NoError(t, err) | ||
|
||
err = serverStore.KeyWrite(ctx, jwk) | ||
require.NoError(t, err) | ||
|
||
rawJWKS, err := serverStore.JSONPrivate(ctx) | ||
require.NoError(t, err) | ||
|
||
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
w.Write(rawJWKS) | ||
})) | ||
t.Cleanup(svr.Close) | ||
|
||
return svr.URL | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.