Solving "E0000003: The request body was not well-formed" working with Okta API

I'm writing a microservice that needs to be able to programmatically create Okta App objects via Python. We have a working implementation using Terraform, but for a variety of reasons the Terraform approach isn't sufficient for our use case.

The Okta API documentation for working with Apps is here, and it's terrible.

It's not completely useless, mind. I really like seeing the example raw responses and the curl invocation examples. But good documentation needs to describe what parts of the JSON schema are mandatory and which are not, and the documentation provides zero guidance.

You might think "Oh, the Nullable column answers that question." Friends, it does not.

  • The charts say name is not nullable, and yet in particular use cases (e.g. custom SAML apps), name is expected to be omitted.
  • The charts say slo is nullable, but the proper way to say you don't want SLO is to specify "slo":{"enabled":false} And confusingly, in the Single Logoff section it says enabled is not nullable.

There's simply too many permutations to find exactly which deviation from the kitchen-sink examples provided in the documentation was causing the JSON validation to fail. The error response certainly doesn't tell you!

{
  "errorCode":"E0000003",
  "errorSummary":"The request body was not well-formed.",
  "errorLink":"E0000003",
  "errorId":"<redacted>",
  "errorCauses":[]
}

Time to work smarter, not harder.

We've got working terraform that generates the app configured how we need it, so we'll just borrow that to get the proper JSON structure. Because if you put Terraform in trace logging mode, it logs everything, including the REST queries it sends to Okta.

Terraform

This code is for terraform 1.0.x:

terraform {
  required_providers {
    okta = {
      source  = "okta/okta"
      version = "~> 3.0"
    }
  }
}

provider "okta" {
  org_name         = "OKTA_ORG_NAME"
  base_url         = "OKTA_BASE_URL"
  api_token        = "OKTA_API_TOKEN"
  max_retries      = 10
  max_wait_seconds = 600
}

resource "okta_app_saml" "my_dummy_okta_app" {
  label = "my_dummy_okta_app"
  sso_url     = "https://my.sso.url"
  recipient   = "https://my.sso.url"
  destination = "https://my.sso.url"

  audience = "my_audience_identifier"

  subject_name_id_template = "$${user.userName}"
  subject_name_id_format   = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"

  signature_algorithm     = "RSA_SHA256"
  digest_algorithm        = "SHA256"
  response_signed         = true
  assertion_signed        = true
  honor_force_authn       = true
  authn_context_class_ref = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
  hide_web                = true
  hide_ios                = true

  lifecycle {
    ignore_changes = [users, groups]
  }
}

Note: hardcoding authentication is bad. I'm doing it here tactically because this isn't a production object and we're going to delete it when we're done. Don't commit this code anywhere.

Getting the JSON

Shell commands

export TF_LOG=TRACE
terraform apply -auto-approve &>debug.log

Now open debug.log in the text editor of your choice and search for SSWS until you find the POST request which will look something like this:

---[ REQUEST ]---------------------------------------
POST /api/v1/apps?activate=true HTTP/1.1
Host: your.okta.host
User-Agent: okta-sdk-golang/2.8.0 golang/go1.17.1 darwin/amd64 okta-terraform/3.15.0
Content-Length: 1190
Accept: application/json
Authorization: SSWS your_okta_api_token
Content-Type: application/json
Accept-Encoding: gzip
{
# ... omitted for brevity ...
}
-----------------------------------------------------: timestamp=YYYY-mm-ddTHH:MM:SS.sss-ZZZZ

Voila! The snipped bit shows the JSON that Terraform successfully posted to Okta. Adapting this is a piece of cake.

Cleaning up

IMPORTANT: Don't forget to clean up!

  • run terraform destroy to delete the dummy app you created.
  • delete debug.log and your local terraform state which both contain your Okta API credentials in the clear.