To reiterate and update: I’m building an iOS native mobile (Swift) authentication library for Solid pods. The design is intended to have the initial part of the authentication (up to and including the browser redirect) work only on the iOS client and later steps (e.g., requests to refresh access tokens using the refresh token) work either on iOS or on backend Swift-based servers (e.g., running on Linux).
I’m building this library for general use in iOS apps, and for my specific project. In my project, I also need to send an id token (or access token) up to my custom server in an initial request. In order to have this happen, I’m going to do the initial /token endpoint request on the iOS client-- Because not all issuers give id token earlier in an authorization sequence.
Proceeding on that basis, I had also not been adding a DPoP header to this initial /token request. In part because the issuers I’m working with so far don’t fail when /token requests are made without a DPoP header, and in part just because its simpler not to have a public/private key pair on my iOS client.
I’m now doing testing of the code that will make resource requests in my server, using the refresh token generated in the above manner, and I’m getting a 401 response with the failure:
I suspect this is because I didn’t use a DPoP header in my /token request that generated the refresh token. I think the error message is effectively saying it doesn’t have my public key in the access token I’m sending in the HEAD request.
If my reasoning is correct, then it looks like I have to have a public/private key pair on the iOS mobile client.
And if so, I’m wondering that public/private key pair has to the same as on my custom server-- which generates DPoP’s to make resource requests. I hope this isn’t needed because that sounds like a security problem-- to have the same public/private key pair on my client and custom server.
The only requirements for the key pair is that you need to use the same when later refreshing the token (although maybe some servers or a lot of them allow you to change the key), and that each request should be signed with this key. So, you will eventually need to share this key pair with the server. But you don’t need to use the same for all clients.
The whole DPoP in /token situation is a shame for DPoP, because it breaks boundaries between browser storage and secure server-side storage. The recommended Solid approach is to do everything in the browser and not have a server at all.
What I am coding for in my solid stack implementation is to store everything and run all requests on server side. The server would provide an app-specific API, and it would respond to this API by making and signing requests to the appropriate Solid servers, without the client having to implement anything else than the app-specific API.
I’m afraid you’re making a wrong assumption here (if I get correctly what you’re doing): the refresh token is meaningless to the resource server, it is only meant to be used at the /token endpoint of rhe OIDC provider to get new Access Tokens, which in turn can be used to make authenticated requests to the Resource Server.
The refresh token being DPoP-bound is a different issue: if your request to the /token endpoint contains a DPoP header, the OIDC provider may or may not choose to bind the refresh token to the DPoP key as well as the Access Token. If the Refresh Token is bound to the key, it should be used when making a new request to the /token endpoint when using the Refresh Token to get a new Access Token, which logically will be bound to the same key too. However, I think that the behaviour of NSS is not to bind the Refresh Token to the DPoP key, which means when doing the refresh request you are free to re-generate a DPoP key and provided it as part of a DPoP header, to bind the refreshed Access Token to this new key, and discard the previous one altogether.
Is my understanding correct, or am I misunderstanding your issue ?
I’m not sure what you mean here, but the private key should NEVER be shared with the server (or with anyone, really), this is the whole point of DPoP .
That’s a bit of an overstatement ^^. Depending on the application you are building, it’s fine to have a server-side component, but Solid makes it very interesting to build a client-side only app, with this I agree
If I remember correctly (maybe I don’t), the use case was that the server should be able to issue requests even if the user is offline. To do that, the server will need to have the key.
Depending on the application you are building, it’s fine to have a server-side component
The problem is, the client key and the access token need to be held by the same side that issues requests, because they are used together, and the refresh token and the key should also be held by the same side, because they are used together in the /token endpoint. So, it’s a situation where every request is performed on the browser and all secrets are stored on the browser, or every request is performed server-side and every secrets are stored by the server, or some secrets are shared (which is bad, I agree).
I think it is a deficiency in the way DPoP binds the key to the access token. I don’t think it is necessary that the client demonstrates the possession of the key in the /token endpoint, maybe it could just convey the hash of the key that would reside in the browser. That way, the server has the refresh token, and the browser has the key and the access token and can issue requests on its own. That won’t solve the problem with offline access though. For this we would need to bind the access token and refresh token to 2 different keys.
I’ll read through the responses fully shortly. First, let me say Thank You for the added information!
Second, let me humbly admit that this issue was raised on the basis of a bug in my (custom) server side code. Sorry about that. I had not added use of the private/public key pair back into my token request method – that refreshed the access token. I just made that change and now am getting success on my test case. This confirms that the refresh token I generated on the iOS client without a DPoP header was able to be used server-side in an access token refresh with a DPoP header, and the resulting access token used in a resource request.
I’m going to start writing a blog article shortly to try to capture the learnings that I’ve made so far on this (in addition to having them in my source code/comments).
Right, my bad, I should have been more precise: if there is a server-side component to the app, then the DPoP key should be generated on the server, and the Access Token can be kept there too. The client-side code never interacts directly with the Solid server, it only sends requests to the application server, so other ways of authenticating and securing the API can be deployed (not supporting cross-origin requests, using a cookie to identify the user session in the browser…). What should not happen is the DPoP key being generated somewhere, and then transferred over the network.
No worries, you’re certainly not the only one who runs into this type of issues so having a public conversation about them is useful to the whole community