nft.storage icon indicating copy to clipboard operation
nft.storage copied to clipboard

Receiving 500 "Audience does not match this service." when attempting to refresh a UCAN token with `POST /ucan/token`

Open 0x00000010 opened this issue 3 years ago • 1 comments

Step 1

Login to NFT.storage and retrieve an API token called $API_KEY

Step 2

Register a DID

> npx ucan-storage keypair

DID:           $DID
Public Key:    $PUBLIC_KEY
Private Key:   $PRIVATE_KEY

Step 3

Link the DID

> curl -X POST \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
--data "{\"did\": \"$DID\"}" \
https://api.nft.storage/user/did

{"ok":true,"value":"$DID"}

Step 4

Use the $API_KEY to request a $UCAN_TOKEN for the $DID

> curl --verbose -X POST \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-H "x-agent-did: $DID" \
https://api.nft.storage/ucan/token

*   Trying 104.x.x.x:443...
* Connected to api.nft.storage (104.x.x.x) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: /etc/ssl/cert.pem
*  CApath: none
* (304) (OUT), TLS handshake, Client hello (1):
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* (304) (IN), TLS handshake, Certificate (11):
* (304) (IN), TLS handshake, CERT verify (15):
* (304) (IN), TLS handshake, Finished (20):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-AES256-GCM-SHA384
* ALPN, server accepted to use h2
* Server certificate:
*  subject: C=US; ST=California; L=San Francisco; O=Cloudflare, Inc.; CN=nft.storage
*  start date: Dec  9 00:00:00 2021 GMT
*  expire date: Dec  8 23:59:59 2022 GMT
*  subjectAltName: host "api.nft.storage" matched cert's "*.nft.storage"
*  issuer: C=US; O=Cloudflare, Inc.; CN=Cloudflare Inc ECC CA-3
*  SSL certificate verify ok.
* Using HTTP2, server supports multiplexing
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7f86d8010a00)
> POST /ucan/token HTTP/2
> Host: api.nft.storage
> user-agent: curl/7.84
> accept: */*
> authorization: Bearer $API_KEY
> content-type: application/json
> x-agent-did: $DID
>
* Connection state changed (MAX_CONCURRENT_STREAMS == 256)!
< HTTP/2 200
< date: Fri, 26 Aug 2022 07:03:17 GMT
< content-type: application/json;charset=UTF-8
< content-length: 512
< access-control-allow-origin: *
< expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
< server: cloudflare
< cf-ray: 740ab4e3bf3e2388-HKG
< alt-svc: h3=":443"; ma=86400, h3-29=":443"; ma=86400
<
* Connection #0 to host api.nft.storage left intact
{"ok":true,"value":"$UCAN_TOKEN"}

the contents of the token are

{
  "aud": "$DID",
  "att": [
    {
      "with": "storage://$DID",
      "can": "upload/*"
    }
  ],
  "exp": 1662706997,
  "iss": "$SERVICE_DID",
  "prf": []
}

and the $SERVICE_DID that matches the contents of curl https://api.nft.storage/did

Step 5

Call the same endpoint with this UCAN token to refresh it with own $DID

> curl --verbose -X POST \
-H "Authorization: Bearer $UCAN_TOKEN" \
-H 'Content-Type: application/json' \
-H "x-agent-did: $DID" \
https://api.nft.storage/ucan/token

{"ok":false,"error":{"code":"Error","message":"Invalid UCAN: Audience does not match this service."}}

Same result when using the SERVICE_DID

> curl --verbose -X POST \
-H "Authorization: Bearer $UCAN_TOKEN" \
-H 'Content-Type: application/json' \
-H "x-agent-did: $SERVICE_DID" \
https://api.nft.storage/ucan/token

{"ok":false,"error":{"code":"Error","message":"Invalid UCAN: Audience does not match this service."}}

The documentation just says that you need to call the endpoint with the token to refresh it - where am I going wrong?

0x00000010 avatar Aug 26 '22 07:08 0x00000010

I think you need to create a request token from the $UCAN_TOKEN that the service gave you.

UCANs are a little weird in that you have to create your own token locally, with your own DID as the "issuer" and the nft.storage service's DID as the "audience". The UCAN that gets returned from /ucan/token gets included in the token you create as part of the "proof chain", to show that your locally created token really has the authorization's it's claiming.

Sorry the docs aren't very clear on that - I'll try to revise them soon to make the surprising bits less surprising.

yusefnapora avatar Sep 05 '22 14:09 yusefnapora

If you still need support, please re-open this issue and provide more detail. Thanks!

elizabeth-griffiths avatar Apr 25 '24 12:04 elizabeth-griffiths