From 16ec8fc422f3a10392ee5d53ef7815240be7b4ec Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Mon, 18 Mar 2024 11:05:13 -0600 Subject: [PATCH] Added refresh token flow --- internal/config.go | 2 ++ internal/flows/jwt_bearer.go | 15 ++++++++++++++- internal/oauth/client.go | 16 ++++++++++++++++ internal/server/server.go | 25 ++++++++++++++++++++++++- 4 files changed, 56 insertions(+), 2 deletions(-) diff --git a/internal/config.go b/internal/config.go index 7179f01..bc61e96 100644 --- a/internal/config.go +++ b/internal/config.go @@ -24,6 +24,7 @@ type Options struct { CachePath string `yaml:"cache"` CacheOnly bool `yaml:"cache-only"` TokenForwarding bool `yaml:"token-forwarding"` + Refresh bool `yaml:"refresh"` Verbose bool `yaml:"verbose"` } @@ -72,6 +73,7 @@ func NewConfig() Config { FlowType: "authorization_code", CacheOnly: false, TokenForwarding: false, + Refresh: true, Verbose: false, }, Authentication: Authentication{ diff --git a/internal/flows/jwt_bearer.go b/internal/flows/jwt_bearer.go index 4b0f085..59e60ef 100644 --- a/internal/flows/jwt_bearer.go +++ b/internal/flows/jwt_bearer.go @@ -24,6 +24,7 @@ type JwtBearerFlowParams struct { IdentityProvider *oidc.IdentityProvider TrustedIssuer *oauth.TrustedIssuer Client *oauth.Client + Refresh bool Verbose bool KeyPath string } @@ -97,14 +98,19 @@ func NewJwtBearerFlow(eps JwtBearerEndpoints, params JwtBearerFlowParams) (strin } } + // add more required claims and validate publicJwk.Set("kid", uuid.New().String()) publicJwk.Set("use", "sig") - if err := publicJwk.Validate(); err != nil { return "", fmt.Errorf("failed to validate public JWK: %v", err) } trustedIssuer.PublicKey = publicJwk + // add offline_access scope to enable refresh tokens + if params.Refresh { + trustedIssuer.Scope = append(trustedIssuer.Scope, "offline_access") + } + // 3.b ...and then, add opaal's server host as a trusted issuer with JWK if verbose { fmt.Printf("Attempting to add issuer to authorization server...\n") @@ -131,6 +137,13 @@ func NewJwtBearerFlow(eps JwtBearerEndpoints, params JwtBearerFlowParams) (strin payload["nbf"] = time.Now().Unix() payload["exp"] = time.Now().Add(time.Second * 3600).Unix() payload["sub"] = "opaal" + + // include the offline_access scope if refresh tokens are enabled + if params.Refresh { + scope := payload["scp"].([]string) + scope = append(scope, "offline_access") + payload["scp"] = scope + } payloadJson, err := json.Marshal(payload) if err != nil { return "", fmt.Errorf("failed to marshal payload: %v", err) diff --git a/internal/oauth/client.go b/internal/oauth/client.go index b9cca21..216764b 100644 --- a/internal/oauth/client.go +++ b/internal/oauth/client.go @@ -211,6 +211,10 @@ func (client *Client) PerformTokenGrant(clientUrl string, encodedJwt string) ([] } _, b, err := httpx.MakeHttpRequest(clientUrl, http.MethodPost, []byte(body), headers) + if err != nil { + return nil, fmt.Errorf("failed to make HTTP request: %v", err) + + } // set flow ID back to empty string to indicate a completed flow client.FlowId = "" @@ -218,6 +222,18 @@ func (client *Client) PerformTokenGrant(clientUrl string, encodedJwt string) ([] return b, err } +func (client *Client) PerformRefreshTokenGrant(url string, refreshToken string) ([]byte, error) { + body := httpx.Body("grant_type=refresh_token" + + "&refresh_token=" + refreshToken + + "&scope" + strings.Join(client.Scope, "+")) + headers := httpx.Headers{} + _, b, err := httpx.MakeHttpRequest(url, http.MethodPost, body, headers) + if err != nil { + return nil, fmt.Errorf("failed to make HTTP request: %v", err) + } + return b, err +} + func (client *Client) DeleteOAuthClient(clientUrl string) error { _, _, err := httpx.MakeHttpRequest(clientUrl+"/"+client.Id, http.MethodDelete, nil, nil) if err != nil { diff --git a/internal/server/server.go b/internal/server/server.go index 2d8bb3e..6ebfee8 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -66,6 +66,29 @@ func (s *Server) Login(buttons string, provider *oidc.IdentityProvider, client * panic(err) } }) + r.HandleFunc("/key", func(w http.ResponseWriter, r *http.Request) { + + }) + r.HandleFunc("/refresh", func(w http.ResponseWriter, r *http.Request) { + // use refresh token provided to do a refresh token grant + refreshToken := r.URL.Query().Get("refresh-token") + _, err := client.PerformRefreshTokenGrant(provider.Endpoints.Token, refreshToken) + if err != nil { + fmt.Printf("failed to perform refresh token grant: %v\n", err) + http.Redirect(w, r, "/error", http.StatusInternalServerError) + return + } + + // return token to target if set or the sending client + returnTarget := r.URL.Query().Get("target") + if returnTarget != "" { + + } else { + host := r.URL.Host + httpx.MakeHttpRequest(host, http.MethodPost, httpx.Body{}, httpx.Headers{}) + + } + }) r.HandleFunc(s.Callback, func(w http.ResponseWriter, r *http.Request) { // get the code from the OIDC provider if r != nil { @@ -104,7 +127,7 @@ func (s *Server) Login(buttons string, provider *oidc.IdentityProvider, client * return } - // extract scopes from ID token and add to trusted issuer + // TODO: extract scopes from ID token and add to trusted issuer // complete JWT bearer flow to receive access token from authorization server // fmt.Printf("bearer: %v\n", string(bearerToken))