Create syntactic sugar for `relativeRef` feature
The current relativeRef feature is more awkward to use than necessary, resulting in DID URLs that look like this:
did:example:123?service=agent&relativeRef=/images/123.png
Which could result in this URL:
https://domain.example/accounts/987/images/123.png
We should introduce some syntactic sugar that makes the default use case for relativeRef generalized across DID Methods, so URLs like this could be used instead:
did:example:123:/images/123.png
The dereferencing algorithm would look for a service of type UrlResolver with a baseUrl property, which might look like this in the DID Document:
"service": [{
"type": "UrlResolver",
"baseUrl": "https://domain.example/accounts/987"
}]
The algorithm would then take the path parameters from the DID URL (/images/123.png), and then append them to the baseUrl, resulting in the final URL to dereference in order to retrieve the resource. DID Methods MAY provide additional or alternate rules for resolving URLs. Multiple base URLs MAY be provided for the purposes of redundancy.
This mostly makes sense to me. I like the direction.
I am wondering what is the syntatic sugar here though? did:example:123:/images/123.png
How do I as a resolver detect that I am now in the relativeRef part?
I am wondering what is the syntatic sugar here though?
It's the first :/ you detect in the DID URL... and calling it "syntactic sugar" might be going a bit too far since it is, I believe, completely conformant to the current DID URL syntax. We would just be saying that there is a universal way to interpret and resolve the DID URL /if/ the DID Document contains a UrlResolver service.
Hmm, this seems to only allow for a single UrlResolver service (without any additional changes here)? Or is there a mechanism I'm just missing to allow for more than one?
While we could have just one, I expect that to cut out some number of use cases and innovation.
Another option includes passing the whole DID URL to this "UrlResolver" service via some DID-resolution-defined protocol over HTTP and then letting that service do whatever translation it wants. We could find a way to provide both options, enabling basic use cases without an actual live service, and more complex and dynamic cases via a live service. A live service could be shared by many different users, potentially enabling some privacy characteristics, and it could optionally require authorization.
One way to enable this would be to have two different types UrlResolverA and UrlResolverB (names to be bikeshed), where only one of these can appear in a DID document (presuming the original intent was to only permit a single UrlResolver in a DID document). If UrlResolverA is present, then the built-in static resolution mentioned above is used. If UrlResolverB is present, then the DID URL is passed to its service endpoint and another URL is returned. If neither are present, then the DID method defines what happens next.
Some suggestions for names:
UrlResolverA => StaticUrlResolver / OfflineUrlResolver
UrlResolverB => DynamicUrlResolver / OnlineUrlResolver / LiveUrlResolver
Hmm, this seems to only allow for a single
UrlResolverservice (without any additional changes here)? Or is there a mechanism I'm just missing to allow for more than one?
Multiple are allowed, which is what I was trying to say with this text:
Multiple base URLs MAY be provided for the purposes of redundancy.
We could do multiple base URLs, or multiple StaticUrlResolvers.
I like the StaticUrlResolver name. I agree that having a DynamicUrlResolver might be useful too for use cases where authorization is desired.
@msporny,
Multiple base URLs MAY be provided for the purposes of redundancy.
Yeah, but this only seemingly covers the redundancy case. What if I want the base path of /images/... to go to one place, the base path /vault-1/... to go somewhere else, /vault-2/... to go yet another place, /blog/... to another and so on?
I think the DynamicUrlResolver would handle this case well without exposing all the possible locations in the DID document and optionally with authorization or some measure of group privacy, etc.
Hmm, perhaps the "StaticUrlResolver" could handle the DynamicUrlResolver case too, though, by making the URL it resolves to itself a live service at the other end. If we're able to rely on existing technologies to do any redirections from that point without inventing anything new, that would be ideal. We'd just want to spell out how to use it in this way. We might want to understand whether it would cause people to have to send everything through a proxy, though, instead of using it to just get the endpoint of interest -- so maybe there's something to consider there.
We might want to understand whether it would cause people to have to send everything through a proxy
Seems like a big price to pay for a single UrlResolver type.
Seems like a big price to pay for a single UrlResolver type.
Agreed, so if that would be the consequence of relying solely on a single type and existing protocols/patterns, then it seems it would be better to define a second type and simple protocol for URL translation for using services of that type.
This was discussed during the #did meeting on 04 September 2025.
View the transcript
Create syntactic sugar for relativeRef feature w3c/did-resolution#181
<ottomorac> w3c/
<ottomorac> Suggestion from Manu on how we can remove the usage of relativeRef and introduce a change in the syntax so that these relative references can be embedded more directly into in the did URL.
<ottomorac> Moving from things: like did:example:123?service=agent&relativeRef=/images/123.png
<ottomorac> To: did:example:123:/images/123.png
<ottomorac> Notice the :/
<ottomorac> There is then a discussion between Dave Longley and Manu on the matter.
JoeAndrieu: this really ends up being part of the special topic call
ottomorac: so just bundle it into the call on the 24th
JoeAndrieu: yes.
markus_sabadello: I think this looks nice
… but is this conformant with the ABNF
… I think `service` and `relativeRef`, though, would still not go away
… and get combined with other things
… so I think this would be in addition too, not a replacement, correct?
manu: yes, this would be in addition
… the way we do paths now is with colons which feels weird to many people
… it may not be compliant
… we're trying to solve one of the long standing issues we've had is doing path stuff with DIDs is really awkward
… this is really just syntactic sugar for `relativeRef`
… we'll have to think of a way to make this work if it does have issues with the current spec
… there are ways we could handle it
<Zakim> JoeAndrieu, you wanted to say this would break some current approaches
manu: but what I hope we end up with a way to do nice looking path stuff that doesn't break compatibility with other systems
JoeAndrieu: unfortunately, this does break current syntax
… did:cosmos for one would likely choke on this
… I think the syntax sugar heads us the wrong way
… because we loose out on the need for it to be URL encoded
manu: I don't think it breaks the use of the colon
… the intent is not to break that specifically
… for URL encoding, we'll have to look at that more
… did:cosmos could say it doesn't support this syntactic sugar
dmitriz: I agree with manu and I think the syntactic sugar is extremely important
… we have lots of need for relative references for many use cases
… and `relativeRef` as the only option makes for very ugly URLs
… and if we don't address it people will roll their own solutions
… so we might as well fix it
markus_sabadello: I'm not sure about allowing DID Methods to override this
… these things do not have to be fully supported by all DID Methods
… and the services don't care how you get the DID document
… and I worry about making this syntactic sugar method dependent
dmitriz: big +1. I think that's what at issue here
… how do we keep each DID Method from solving for `relativeRef` their own way
… one potential option is to define a service endpoint that defines the default relative ref service ID
… so, a 404 service of sorts
manu: dmitriz I interpreted what you said in two different ways
… my concern if we don't make it optional is that we'll get formal objections
… that's the only reason I'm proposing it be optional
… but I expect many would opt in
… but others may not because they chose a different path before this was introduced
… I don't want to make it a requirement and then cause problems for them
… and we don't want to make this painful for various methods if we can avoid it
dmitriz: this does get revisited by several did methods
… we need relative references
… the DID Core syntax is too unwieldy
… so, we need to find a did method where this is better
… or create our own did method
… and it would be better to have this solved in someway at did core
<dmitriz> did:example:123?service=agent&relativeRef=/images/123.png
<dmitriz> serviceEndpoints: [ { type: "default relativeRef service", value: agent } ]
<manu> oh, right, excellent, that makes sense to me.
dmitriz: these are some approaches
manu: I think that's an excellent point dmitriz
… that would also potentially address JoeAndrieu's concern about how did:cosmos does it now
… and looking for that service endpoint would be a way to know if/when it's available
… and situations like cosmos's would be more manageable through this expression
… and if they ever wanted to do this, they'd have a mechanism that doesn't break backwards compatibility
… another thing dlongley brought up was about static vs. dynamic references
… imagine if a service is constantly under attack and they're having to shift those around to stay up
… so, static resolution gives you a static URL and you dereference it staticly
… and a dynamic resolution would provide a protocol that would eventually give you a path
Wip: I think this is a great discussion, but I think we should move on and leave the rest for the special topic call
… the chairs also feel this is one of the last remaining things to address in the spec
<Zakim> JoeAndrieu, you wanted to say I think the colon mechanism is a non-starter from a charter standpoint
Wip: so I'd like to pause now, address it more on the 24th, and then make more time at TPAC
JoeAndrieu: delaying until the special topic call makes sens
<Zakim> manu, you wanted to say I don't agree :P
JoeAndrieu: if we go with the :/ approach, we'd have to change the ABNF
manu: that's the bit I was saying about language lawyering our way around it
… it's not a DID and it's not a DID URL...it's a thing that gets translated into a DID
… ideally, we don't have to...but that is an option
… we could use any other separator
… but this might be one of the features of this
… since it is not valid syntax, then no one could have picked this
… and so we know it won't break other things
JoeAndrieu: no, this change would break existing DIDs once this is introduced
There are several ways to introduce a service to enable this feature (better relativeRef "sugar") that is backwards compatible with existing DID URLs. For example, the service could express a base path on which it operates (one way or another), such that only if that base path is used in a DID URL will the resolver service will become operative (otherwise it will not).
Just to note from yesterday's DID WG call, did:example:123:/images/123.png isn't a valid DID URL per https://www.w3.org/TR/did-1.1/#did-url-syntax:
did-url = did path-abempty [ "?" query ] [ "#" fragment ]
and
did = "did:" method-name ":" method-specific-id
method-name = 1*method-char
method-char = %x61-7A / DIGIT
method-specific-id = *( *idchar ":" ) 1*idchar
idchar = ALPHA / DIGIT / "." / "-" / "_" / pct-encoded
pct-encoded = "%" HEXDIG HEXDIG
@talltree may also have an opinion on this, he loves ABNF discussions :)
For example, the service could express a base path on which it operates (one way or another), such that only if that base path is used in a DID URL will the resolver service will become operative (otherwise it will not).
Right, and so the following would become the example (which is conformant to DID URL syntax):
did:example:123/images/123.png
The dereferencing algorithm would look for a service of type UrlResolver with a baseUrl property, which might look like the following two examples. For a static resolver:
"service": [{
"type": "StaticDidUrlResolver",
"basePath": "/",
"serviceEndpoint": "https://domain.example/accounts/987"
}]
For a dynamic resolver:
"service": [{
"type": "DynamicDidUrlResolver",
"serviceEndpoint": "https://resolver.example/service/"
}]
For a dynamic resolver:
"service": [{ "type": "DynamicDidUrlResolver", "serviceEndpoint": "https://resolver.example/service/" }]
You could still map a DynamicDidUrlResolver to a basePath too -- so it should probably be specified there. The meaning is: if a given DID URL has a matching basePath with one of the listed URL resolvers (any of the static or dynamic ones), then use that service to resolve the URL.
I think the DynamicDidUrlResolver idea is basically the same as the "proxy" service type proposal that was first proposed here and then discussed here.
did:example:123/images/123.png
Currently, it's left to the DID method and/or the DID controller to define how to dereference such DID URLs with a path, and I don't think we should change that. Note that e.g. did:cheqd or did:webvh use the path in various ways to identify resources other than DID documents. In my opinion, the fact that the path dereferencing can be different for every DID method is a feature, not a bug.