WebID-TLS: API mismatch between libraries and browser

@RubenVerborgh wrote:


It has been a design goal to run WebID-OIDC alongside WebID-TLS (#22). This means that we need client libraries that abstract away the differences between the two. This issue argues why the current abstraction we use is inadequate.


Current client-side browser libraries for Solid are incompatible with the browser model for TLS with client certificates. Those libraries assume that currentUser is a function of the application, whereas it actually is a function of the request origin and destination origin. Furthermore, the client-side library has no control over the value of that function. As a result, the used abstraction does not accurately represent reality.


Because of their API, current client-side libraries cannot accurately represent or manipulate user authentication status with WebID-TLS. This is not an implementation problem, but a design problem.

Intended usage

User U wants to use one of their WebIDs W1 , W2 , …, Wk to sign in to an application A running on a data pod Pa in order for that application to be able to access data from pods P1 , P2 , …, Pn .

Currently, applications typically require data from a relatively low number of pods. However, as more users join Solid and store data in their own pod, it will not be unusual for an application to access tens or hundreds of pods (use case: a social media feed of all your friends).

Current TLS client certificate implementation in browsers

In current browsers, the client certificate—and thus, for Solid purposes, their WebID—is a function of request origin and destination origin (where “origin” = “host:port”).
In other words:

OIDC functionality

With OIDC, the client identity—and thus, for Solid purposes, their WebID—is a function of the current application. (In the Solid implementation, of the current origin, but that is a changeable implementation detail.)
In other words:

Current client-side library API

Current libraries, including rdflib, solid-client, and solid-auth-client, expose (in one way or another) a currentUser() function that returns the currently logged in user to the application.
In other words:

Here, currentApplication is an implicit parameter that is not explicitly passed to the function, but derived by the library from the execution context.

Issue with current client-side API

As we can see from the above definitions, there is a match between LibraryUser and OidcUser , but a mismatch between LibraryUser and TlsUser . This means that no current library accurately captures the TLS authentication status, not because of an implementation issue but because the API is incorrect.

In other words, current libraries expose a function to answer the question “Who is logged in to the application?”, but this question is not meaningfully defined for TLS in the browser. It is as if someone where to ask another person the question “what number do you have?”: we cannot meaningfully give an answer, because we lack context parameters to determine whether this is a street number, office number, phone number, or something else.

In TLS, there is only a meaningful answer to “which user identity will be used to fetch data for host X to host Y”, and that answer would vary with different values of X and Y.

Why did it work then?

Several people have been successfully using WebID-TLS, also through some of the aforementioned libraries. So why did this work while we just have shown that it could not have?

The answer is in our definition above:

User U wants to use one of their WebIDs W1 , W2 , …, Wk to sign in to an application A running on a data pod Pa in order for that application to be able to access data from pods P1 , P2 , …, Pn .

In current Solid applications, it very often happens that Pa=P1=Pn . That is: the application is on the same domain as the data, and most authenticated data comes from that one location.

In other words, while TlsUser and LibraryUser above tell us that:

and hence there are n + 1 possible definitions of currentUser for application A .

Yet in practice, we had:

so that it was simply assumed that currentUser was equal to whichever webID would fetch data from f(Pa, Pa) .

As an example, Melvin would go to timbl.com, authenticate as f(timbl.com, timbl.com) = webIDmelvin and be served the databrowser’s chat pane, which would read its data from timbl.com and also store it there. Therefore, there seemed to be no question that currentUser = webIDmelvin and we would say that “Melvin is logged in as webIDmelvin to the databrowser on timbl.com”. However, reality is only reflected in the statement that “Melvin’s browser uses webIDmelvin to fetch data for timbl.com from timbl.com”.

Why will we notice in the future that this API abstraction is broken?

Given that, in the Solid vision, every person should be able to store data where they want, we assume that people will be able to store every single piece of data they produce. As argued here, this means that every small piece of data, such as every individual like on a post, could be stored in another place. So when generating a social media feed in the Solid ecosystem, it is not unrealistic that tens or hundreds of pods will need to be queried.

As an example, rather than storing every message of a timbl.com chat on timbl.com (as we do today), every user could store their own messages on their own pod (and post links to them on the timbl.com chat).

This means that the function currentUser() suddenly becomes ill-defined. Because where do we look to determine that information? TlsUser is not defined in terms of an application, but in terms of request and destination origins. In other words, the API cannot be:

but should rather be

Now of course we could argue that “users will always use the same WebID to sign in to all of the data pods they access”, but that is not the point. The point is that the browser is imposing this API on us because of the way the UX is designed, and that we cannot change that contract (unless we change the browser).

Control over the value of the function

Another major difference is that, with WebID-TLS, the value of TlsUser is set by the browser (through a user interaction) and cannot be changed without restarting the browser (unless through an extension, but even then possibilities are limited).

With WebID-OIDC, the value of OidcUser is set by the client-side library in JavaScript (through a user interaction), and can be changed.

This means that, for WebID-TLS, the functions login() and logout() are essentially meaningless (without browser extension, which is again limited).


This issue has already had impact: because the TlsUser function was assumed to be the same as LibraryUser , solid-auth-client would assume you were “logged in to the application” as soon as the user authenticated with WebID-TLS to one host. This is reflected in the API design, which has a currentSession() function. However, this assumption is incorrect, as proven above: there is no such thing as logging in to an application with WebID-TLs. This misconception resulted in the UI displaying “logged in as X” while requests to a server would fail with a 401 status code (#138 (comment)). Furthermore, since the client is not in control over those values, we cannot transfer a login from one server to another (as we can with OIDC). The library assumed—as any other library with the same API—that the identity was tied to the application, whereas it is not.


Client-side libraries cannot use currentUser() , login() , logout() contracts for WebID-TLS. The proper contract for the first is currentUser(datapod) , and login and logout have no meaningful interpretation. Hence, creating an abstraction for running WebID-OIDC alongside WebID-TLS is not possible with this API.

Either the API has to change, which might be inconvenient, or browser functionality needs to be altered through source code modifications and/or extensions, which might make adoption difficult.

Alternatively, we can redefine “alongside”. For instance, by picking OIDC as main authentication protocol, but still allowing users to identify themselves to OIDC through a client certificate. This latter behavior is provided by node-solid-server in OIDC mode.

There has been some discussion on https://github.com/solid/solid/issues/153 with @kjetilk, @elf-pavilik, and @melvincarvalho and would like to invite more here.

1 Like

It seems that I currently run into this exact problem anytime I need to update the server version
My server is build on synology docker and each update creates a new server.
Data are kept with volume mounts.

I need to rebuild all pods’s I have created and move out/back the public and inbox pod’s folders.