@@ -68,6 +68,7 @@ type Client interface {
6868 Discover (ctx context.Context , endpoint string ) (* OIDCConfiguration , error )
6969 Start (ctx context.Context , endpoint string , scopes []string ) (* DeviceAuthResponse , error )
7070 Poll (ctx context.Context , endpoint , deviceCode string , interval time.Duration , expiresIn int ) (* TokenResponse , error )
71+ Refresh (ctx context.Context , endpoint , refreshToken string ) (* TokenResponse , error )
7172}
7273
7374type httpClient struct {
@@ -307,3 +308,55 @@ func (c *httpClient) pollOnce(ctx context.Context, tokenEndpoint, deviceCode str
307308
308309 return & tokenResp , nil
309310}
311+
312+ // Refresh exchanges a refresh token for a new access token.
313+ func (c * httpClient ) Refresh (ctx context.Context , endpoint , refreshToken string ) (* TokenResponse , error ) {
314+ endpoint = strings .TrimRight (endpoint , "/" )
315+
316+ config , err := c .Discover (ctx , endpoint )
317+ if err != nil {
318+ return nil , errors .Wrap (err , "OIDC discovery failed" )
319+ }
320+
321+ if config .TokenEndpoint == "" {
322+ return nil , errors .New ("token endpoint not found in OIDC configuration" )
323+ }
324+
325+ data := url.Values {}
326+ data .Set ("client_id" , c .clientID )
327+ data .Set ("grant_type" , "refresh_token" )
328+ data .Set ("refresh_token" , refreshToken )
329+
330+ req , err := http .NewRequestWithContext (ctx , "POST" , config .TokenEndpoint , strings .NewReader (data .Encode ()))
331+ if err != nil {
332+ return nil , errors .Wrap (err , "creating refresh token request" )
333+ }
334+ req .Header .Set ("Content-Type" , "application/x-www-form-urlencoded" )
335+ req .Header .Set ("Accept" , "application/json" )
336+
337+ resp , err := c .client .Do (req )
338+ if err != nil {
339+ return nil , errors .Wrap (err , "refresh token request failed" )
340+ }
341+ defer resp .Body .Close ()
342+
343+ body , err := io .ReadAll (resp .Body )
344+ if err != nil {
345+ return nil , errors .Wrap (err , "reading refresh token response" )
346+ }
347+
348+ if resp .StatusCode != http .StatusOK {
349+ var errResp ErrorResponse
350+ if err := json .Unmarshal (body , & errResp ); err == nil && errResp .Error != "" {
351+ return nil , errors .Newf ("refresh token failed: %s: %s" , errResp .Error , errResp .ErrorDescription )
352+ }
353+ return nil , errors .Newf ("refresh token failed with status %d: %s" , resp .StatusCode , string (body ))
354+ }
355+
356+ var tokenResp TokenResponse
357+ if err := json .Unmarshal (body , & tokenResp ); err != nil {
358+ return nil , errors .Wrap (err , "parsing refresh token response" )
359+ }
360+
361+ return & tokenResp , nil
362+ }
0 commit comments