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