Resource requests to the Enterprise Solid Server: Getting 400, Bad Request

I’m trying to make resource requests of the Enterprise Solid Server. I have this working (with the different relevant credentials) for the NSS (e.g., with https://crspybits.solidcommunity.net), but I’m getting 400 http responses and “Bad Request” from the ESS.

I’m able to successfully use /token requests with the ESS to refresh my access token.

This uses Swift code, using my own HTTP requests. For reference, the code is here: GitHub - SyncServerII/ServerSolidAccount: Server-side Solid Pod Account for SyncServerII

I’m attempting to create a new directory/container, and upload a file. In the request below, the access token was successfully refreshed immediately before.

My webid is: https://pod.inrupt.com/crspybits/profile/card#me

I’m making a PUT request to:
https://pod.inrupt.com/crspybits/32CD8522-D921-43DF-8B3B-8FAC4A71D3E6/13567B1B-1C4C-40C0-AE93-86DC117D04FD.txt

Request headers:
["Authorization": "DPoP <snip>", 
"Link": "<http://www.w3.org/ns/ldp#Resource>; rel=\"type\"", 
"Dpop": "<snip>", 
"Host": "pod.inrupt.com", 
"Content-Type": "text/plain"]

Response body:

 {
    "error": {
        
        "code": "400",
        "message": "Bad Request"
    }
}

Response headers:

[AnyHashable("Content-Length"): "90", AnyHashable("Connection"): "keep-alive", AnyHashable("Date"): "Fri, 24 Sep 2021 00:44:13 GMT", AnyHashable("Content-Type"): "application/json", AnyHashable("Strict-Transport-Security"): "max-age=15724800; includeSubDomains"]

(This issue is also here: Resource requests to the Enterprise Solid Server: Getting 400, Bad Request · Issue #5 · SyncServerII/ServerSolidAccount · GitHub)

Well, I’m looking around for issues that could be causing this. One thing I’m noticing is that my DPoP proof does not have an ath claim (see draft-ietf-oauth-dpop-03). Could the ESS require this, but the NSS not?

I’ve got an ath claim now. No change in the result.

Well, in further poking around I’ve gotten this to work. I’ve removed the DPoP header entirely from the PUT request and this is now working. My test debugging output follows. Somehow the ESS doesn’t require a DPoP proof. And if you give it the request fails, at least with my setup.

[2021-09-25T15:43:41.329Z] [DEBUG] [SolidCreds+CloudStorage+Requests.swift:221 request(path:httpMethod:body:headers:accessTokenAutoRefresh:completion:)] Request: method: PUT

[2021-09-25T15:43:41.329Z] [DEBUG] [SolidCreds+CloudStorage+Requests.swift:222 request(path:httpMethod:body:headers:accessTokenAutoRefresh:completion:)] Request: url.host: pod.inrupt.com

[2021-09-25T15:43:41.329Z] [DEBUG] [SolidCreds+CloudStorage+Requests.swift:223 request(path:httpMethod:body:headers:accessTokenAutoRefresh:completion:)] Request: Request headers: Optional([“Content-Type”: “text/plain”, “Authorization”: “DPoP [Snipped-- Was the access token]”, “Host”: “pod.inrupt.com”, “Link”: “http://www.w3.org/ns/ldp#Resource; rel="type"”])

[2021-09-25T15:43:41.330Z] [DEBUG] [SolidCreds+CloudStorage+Requests.swift:224 request(path:httpMethod:body:headers:accessTokenAutoRefresh:completion:)] Request: URL: https://pod.inrupt.com/crspybits/9400A867-9403-489E-B2A3-BF7F604517B3/F311C833-FF7E-4BAA-89D7-78A942B7B1AF.txt

[2021-09-25T15:43:42.033Z] [DEBUG] [SolidCreds+CloudStorage+Extras.swift:145 uploadFile(named:inDirectory:data:mimeType:completion:)] Success Response: Data: Headers: [AnyHashable(“Connection”): “keep-alive”, AnyHashable(“Content-Length”): “0”, AnyHashable(“Date”): “Sat, 25 Sep 2021 15:43:41 GMT”, AnyHashable(“Strict-Transport-Security”): “max-age=15724800; includeSubDomains”, AnyHashable(“Content-Location”): “https://pod.inrupt.com/crspybits/9400A867-9403-489E-B2A3-BF7F604517B3/F311C833-FF7E-4BAA-89D7-78A942B7B1AF.txt”, AnyHashable(“Link”): “http://www.w3.org/ns/ldp#Resource; rel="type", http://www.w3.org/ns/ldp#NonRDFSource; rel="type", </crspybits/9400A867-9403-489E-B2A3-BF7F604517B3/F311C833-FF7E-4BAA-89D7-78A942B7B1AF.txt?ext=shex>; rel="Shape Expression Vocabulary”, </powerswitch/crspybits>; rel="https://inrupt.com/ns/ess#hasPowerSwitch\“, https://pod.inrupt.com/crspybits/9400A867-9403-489E-B2A3-BF7F604517B3/F311C833-FF7E-4BAA-89D7-78A942B7B1AF.txt?ext=description; rel="describedby", </crspybits/9400A867-9403-489E-B2A3-BF7F604517B3/F311C833-FF7E-4BAA-89D7-78A942B7B1AF.txt?ext=acr>; rel="http://www.w3.org/ns/solid/acp#accessControl\”, http://www.w3.org/ns/solid/acp#Read; rel="http://www.w3.org/ns/solid/acp#allow\“, http://www.w3.org/ns/solid/acp#Write; rel="http://www.w3.org/ns/solid/acp#allow\”"]

Status Code: 201

Test Case ‘GeneralTests.testUploadNewFile_NewDirectory’ passed (1.634 seconds)

And so you can see the evidence of the successful creation of the new folder in the Pod browser:

One more fact: If I also don’t use the DPoP header with resource requests to https://crspybits.solidcommunity.net/ it fails. It seems a difference between CSS and ESS is that CSS needs the DPoP proof where ESS doesn’t.

The DPoP header is a red herring. The problem is the use of your link header. Try using <http://www.w3.org/ns/ldp#NonRDFSource>; rel="type" when creating text resources.

In ESS, DPoP is required when the client uses DPoP when it first requests a token from the identity provider. If the client does not use DPoP at the token_endpoint, then DPoP is not required because the token will not be bound to a client-managed keypair. Generally, you should be using DPoP, especially if you are sending tokens to multiple Pod servers.

Thanks for that. I changed the code to use this for the link header, signed the user in again, generated a new access token, and retried the request. Unfortunately, no change.

[2021-09-28T02:22:23.874Z] [DEBUG] [SolidCreds+CloudStorage+Requests.swift:221 request(path:httpMethod:body:headers:accessTokenAutoRefresh:completion:)] Request: method: PUT

[2021-09-28T02:22:23.874Z] [DEBUG] [SolidCreds+CloudStorage+Requests.swift:222 request(path:httpMethod:body:headers:accessTokenAutoRefresh:completion:)] Request: url.host: pod.inrupt.com

[2021-09-28T02:22:23.874Z] [DEBUG] [SolidCreds+CloudStorage+Requests.swift:223 request(path:httpMethod:body:headers:accessTokenAutoRefresh:completion:)] Request: Request headers: Optional([“Dpop”: “[snip]”, “Link”: “http://www.w3.org/ns/ldp#NonRDFSource; rel="type"”, “Content-Type”: “text/plain”, “Authorization”: “DPoP [snip]”, “Host”: “pod.inrupt.com”])

[2021-09-28T02:22:23.874Z] [DEBUG] [SolidCreds+CloudStorage+Requests.swift:224 request(path:httpMethod:body:headers:accessTokenAutoRefresh:completion:)] Request: URL: https://pod.inrupt.com/crspybits/62DB7A4F-2281-47D0-9DB3-A60D2FD870F1/1706F328-4C0A-485E-8F7B-09ACD1164433.txt

[2021-09-28T02:22:24.475Z] [DEBUG] [SolidCreds+CloudStorage+Extras.swift:154 uploadFile(named:inDirectory:data:mimeType:completion:)] Failure Response: Data: {
“error”: {

    "code": "400",
    "message": "Bad Request"
}

}

Headers: [AnyHashable(“Connection”): “keep-alive”, AnyHashable(“Content-Type”): “application/json”, AnyHashable(“Content-Length”): “90”, AnyHashable(“Strict-Transport-Security”): “max-age=15724800; includeSubDomains”, AnyHashable(“Date”): “Tue, 28 Sep 2021 02:22:24 GMT”]

Status Code: 400; error: badStatusCode

I’ve refactored this code and the resource access code I’m using now is in: GitHub - crspybits/SolidResourcesSwift: Make resource requests to a Solid Pod in the Swift language

Summary: While I can get resource requests working with the ESS, I cannot do so with a DPoP header present. This is a problem for me because in my testing, the NSS seems to require the DPoP header for resource requests, and I need a method that works generally across all Solid servers (e.g., for both the ESS and NSS).