1

I am working on an API for account invitations, where one user creates an invitation for another user to join the platform, with some specific set of permissions. The invitation itself will be in the well known form of an email containing a link with some random key to accept the invitation, similar to how we commonly see password reset and account confirmation flows being implemented.

Like many other APIs, this API will have the regular CRUD operations such as POST /invites/, GET /invites/{id}, DELETE /invites/{id} and so forth, based on the invite-entity's actual ID and implementing a regular authorization scheme. But we also need a way to retrieve the invite by a secondary ID, the secret identifier used in the link.

One could consider to just use the secret as the primary identifier for the invites but this would be problematic. Since knowing secret implies permission to apply the invitation the secret itself should not (always) be exposed by the other CRUD calls; the response to GET /invites/ would not normally include the secrets, making it problematic to perform any other operations, such as DELETE, on the invites.

There have already been several good questions about ways to refer to resources by more than one identifier, such as REST API DESIGN - Getting a resource through REST with different parameters but same url pattern and several others it points to. Usually what they boil down to is either:

  • Suggesting solutions like /foos/{id} for referencing by primary ID and /foos/field/{value} for referencing by another field, which in this case would be the secret. However I feel this makes the URI structure ambiguous, because /foos/field/{value} could also be read as referring to the '{value}' field of the foo with ID 'field'. Since IDs are often randomly generated, There might even be a collision between an ID and an actual field name in the resource.
  • Stating that there should be exactly 1 URI to refer to 1 resource, implying 1 primary identifier, and suggesting that referring to a resource by anything other than that primary identifier should be represented as a filter query that should return a collection. So the URI would become /invites/?secret={secret}. However this doesn't 'feel right' as a secret does actually refer to exactly one resource. Also this 'secret' field itself would probably not even be exposed in the resource representation, making it feel a bit weird that one could 'filter' on it.

It's also commonly pointed out that it's never really needed to refer to an entity by more than one unique identifier. But then, how would a use-case like this be implemented?

In the past I've solved similar situations by using the filter pattern but since I keep encountering this I'm really curious if there is a better solution.

SFG
  • 143
  • 1
  • 12

1 Answers1

0

What I eventually came up with is to define a new collection 'authLinks', short for 'authenticated links'. The 'secret' is the primary identifier for entities in this collection. Those entities have a 'resource' field which is a link to the resource (in this case invite) exposed by this authLink. When such an 'authLink' is retrieved from the API the server will retrieve the linked resource using it's own internal credentials and include it in the response as a 'computed' field, resolvedResource.

So, what the interaction will look like:

POST `/authLinks/supserSecretSecret` {"resource": "/invites/someId"} (with appropriate credentials)
> `{"id": "supserSecretSecret", "resource": "/invites/someId", "resolvedResource": {"id": "someId", ...}}`

GET `/authLinks/supserSecretSecret` (without credentials)
> `{"id": "supserSecretSecret", "type": "invite", "resource": "/invites/someId", "resolvedResource": {"id": "someId", ...}}`

System components and administrators can CRUD such authLinks for new or existing resources such as invites or any other kind, with an appropriate permissioning scheme for Create, Update, Delete. Reading does not require any permissions.

In some cases it might be appropriate to implement read-once mechanics. Although we should ask ourselves whether or net GET is the appropriate method to retrieve the data in such a case (no), definitely don't forget to set appropriate headers to prevent caching.

For my situation this solves the problem nicely in a generic way, ie for any kind of resource that needs to be accessible through a shared secret link.

SFG
  • 143
  • 1
  • 12