mirror of
https://github.com/oauth2-proxy/oauth2-proxy.git
synced 2025-05-19 22:23:30 +02:00
fix(entra-id): use federated credentials for refresh token (#3031)
* fix: use federated credentials to refresh token in entra id * fix: add some error handling * chore: update changelog * chore: update comments * chore: update comments * doc: reference entra id docs and clearer phrasing of comments Signed-off-by: Jan Larwig <jan@larwig.com> --------- Signed-off-by: Jan Larwig <jan@larwig.com> Co-authored-by: Jan Larwig <jan@larwig.com>
This commit is contained in:
parent
3afae76103
commit
7d85c99d8e
@ -8,6 +8,7 @@
|
||||
|
||||
## Changes since v7.8.2
|
||||
|
||||
- [#3031](https://github.com/oauth2-proxy/oauth2-proxy/pull/3031) Fixes Refresh Token bug with Entra ID and Workload Identity (#3027)[https://github.com/oauth2-proxy/oauth2-proxy/issues/3028] by using client assertion when redeeming the token (@richard87)
|
||||
- [#3001](https://github.com/oauth2-proxy/oauth2-proxy/pull/3001) Allow to set non-default authorization request response mode (@stieler-it)
|
||||
- [#3041](https://github.com/oauth2-proxy/oauth2-proxy/pull/3041) chore(deps): upgrade to latest golang v1.23.x release (@TheImplementer)
|
||||
|
||||
|
@ -8,7 +8,9 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
||||
@ -114,7 +116,8 @@ func (p *MicrosoftEntraIDProvider) redeemWithFederatedToken(ctx context.Context,
|
||||
|
||||
params := url.Values{}
|
||||
|
||||
// create custom exchange parameters
|
||||
// Exchange parameters for token federation
|
||||
// https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-auth-code-flow#request-an-access-token-with-a-certificate-credential
|
||||
if codeVerifier != "" {
|
||||
params.Add("code_verifier", codeVerifier)
|
||||
}
|
||||
@ -125,29 +128,78 @@ func (p *MicrosoftEntraIDProvider) redeemWithFederatedToken(ctx context.Context,
|
||||
params.Add("code", code)
|
||||
params.Add("grant_type", "authorization_code")
|
||||
|
||||
// perform exchange
|
||||
resp := requests.New(p.RedeemURL.String()).
|
||||
WithContext(ctx).
|
||||
WithMethod("POST").
|
||||
WithBody(bytes.NewBufferString(params.Encode())).
|
||||
SetHeader("Content-Type", "application/x-www-form-urlencoded").
|
||||
Do()
|
||||
|
||||
// prepare token of type *oauth2.Token
|
||||
var token *oauth2.Token
|
||||
var rawResponse interface{}
|
||||
|
||||
body := resp.Body()
|
||||
if err := json.Unmarshal(body, &rawResponse); err != nil {
|
||||
return nil, err
|
||||
token, err := p.fetchToken(ctx, params)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error fetching token: %w", err)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(body, &token); err != nil {
|
||||
return nil, err
|
||||
return p.OIDCProvider.createSession(ctx, token, false)
|
||||
}
|
||||
|
||||
// RefreshSession uses the RefreshToken to fetch new Access and ID Tokens
|
||||
func (p *MicrosoftEntraIDProvider) RefreshSession(ctx context.Context, s *sessions.SessionState) (bool, error) {
|
||||
if s == nil || s.RefreshToken == "" {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// create session using new token and generic OIDC provider
|
||||
return p.OIDCProvider.createSession(ctx, token.WithExtra(rawResponse), false)
|
||||
var err error
|
||||
ctx = oidc.ClientContext(ctx, requests.DefaultHTTPClient)
|
||||
if p.federatedTokenAuth {
|
||||
err = p.redeemRefreshTokenWithFederatedToken(ctx, s)
|
||||
} else {
|
||||
err = p.redeemRefreshToken(ctx, s)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("unable to redeem refresh token: %v", err)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// redeemRefreshTokenWithFederatedToken uses a RefreshToken and federated credentials with the RedeemURL to refresh the
|
||||
// Refresh Token, Access Token and ID Token
|
||||
func (p *MicrosoftEntraIDProvider) redeemRefreshTokenWithFederatedToken(ctx context.Context, s *sessions.SessionState) error {
|
||||
federatedTokenPath := os.Getenv("AZURE_FEDERATED_TOKEN_FILE")
|
||||
federatedToken, err := os.ReadFile(federatedTokenPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading federated token file %s: %s", federatedTokenPath, err)
|
||||
}
|
||||
|
||||
params := url.Values{}
|
||||
params.Add("client_id", p.ClientID)
|
||||
params.Add("client_assertion", string(federatedToken))
|
||||
params.Add("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer")
|
||||
params.Add("refresh_token", s.RefreshToken)
|
||||
params.Add("grant_type", "refresh_token")
|
||||
params.Add("expiry", time.Now().Add(-time.Hour).Format(time.RFC3339))
|
||||
|
||||
token, err := p.fetchToken(ctx, params)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error fetching token: %w", err)
|
||||
}
|
||||
|
||||
newSession, err := p.OIDCProvider.createSession(ctx, token, true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable create new session state from response: %v", err)
|
||||
}
|
||||
|
||||
// Update the ID Token and user details if returned as part of the refresh response
|
||||
// ref. https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokenResponse
|
||||
if newSession.IDToken != "" {
|
||||
s.IDToken = newSession.IDToken
|
||||
s.Email = newSession.Email
|
||||
s.User = newSession.User
|
||||
s.Groups = newSession.Groups
|
||||
s.PreferredUsername = newSession.PreferredUsername
|
||||
}
|
||||
|
||||
s.AccessToken = newSession.AccessToken
|
||||
s.RefreshToken = newSession.RefreshToken
|
||||
s.CreatedAt = newSession.CreatedAt
|
||||
s.ExpiresOn = newSession.ExpiresOn
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkGroupOverage checks ID token's group membership claims for the group overage
|
||||
@ -245,3 +297,26 @@ func (p *MicrosoftEntraIDProvider) checkTenantMatchesTenantList(tenant string, a
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *MicrosoftEntraIDProvider) fetchToken(ctx context.Context, params url.Values) (*oauth2.Token, error) {
|
||||
resp := requests.New(p.RedeemURL.String()).
|
||||
WithContext(ctx).
|
||||
WithMethod("POST").
|
||||
WithBody(bytes.NewBufferString(params.Encode())).
|
||||
SetHeader("Content-Type", "application/x-www-form-urlencoded").
|
||||
Do()
|
||||
|
||||
var token *oauth2.Token
|
||||
var rawResponse interface{}
|
||||
|
||||
body := resp.Body()
|
||||
if err := json.Unmarshal(body, &rawResponse); err != nil {
|
||||
return nil, fmt.Errorf("unable to unmarshal raw response body: %w", err)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(body, &token); err != nil {
|
||||
return nil, fmt.Errorf("unable to unmarshal token response body: %w", err)
|
||||
}
|
||||
|
||||
return token.WithExtra(rawResponse), nil
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user