Reworked flow to consume ID token and to use own JWT

This commit is contained in:
David Allen 2024-03-05 20:49:26 -07:00
parent dc195afc61
commit d910e98f72
No known key found for this signature in database
GPG key ID: 1D2A29322FBB6FCB

View file

@ -1,6 +1,8 @@
package opaal
import (
"crypto/rand"
"crypto/rsa"
"davidallendj/opaal/internal/oidc"
"encoding/json"
"errors"
@ -10,6 +12,11 @@ import (
"time"
"github.com/davidallendj/go-utils/util"
"github.com/google/uuid"
"github.com/lestrrat-go/jwx/v2/jwa"
"github.com/lestrrat-go/jwx/v2/jwk"
"github.com/lestrrat-go/jwx/v2/jws"
"github.com/lestrrat-go/jwx/v2/jwt"
)
// TODO: change authorization code flow to use these instead
@ -123,6 +130,131 @@ func AuthorizationCodeWithConfig(config *Config, server *Server, client *Client,
fmt.Println()
}
if !config.Options.ForwardToken {
// TODO: implement our own JWT to send to Hydra
// 1. verify that the JWT from the issuer is valid
key, ok := idp.Jwks.Key(0)
if !ok {
return fmt.Errorf("no key found in key set")
}
parsedIdToken, err := jwt.ParseString(idToken, jwt.WithKey(jwa.RS256, key))
if err != nil {
return fmt.Errorf("failed to parse ID token: %v", err)
}
_, err = jwt.ParseString(accessToken, jwt.WithKeySet(idp.Jwks))
if err != nil {
return fmt.Errorf("failed to parse access token: %v", err)
}
_, err = jws.Verify([]byte(idToken), jws.WithKeySet(idp.Jwks), jws.WithValidateKey(true))
if err != nil {
return fmt.Errorf("failed to verify JWT: %v", err)
}
// 2. create a new JWKS (or just JWK) to be verified
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return fmt.Errorf("failed to generate private RSA k-ey: %v", err)
}
privateJwk, err := jwk.FromRaw(privateKey)
if err != nil {
return fmt.Errorf("failed to create private JWK: %v", err)
}
publicJwk, err := jwk.PublicKeyOf(privateJwk)
if err != nil {
return fmt.Errorf("failed to create public JWK: %v", err)
}
publicJwk.Set("kid", uuid.New().String())
// 3. add opaal's server host as a trusted issuer with JWK
fmt.Printf("Attempting to add issuer to authorization server...\n")
res, err := client.AddTrustedIssuer(
config.Authorization.RequestUrls.TrustedIssuers,
server.Addr,
publicJwk,
"1",
time.Second*3600,
)
if err != nil {
return fmt.Errorf("failed to add trusted issuer: %v", err)
}
fmt.Printf("%v\n", string(res))
// 4. create a new JWT based on the claims from the identity provider and sign
payload := parsedIdToken.PrivateClaims()
payload["iss"] = server.Addr
payload["aud"] = []string{config.Authorization.RequestUrls.Token}
payload["iat"] = time.Now().Unix()
payload["nbf"] = time.Now().Unix()
payload["exp"] = time.Now().Add(time.Second * 3600).Unix()
payload["sub"] = "1"
payloadJson, err := json.Marshal(payload)
if err != nil {
return fmt.Errorf("failed to marshal payload: %v", err)
}
newToken, err := jws.Sign(payloadJson, jws.WithJSON(), jws.WithKey(jwa.RS256, privateJwk))
if err != nil {
return fmt.Errorf("failed to sign token: %v", err)
}
// sig = rsasha256(b64urlencode(header) + "." + b64urlencode(payload))
// signature := util.EncodeBase64() + util.EncodeBase64() +
// 5. dynamically register new OAuth client and authorize it
fmt.Printf("Registering new OAuth2 client with authorization server...\n")
res, err = client.RegisterOAuthClient(config.Authorization.RequestUrls.Register, []string{})
if err != nil {
return fmt.Errorf("failed to register client: %v", err)
}
fmt.Printf("%v\n", string(res))
// extract the client info from response
var clientData map[string]any
err = json.Unmarshal(res, &clientData)
if err != nil {
return fmt.Errorf("failed to unmarshal client data: %v", err)
} else {
// check for error first
errJson := clientData["error"]
if errJson == nil {
client.Id = clientData["client_id"].(string)
client.Secret = clientData["client_secret"].(string)
} else {
// delete client and create again
fmt.Printf("Attempting to delete client...\n")
err := client.DeleteOAuthClient(config.Authorization.RequestUrls.Clients)
if err != nil {
return fmt.Errorf("failed to delete OAuth client: %v", err)
}
fmt.Printf("Attempting to re-create client...\n")
res, err := client.CreateOAuthClient(config.Authorization.RequestUrls.Clients, []string{})
if err != nil {
return fmt.Errorf("failed to register client: %v", err)
}
fmt.Printf("%v\n", string(res))
}
}
// authorize the client
// fmt.Printf("Attempting to authorize client...\n")
// res, err = client.AuthorizeOAuthClient(config.Authorization.RequestUrls.Authorize)
// if err != nil {
// return fmt.Errorf("failed to authorize client: %v", err)
// }
// fmt.Printf("%v\n", string(res))
// 6. send JWT to authorization server and receive a access token
if config.Authorization.RequestUrls.Token != "" {
fmt.Printf("Fetching access token from authorization server...\n")
res, err := client.PerformTokenGrant(config.Authorization.RequestUrls.Token, string(newToken))
if err != nil {
return fmt.Errorf("failed to fetch access token: %v", err)
}
fmt.Printf("%s\n", res)
}
} else {
// extract the scope from access token claims
// var scope []string
// var accessJsonPayload map[string]any
@ -173,13 +305,13 @@ func AuthorizationCodeWithConfig(config *Config, server *Server, client *Client,
// fetch JWKS and add issuer to authentication server to submit ID token
fmt.Printf("Fetching JWKS from authentication server for verification...\n")
err = idp.FetchJwk()
err = idp.FetchJwks()
if err != nil {
return fmt.Errorf("failed to fetch JWK: %v", err)
} else {
fmt.Printf("Successfully retrieved JWK from authentication server.\n\n")
fmt.Printf("Attempting to add issuer to authorization server...\n")
res, err := client.AddTrustedIssuer(
res, err := client.AddTrustedIssuerWithIdentityProvider(
config.Authorization.RequestUrls.TrustedIssuers,
idp,
subject,
@ -247,6 +379,7 @@ func AuthorizationCodeWithConfig(config *Config, server *Server, client *Client,
}
fmt.Printf("%s\n", res)
}
}
var access_token []byte
d <- access_token
return nil