Problem
Developers attempting to call the OpenAI GPT‑4o API from a local development machine using SAML‑based Single Sign‑On receive the following error response:
{
"error": {
"code": "invalid_saml_assertion",
"message": "SAML assertion invalid"
}
}
The failure occurs during the Authorization: Bearer <SAML‑assertion> step, preventing any API request from succeeding.
Root Cause
The OpenAI API validates the SAML assertion against a strict set of criteria that differ from typical web‑SSO flows. In the local development setup the following conditions were violated:
- Clock skew: The assertion’s
NotBeforeandNotOnOrAftertimestamps were outside the 5‑minute tolerance because the developer workstation’s clock was unsynchronized. - Audience restriction: The
AudienceRestrictionelement contained the production SP entity ID (https://app.openai.com/sp) while the API expectshttps://api.openai.com/for token‑exchange calls. - Signature algorithm mismatch: The IdP signed the assertion with
RSA‑SHA1, but the API rejects SHA‑1 signatures and requires at leastRSA‑SHA256. - RelayState omission: The local SP omitted the
RelayStateparameter required by OpenAI’s token endpoint, causing the API to treat the assertion as malformed.
These issues collectively cause the API’s SAML validator to reject the assertion as “invalid”.
Debug
Below is a reproducible debugging workflow that isolates each failure point.
1. Capture the raw SAML assertion
# Assuming the local SP writes the assertion to a file for debugging
cat /tmp/saml_assertion.xml
2. Verify timestamps
xmlstarlet sel -t -v "/*[local-name()='Assertion']/*[local-name()='Conditions']/@NotBefore" /tmp/saml_assertion.xml
xmlstarlet sel -t -v "/*[local-name()='Assertion']/*[local-name()='Conditions']/@NotOnOrAfter" /tmp/saml_assertion.xml
Expected output (UTC):
2024-10-11T14:02:00Z
2024-10-11T14:07:00Z
If the values differ by more than 5 minutes from the local system time, the assertion will be rejected.
3. Check the audience restriction
xmlstarlet sel -t -v "/*[local-name()='Assertion']/*[local-name()='Conditions']/*[local-name()='AudienceRestriction']/*[local-name()='Audience']" /tmp/saml_assertion.xml
Correct value:
https://api.openai.com/
4. Validate the signature algorithm
xmlsec1 --verify --id-attr:ID Assertion /tmp/saml_assertion.xml
Sample error when SHA‑1 is used:
Signature verification failed: algorithm not allowed (RSA-SHA1)
5. Inspect the HTTP request sent to the API
curl -v -X POST https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $(cat /tmp/saml_assertion.xml | base64 -w0)" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-4o","messages":[{"role":"user","content":"Hello"}]}'
Note the -v flag; the response headers include the error payload shown in the “Problem” section.
6. Correlate with IdP logs
# Example IdP log snippet (Okta)
2024-10-11 14:01:58,123 INFO Assertion generated for user=jdoe, audience=https://api.openai.com/
2024-10-11 14:01:58,124 WARN Signature algorithm SHA1 is deprecated
Solution
Apply the following changes to the local SAML SP configuration and the workstation environment.
1. Synchronize the workstation clock
# Install and start NTP (Linux example)
sudo apt-get install -y ntp
sudo systemctl enable --now ntp
# Verify offset
ntpq -p
Offset should be <100 ms. If the workstation cannot reach external NTP servers, use chrony or manually set the time.
2. Update the SP metadata to request the correct audience
Before (incorrect audience):
<md:SPSSODescriptor ...>
<md:AssertionConsumerService .../>
<md:RequestedAttribute Name="audience" FriendlyName="Audience" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
<saml:AttributeValue>https://app.openai.com/sp</saml:AttributeValue>
</md:RequestedAttribute>
</md:SPSSODescriptor>
After (correct audience):
<md:SPSSODescriptor ...>
<md:AssertionConsumerService .../>
<md:RequestedAttribute Name="audience" FriendlyName="Audience" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
<saml:AttributeValue>https://api.openai.com/</saml:AttributeValue>
</md:RequestedAttribute>
</md:SPSSODescriptor>
3. Enforce RSA‑SHA256 (or stronger) signing on the IdP
In the IdP admin console (e.g., Okta, Azure AD) set the signing algorithm to RSA‑SHA256. Verify with a new assertion:
xmlsec1 --verify --id-attr:ID Assertion /tmp/saml_assertion.xml
Expected output:
Signature verification succeeded
4. Include the required RelayState parameter
Modify the token‑exchange request to embed the RelayState that matches the SP’s ACS URL.
# Example using curl with RelayState
curl -X POST https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $(cat /tmp/saml_assertion.xml | base64 -w0)" \
-H "Content-Type: application/json" \
-d '{
"model":"gpt-4o",
"messages":[{"role":"user","content":"Hello"}],
"relay_state":"http://localhost:3000/callback"
}'
5. Regenerate the assertion after all changes
Trigger a fresh SSO login (or use the IdP’s “Generate Assertion” utility) and repeat steps 1‑4 to confirm the corrected values.
Verify
Run the API call again and confirm a successful 200 response.
curl -s -X POST https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $(cat /tmp/saml_assertion.xml | base64 -w0)" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-4o","messages":[{"role":"user","content":"Hello"}]}' | jq .
Expected JSON (truncated):
{
"id": "chatcmpl-123",
"object": "chat.completion",
"created": 1728601234,
"model": "gpt-4o",
"choices": [
{
"index": 0,
"message": {"role":"assistant","content":"Hello! How can I help you today?"},
"finish_reason": "stop"
}
]
}
Additional verification steps:
- Check OpenAI API logs (if enabled) for a successful authentication event.
- Inspect the local SP logs for “Assertion validated – token issued”.
- Run
openssl x509 -noout -dates -in sp_cert.pemto ensure the SP certificate is still valid.
Prevent
Implement safeguards to avoid recurrence in future development cycles.
- Automated time sync: Enforce NTP on all developer workstations and CI runners.
- Configuration linting: Add a CI job that parses the SP metadata and asserts that the
AudienceRestrictionequalshttps://api.openai.com/. - Signature policy enforcement: Configure the IdP to reject any signing algorithm weaker than SHA‑256.
- Integration test: Write a test that generates a SAML assertion, validates timestamps, audience, and signature using
xmlsec1, then performs a real API call against a staging OpenAI endpoint. - Monitoring: Add an alert on OpenAI API error code
invalid_saml_assertionthat triggers a runbook to check clock drift and metadata.
Related Topic Hub: LLM Systems Troubleshooting Hub
FAQ
- Why does the same SAML configuration work in production but fail locally?
Production environments typically run NTP and have the correct ACS URL (
https://api.openai.com/) baked into the metadata. Local machines often have unsynced clocks and usehttp://localhostURLs, causing audience mismatches and timestamp violations. - Can I use a JWT instead of a SAML assertion for local development?
OpenAI currently accepts only SAML assertions for SSO‑based token exchange. A JWT can be used only with API keys, not with the SAML flow.
- What is the maximum allowed clock skew for the OpenAI SAML validator?
The validator allows a ±5‑minute window. Anything outside this range results in
invalid_saml_assertion. - How do I know which signature algorithm the API expects?
The OpenAI SAML validation service rejects SHA‑1 and MD5. Acceptable algorithms are RSA‑SHA256, RSA‑SHA384, RSA‑SHA512, and ECDSA‑SHA256+. The IdP’s metadata
SignatureMethodelement should reflect one of these. - Is the
RelayStateparameter mandatory for every request?Yes. The API uses
RelayStateto bind the assertion to the specific SP endpoint. Omitting it causes the assertion to be considered malformed.