Log truncated hash of refresh token param for refresh_token OAuth flow
What does this MR do and why?
- For all
POST /oauth/tokenrequests for therefresh_tokengrant type, when arefresh_tokenparam is present, log a truncated hash of its value. - Only log the first 10 characters of the computed hash .
- This supports our use case while keeping info logged to a minimum.
- I'm appending
refresh_token_hashto thepayload.metadatakey, but this gets flattened by middleware and becomes a top-level log attribute.- I'm appending to
metadatato be consistent with the existing log augmentation going on in this controller.
- I'm appending to
Rollout
I'm being ultra cautious and putting this behind a feature flag. The reason is the /oauth/token endpoint controls all OAuth token creation so downtime or anything unexpected could have severe negative impact. The code is not complicated but there is an external system involved (logging).
I think this is defensible given the published "when to use a FF" criteria but if there are strong opinions against, I'm interested in hearing them.
References
Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/523854
How to set up and validate locally
- Visit local GDK and create an OAuth application
https://<gdk_url>/-/user_settings/applications - Note the application ID, secret, and redirect URI.
First, we'll validate that the flow does not log refresh_token_hash when the feature flag is disabled (the default).
Step 1: Set Environment Variables
Set up your OAuth application credentials:
export CLIENT_ID="your_application_id_here"
export CLIENT_SECRET="your_application_secret_here"
export REDIRECT_URI="your_redirect_uri_here"
export GITLAB_URL="your_gdk_url_here"
Step 2: Get Authorization Code
-
Visit the authorization URL in your browser:
echo "Visit: ${GITLAB_URL}/oauth/authorize?client_id=${CLIENT_ID}&redirect_uri=${REDIRECT_URI}&response_type=code&scope=api+read_user" -
Authorize the application when prompted in the browser
-
Copy the authorization code from the redirect URL parameter (e.g.,
?code=abc123...) -
Set the authorization code:
export AUTH_CODE="your_authorization_code_here"
Step 3: Exchange Authorization Code for Tokens
Make a cURL request to get the initial access and refresh tokens:
curl -X POST "${GITLAB_URL}/oauth/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=authorization_code" \
--data-urlencode "client_id=${CLIENT_ID}" \
--data-urlencode "client_secret=${CLIENT_SECRET}" \
--data-urlencode "redirect_uri=${REDIRECT_URI}" \
--data-urlencode "code=${AUTH_CODE}" \
| jq '.'
Expected Response:
{
"access_token": "abc123...",
"refresh_token": "def456...",
"token_type": "Bearer",
"expires_in": 7200
}
-
Extract and save the refresh token:
export REFRESH_TOKEN="your_refresh_token_from_response"
Step 4: Calculate Expected Hash
Calculate the expected SHA256 hash of your refresh token (first 10 characters):
# Calculate full hash
FULL_HASH=$(echo -n "$REFRESH_TOKEN" | shasum -a 256 | cut -d' ' -f1)
EXPECTED_HASH_PREFIX=${FULL_HASH:0:10}
echo "Full refresh token: $REFRESH_TOKEN"
echo "Full SHA256 hash: $FULL_HASH"
echo "Expected hash prefix (first 10 chars): $EXPECTED_HASH_PREFIX"
Step 5: Start Log Monitoring
In a separate terminal, start monitoring the GitLab logs for refresh token hash entries:
# Monitor logs for refresh token hash entries
tail -f log/development_json.log | grep refresh_token_hash
Step 6: Make Refresh Token Request
Make a cURL request to refresh the access token:
curl -X POST "${GITLAB_URL}/oauth/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=refresh_token" \
--data-urlencode "client_id=${CLIENT_ID}" \
--data-urlencode "client_secret=${CLIENT_SECRET}" \
--data-urlencode "redirect_uri=${REDIRECT_URI}" \
--data-urlencode "refresh_token=${REFRESH_TOKEN}" \
| jq '.'
Expected Response:
{
"access_token": "new_access_token...",
"refresh_token": "new_refresh_token...",
"token_type": "Bearer",
"expires_in": 7200
}
Step 7: Verify no log entry
- Check log monitoring terminal and validate a log was produced, but with no refresh_token_hash entry.
Step 8: Enable feature flag
- Get a Rails console and enable the feature flag
gdk rails c
> Feature.enable(:log_refresh_token_hash)
> true
Step 9: Repeat the refresh_token request
- Reset the
REFRESH_TOKENenvironment variable according to the most recent response from the server:
export REFRESH_TOKEN=<refresh_token_from_last_response>
-
Repeat Step 6 and make another
refresh_tokenrequest.
Step 10: Verify Logging
-
Check the log monitoring terminal for entries containing
refresh_token_hash -
Expected log entry format:
{"time":"2024-01-01T12:00:00.000Z","severity":"INFO","message":"OAuth token refresh","refresh_token_hash":"abc1234567"} -
Observe there is a
refresh_token_hashkey logged and the value is a truncated SHA256 hash as expected.
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.
Related to #523854