From 2839ec7553a5df25e5ea6db062485af0b7cedc5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Reegn?= Date: Tue, 14 Oct 2025 12:43:32 +0200 Subject: [PATCH] fix(security): use proper uuid for device token Okta device tokens are supposed to be unguessable. They are meant to they help in not having to MFA constantly for devices, essentially a short-cirtuit to avoid needing MFA. So in sensitivity they are between your password and your MFA, therefore they are NOT meant to be precomputed from the username (as saml2aws does it today). If the device-token is easily guessable, then it defeats MFA, and users The current users of saml2aws live in a false sense of security when they allow remember-my-device (the default with saml2aws). With saml2aws using a predictable device token, it poses a security risk for any user using saml2aws, as it defeats using MFA. This fix aims at generating a UUID specific to the device saml2aws runs on and storing it as a secret (like storing the users password). This makes using 'remember device' safe. --- pkg/provider/okta/okta.go | 57 +++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 32 deletions(-) diff --git a/pkg/provider/okta/okta.go b/pkg/provider/okta/okta.go index 52690b2f3..51c276599 100644 --- a/pkg/provider/okta/okta.go +++ b/pkg/provider/okta/okta.go @@ -20,6 +20,7 @@ import ( "time" "github.com/PuerkitoBio/goquery" + "github.com/google/uuid" "github.com/marshallbrekka/go-u2fhost" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -337,42 +338,34 @@ func (oc *Client) authWithSession(loginDetails *creds.LoginDetails) (string, err return oc.follow(ctx, req, loginDetails) } -// getDeviceTokenFromOkta creates a dummy HTTP call to Okta and returns the device token -// cookie value -// This function is not currently used and but can be used in the future -func (oc *Client) getDeviceTokenFromOkta(loginDetails *creds.LoginDetails) (string, error) { - //dummy request to set device token cookie ("dt") - req, err := http.NewRequest("GET", loginDetails.URL, nil) - if err != nil { - return "", errors.Wrap(err, "error building device token request") - } - resp, err := oc.client.Do(req) - if err != nil { - return "", errors.Wrap(err, "error retrieving device token") - } +// getDeviceToken retrieves a stored device token or generates a new UUID. +// Device tokens are stored in the credential manager to allow reuse across sessions. +func (oc *Client) getDeviceToken(loginDetails *creds.LoginDetails) string { + deviceTokenKey := loginDetails.URL + "/deviceToken" - for _, c := range resp.Cookies() { - if c.Name == "DT" { // Device token - return c.Value, nil - } + // Try to retrieve stored device token + _, storedToken, err := credentials.CurrentHelper.Get(deviceTokenKey) + if err == nil && storedToken != "" { + logger.Debug("Using stored device token") + return storedToken } - return "", fmt.Errorf("unable to get a device token from okta") -} - -// setDeviceTokenCookie sets the DT cookie in the HTTP Client cookie jar -// using the okta__saml2aws, we reduce making an extra api call -// this func can be uplifted in the future to set custom device tokens or used with -// getDeviceTokenFromOkta function -func (oc *Client) setDeviceTokenCookie(loginDetails *creds.LoginDetails) error { + // Generate a new UUID device token (Okta recommends UUIDs) + logger.Debug("Generating new UUID device token") + deviceToken := uuid.New().String() - // getDeviceTokenFromOkta is not used but doing this to keep the function code - // uncommented (avoid linting issues) - if false { - dt, _ := oc.getDeviceTokenFromOkta(loginDetails) - logger.Debugf("getDeviceTokenFromOkta is not yet implemented: dt: %s", dt) + // Store for future use + if err := credentials.SaveCredentials(deviceTokenKey, loginDetails.Username, deviceToken); err != nil { + logger.Warnf("Failed to store device token: %v", err) + } else { + logger.Debug("Device token stored successfully") } + return deviceToken +} +// setDeviceTokenCookie sets the DT cookie in the HTTP Client cookie jar. +// The device token is either retrieved from storage or generated as a new UUID. +func (oc *Client) setDeviceTokenCookie(loginDetails *creds.LoginDetails) error { oktaURL, err := url.Parse(loginDetails.URL) if err != nil { return errors.Wrap(err, "error building oktaURL to set device token cookie") @@ -385,8 +378,8 @@ func (oc *Client) setDeviceTokenCookie(loginDetails *creds.LoginDetails) error { cookie := http.Cookie{ Name: "DT", Secure: true, - Expires: time.Now().Add(time.Hour * 24 * 30), // 30 Days -> this time might not matter as this cookie is set on every saml2aws login request - Value: fmt.Sprintf("okta_%s_saml2aws", loginDetails.Username), // Okta recommends using an UUID but this should be unique enough. Also, this is key to remembering Okta MFA device + Expires: time.Now().Add(time.Hour * 24 * 30), // 30 Days -> this time might not matter as this cookie is set on every saml2aws login request + Value: oc.getDeviceToken(loginDetails), } cookies = append(cookies, &cookie) oc.client.Jar.SetCookies(baseURL, cookies)