Added refresh token flow

This commit is contained in:
David Allen 2024-03-18 11:05:13 -06:00
parent 6938037a3c
commit 16ec8fc422
No known key found for this signature in database
GPG key ID: 1D2A29322FBB6FCB
4 changed files with 56 additions and 2 deletions

View file

@ -24,6 +24,7 @@ type Options struct {
CachePath string `yaml:"cache"` CachePath string `yaml:"cache"`
CacheOnly bool `yaml:"cache-only"` CacheOnly bool `yaml:"cache-only"`
TokenForwarding bool `yaml:"token-forwarding"` TokenForwarding bool `yaml:"token-forwarding"`
Refresh bool `yaml:"refresh"`
Verbose bool `yaml:"verbose"` Verbose bool `yaml:"verbose"`
} }
@ -72,6 +73,7 @@ func NewConfig() Config {
FlowType: "authorization_code", FlowType: "authorization_code",
CacheOnly: false, CacheOnly: false,
TokenForwarding: false, TokenForwarding: false,
Refresh: true,
Verbose: false, Verbose: false,
}, },
Authentication: Authentication{ Authentication: Authentication{

View file

@ -24,6 +24,7 @@ type JwtBearerFlowParams struct {
IdentityProvider *oidc.IdentityProvider IdentityProvider *oidc.IdentityProvider
TrustedIssuer *oauth.TrustedIssuer TrustedIssuer *oauth.TrustedIssuer
Client *oauth.Client Client *oauth.Client
Refresh bool
Verbose bool Verbose bool
KeyPath string 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("kid", uuid.New().String())
publicJwk.Set("use", "sig") publicJwk.Set("use", "sig")
if err := publicJwk.Validate(); err != nil { if err := publicJwk.Validate(); err != nil {
return "", fmt.Errorf("failed to validate public JWK: %v", err) return "", fmt.Errorf("failed to validate public JWK: %v", err)
} }
trustedIssuer.PublicKey = publicJwk 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 // 3.b ...and then, add opaal's server host as a trusted issuer with JWK
if verbose { if verbose {
fmt.Printf("Attempting to add issuer to authorization server...\n") 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["nbf"] = time.Now().Unix()
payload["exp"] = time.Now().Add(time.Second * 3600).Unix() payload["exp"] = time.Now().Add(time.Second * 3600).Unix()
payload["sub"] = "opaal" 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) payloadJson, err := json.Marshal(payload)
if err != nil { if err != nil {
return "", fmt.Errorf("failed to marshal payload: %v", err) return "", fmt.Errorf("failed to marshal payload: %v", err)

View file

@ -211,6 +211,10 @@ func (client *Client) PerformTokenGrant(clientUrl string, encodedJwt string) ([]
} }
_, b, err := httpx.MakeHttpRequest(clientUrl, http.MethodPost, []byte(body), headers) _, 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 // set flow ID back to empty string to indicate a completed flow
client.FlowId = "" client.FlowId = ""
@ -218,6 +222,18 @@ func (client *Client) PerformTokenGrant(clientUrl string, encodedJwt string) ([]
return b, err 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 { func (client *Client) DeleteOAuthClient(clientUrl string) error {
_, _, err := httpx.MakeHttpRequest(clientUrl+"/"+client.Id, http.MethodDelete, nil, nil) _, _, err := httpx.MakeHttpRequest(clientUrl+"/"+client.Id, http.MethodDelete, nil, nil)
if err != nil { if err != nil {

View file

@ -66,6 +66,29 @@ func (s *Server) Login(buttons string, provider *oidc.IdentityProvider, client *
panic(err) 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) { r.HandleFunc(s.Callback, func(w http.ResponseWriter, r *http.Request) {
// get the code from the OIDC provider // get the code from the OIDC provider
if r != nil { if r != nil {
@ -104,7 +127,7 @@ func (s *Server) Login(buttons string, provider *oidc.IdentityProvider, client *
return 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 // complete JWT bearer flow to receive access token from authorization server
// fmt.Printf("bearer: %v\n", string(bearerToken)) // fmt.Printf("bearer: %v\n", string(bearerToken))