OpenAI GPT-4o API SAML assertion invalid in local development

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 NotBefore and NotOnOrAfter timestamps were outside the 5‑minute tolerance because the developer workstation’s clock was unsynchronized.
  • Audience restriction: The AudienceRestriction element contained the production SP entity ID (https://app.openai.com/sp) while the API expects https://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 least RSA‑SHA256.
  • RelayState omission: The local SP omitted the RelayState parameter 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.pem to 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 AudienceRestriction equals https://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_assertion that triggers a runbook to check clock drift and metadata.

Related Topic Hub: LLM Systems Troubleshooting Hub

FAQ

  1. 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 use http://localhost URLs, causing audience mismatches and timestamp violations.

  2. 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.

  3. 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.

  4. 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 SignatureMethod element should reflect one of these.

  5. Is the RelayState parameter mandatory for every request?

    Yes. The API uses RelayState to bind the assertion to the specific SP endpoint. Omitting it causes the assertion to be considered malformed.