Comments (8)
More than willing. I'll put together a rough example. I believe it requires an alteration to multiple interface implementations (i.e. the implementations themselves, not the signature).
from fosite.
I agree with the pain from not being able to figure out this easily. For reference I made my own Signer implementation which looks at the kid
and alg
header values and uses that to lookup the key to use.
from fosite.
Oh, so you expose kid
and alg
through GetJWTHeader
of your session struct based on client configuration? That is a nice trick and it seems better than my proposed API above. Can you share your implementation? Maybe we should add this into DefaultSigner
implementation in fosite itself?
from fosite.
I've tried to include as much context as possible. Some elements are domain logic and where possible have been excluded. It should be easy enough to extrapolate the required information if not I can clarify upon request. It should be noted that how I utilize this is I just set the kid, and the client when initialized determines what the kid should be based on the admins config of either the alg or the explicit kid.
For this to work I believe you must set the kid
or alg
header when generating the openid.Session before NewAuthorizeResponse
:
session := &openid.DefaultSession{
Headers: &jwt.Headers{
Extra: map[string]any{
"kid": "abc123,
"alg": "ES512",
},
},
}
Signer Implementation example (not intended to be modified at runtime but could be relatively easily):
// The DynamicSigner type handles JWKs and signing operations.
type DynamicSigner struct {
alg2kid map[string]string
kids map[string]*JWK
algs map[string]*JWK
}
// GetByHeader returns the JWK a JWT header with the appropriate kid value or returns an error.
func (s *DynamicSigner) GetByHeader(ctx context.Context, header fjwt.Mapper) (jwk *JWK, err error) {
var (
kid, alg string
ok bool
)
if header == nil {
return nil, fmt.Errorf("jwt header was nil")
}
kid, _ = header.Get(JWTHeaderKeyIdentifier).(string)
alg, _ = header.Get(JWTHeaderKeyAlgorithm).(string)
if len(kid) != 0 {
if jwk, ok = s.kids[kid]; ok {
return jwk, nil
}
return nil, fmt.Errorf("jwt header '%s' with value '%s' does not match a managed jwk", JWTHeaderKeyIdentifier, kid)
}
if len(alg) != 0 {
if jwk, ok = s.algs[alg]; ok {
return jwk, nil
}
return nil, fmt.Errorf("jwt header '%s' with value '%s' does not match a managed jwk", JWTHeaderKeyAlgorithm, alg)
}
return nil, fmt.Errorf("jwt header did not match a known jwk")
}
// GetByTokenString does an invalidated decode of a token to get the header, then calls GetByHeader.
func (s *DynamicSigner) GetByTokenString(ctx context.Context, tokenString string) (jwk *JWK, err error) {
var (
token *jwt.Token
)
if token, _, err = jwt.NewParser().ParseUnverified(tokenString, jwt.MapClaims{}); err != nil {
return nil, err
}
return s.GetByHeader(ctx, &fjwt.Headers{Extra: token.Header})
}
// GetByKID returns the JWK given an key id or nil if it doesn't exist. If given a blank string it returns the default.
func (s *DynamicSigner) GetByKID(ctx context.Context, kid string) *JWK {
if kid == "" {
return s.kids[s.alg2kid[SigningAlgRSAUsingSHA256]]
}
if jwk, ok := s.kids[kid]; ok {
return jwk
}
return nil
}
// Generate implements the fosite jwt.Signer interface and automatically maps the underlying keys based on the JWK Header kid.
func (s *DynamicSigner) Generate(ctx context.Context, claims fjwt.MapClaims, header fjwt.Mapper) (tokenString string, sig string, err error) {
var jwk *JWK
if jwk, err = s.GetByHeader(ctx, header); err != nil {
return "", "", fmt.Errorf("error getting jwk from header: %w", err)
}
extra := header.ToMap()
extra[JWTHeaderKeyIdentifier] = jwk.KeyID()
extra[JWTHeaderKeyAlgorithm] = jwk.Algorithm()
return jwk.Strategy().Generate(ctx, claims, &fjwt.Headers{Extra: extra})
}
// Validate implements the fosite jwt.Signer interface and automatically maps the underlying keys based on the JWK Header kid.
func (s *DynamicSigner) Validate(ctx context.Context, tokenString string) (sig string, err error) {
var jwk *JWK
if jwk, err = s.GetByTokenString(ctx, tokenString); err != nil {
return "", fmt.Errorf("error getting jwk from token string: %w", err)
}
return jwk.Strategy().Validate(ctx, tokenString)
}
// Hash implements the fosite jwt.Signer interface.
func (s *DynamicSigner) Hash(ctx context.Context, in []byte) (sum []byte, err error) {
return s.GetByKID(ctx, "").Strategy().Hash(ctx, in)
}
// Decode implements the fosite jwt.Signer interface and automatically maps the underlying keys based on the JWK Header kid.
func (s *DynamicSigner) Decode(ctx context.Context, tokenString string) (token *fjwt.Token, err error) {
var jwk *JWK
if jwk, err = s.GetByTokenString(ctx, tokenString); err != nil {
return nil, fmt.Errorf("error getting jwk from token string: %w", err)
}
return jwk.Strategy().Decode(ctx, tokenString)
}
// GetSignature implements the fosite jwt.Signer interface.
func (s *DynamicSigner) GetSignature(ctx context.Context, tokenString string) (sig string, err error) {
return getTokenSignature(tokenString)
}
// GetSigningMethodLength implements the fosite jwt.Signer interface.
func (s *DynamicSigner) GetSigningMethodLength(ctx context.Context) (size int) {
return s.GetByKID(ctx, "").Strategy().GetSigningMethodLength(ctx)
}
// JWK is a representation layer over the *jose.JSONWebKey for convenience.
type JWK struct {
kid string
use string
alg jwt.SigningMethod
hash crypto.Hash
key schema.CryptographicPrivateKey
chain schema.X509CertificateChain
thumbprintsha1 []byte
thumbprint []byte
}
// GetSigningMethod returns the jwt.SigningMethod for this *JWK.
func (j *JWK) GetSigningMethod() jwt.SigningMethod {
return j.alg
}
// GetPrivateKey returns the Private Key for this *JWK.
func (j *JWK) GetPrivateKey(ctx context.Context) (any, error) {
return j.PrivateJWK(), nil
}
// KeyID returns the Key ID for this *JWK.
func (j *JWK) KeyID() string {
return j.kid
}
// Algorithm returns the Algorithm for this *JWK.
func (j *JWK) Algorithm() string {
return j.alg.Alg()
}
// DirectJWK directly returns the *JWK as a jose.JSONWebKey with the private key if appropriate.
func (j *JWK) DirectJWK() (jwk jose.JSONWebKey) {
return jose.JSONWebKey{
Key: j.key,
KeyID: j.kid,
Algorithm: j.alg.Alg(),
Use: j.use,
Certificates: j.chain.Certificates(),
CertificateThumbprintSHA1: j.thumbprintsha1,
CertificateThumbprintSHA256: j.thumbprint,
}
}
// PrivateJWK directly returns the *JWK as a *jose.JSONWebKey with the private key if appropriate.
func (j *JWK) PrivateJWK() (jwk *jose.JSONWebKey) {
value := j.DirectJWK()
return &value
}
// JWK directly returns the *JWK as a jose.JSONWebKey specifically without the private key.
func (j *JWK) JWK() (jwk jose.JSONWebKey) {
if jwk = j.DirectJWK(); jwk.IsPublic() {
return jwk
}
return jwk.Public()
}
// Strategy returns the fosite jwt.Signer.
func (j *JWK) Strategy() (strategy fjwt.Signer) {
return &Signer{
hash: j.hash,
alg: j.alg,
GetPrivateKey: j.GetPrivateKey,
}
}
from fosite.
I see. Thanks. I think this could be probably made generic as part of default signer.
from fosite.
If it could be added in a way which prevents breaking changes then I'd agree. My guess is that it may not be so simple. Not that a breaking change is a problem, but it may not be necessary in which case maybe adding a new optional implementation within fosite itself would be more appropriate.
Also not entirely sure the JWK struct is needed, I just made that struct to handle some domain logic I think.
But I would be more than happy for a direct copy paste of anything under the respective fosite license.
from fosite.
My guess is that it may not be so simple.
Why not? If there are headers set, use that key, otherwise use default. It could work I think. But of course, we will see once somebody works on this for real. :-)
I will wait for now on others (e.g., @aeneasr) to confirm that this is something to be added to fosite.
from fosite.
The way I implemented it assumes you will iteratively add the keys to the struct itself. The DefaultSigner
if memory serves me correct takes a delegate which doesn't have adequate functionality to do what is needed.
from fosite.
Related Issues (20)
- Failing to fetch a PKCE request session fails requests even when PKCE is not enforced HOT 3
- redirect_uri matching does not follow RFC3986 HOT 2
- Changelog is out of sync
- During refresh token grant if token is invalid or expired return status code should be 400 HOT 3
- Allow revoking access token without revoking refresh token HOT 2
- authorize_helper.isLoopbackAddress has flaws HOT 1
- clientCredentialsFromRequest should not expect Basic Authorization terms being URL Escaped HOT 2
- Refresh token flow handler does not set the original request ID in the handler early enough
- use mattn/go-sqlite3 v2.0.3+incompatible no the new version HOT 6
- Failed to decode `id_token_hint` when using different signer for `id_token` and others
- `iat` field in access token (JWT) issued as part of `refresh_token` grant. HOT 8
- Concurrent requests for token endpoint on auth-code flow with same code succeed. HOT 8
- Can not run the example code
- OIDC callback is always HTTPS, even when entered as HTTP HOT 1
- DefaultSigner should support key rotation
- Make prefix used in HMACSHAStrategy configurable
- openid session storage should be deleted when the authcode is exchanged HOT 9
- private_key_jwt assetion tokens can have unbounded expiration which can fill data store HOT 3
- NewDefaultSession's SetSubject should set IDTokenClaims as well
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from fosite.