solid / specification

Solid Technical Reports
https://solidproject.org/TR/
MIT License
486 stars 46 forks source link

[AuthN & AuthZ] Server side clients (apps) #504

Open elf-pavlik opened 1 year ago

elf-pavlik commented 1 year ago

Abstract

Solid historically puts a major focus on clients (apps) which run on users' devices. Solid-OIDC is a great example of crafting a solution addressing this specific class of clients (apps). Currently, there is a growing number of clients (apps) running on the server rather than on users' devices. We urgently need to take those server-side clients into greater consideration for our next iteration of AuthN & AuthZ work for the Solid ecosystem.

Introduction

This issue acts as a follow-up to the Coordinating further work on authentication and authorization discussion on 2023-02-15 . There are more related issues, I would propose that we keep this one focused and work around specific existing implementation which will be impacted. To be specific I will focus on three product classes from two specifications in the Solid Ecosystem.

Solid Notifications

Solid Notifications Protocol defines 5 (possibly 6) classes of specialized products. In the repository we track a few relevant issues:

The most relevant classes of products are:

Subscription Client

Solid Notifications Subscription Client is responsible for establishing customized Notifications Channel. It requires AuthN & AuthZ and there are existing implementations that are being used by clients running on the user's device as well as clients running on the server. One example is included in solid-webhook-client maintained by @jaxoncreed which among various other dependants is also used by SAI Authorization Agent co-maintained by me.

Notifications Sender

Solid Notifications Sender is responsible for sending notifications to the Solid Notifications Receiver. There are currently 3 Notification Channel Types defined where the Notifications Sender will act as a server-side client

WebhookChannel2023

WebhookChannel2023 follows the well-established webhook pattern. Conformant Notifications Sender is going to be implemented in CommunitySolidServer. CSS v6 already includes conformant implementation of the predecessor WebhookSubscription2021

LDNChannel2023

LDNChannel2023 is building on Linked Data Notifications. I'm not aware of existing Notifications Sender implementation, I'm guessing @csarven would be interested in getting it into CSS.

WebPushChannel2023

WebPushChannel2023 is building on top of Push API, Generic Event Delivery Using HTTP Push and Voluntary Application Server Identification (VAPID) for Web Push. Similar to LDNChannel2023 I'm not aware of existing Notifications Sender implementation. I'm interested in helping with getting it into CSS.

Solid Application Interoperability

Solid Application Interoperability (SAI) has one crucial product class that encapsulates a lot of complexity and is expected to be a server-side application, in some flows acting as a server in other flows acting as a client.

Authorization Agent

SAI Authorization Agent is associated with the end user, which could be an individual or an organization. In some ways, it acts similarly to an OIDC Provider (aka issuer). I'm co-maintaining an open source TS implementation. Besides acting as both Client and Resource Server, it also comes with Notification Subscription Client and Notifications Receiver using mentioned solid-webhook-client. One of the issues with using Solid-OIDC is being tracked in the repo of the Solid-OIDC client library being used:

TL;DR I want to run the Solid Client component and Solid Subscription Client component in separate processes. Since Solid-OIDC relies on using refresh tokens (which are being rotated), it adds a lot of complexity to orchestrate refresh token rotation across all the processes sharing the same OIDC session.

Proposal

While we should incorporate feedback from other implementation efforts working on server-side clients, and also keep in mind prior work including:

I would like that we first search for consensus on a few basic design choices:

Server-side clients can manage their private keys

What I see as one of the main motivations for Solid-OIDC and its predecessor WebID-OIDC, relates to the lack of a reliable and user-friendly way to manage keys in on device clients (apps). Solid-OIDC shifts that responsibility to the OIDC Provider (aka issuer), which each user can choose. While the client still uses ephemeral key pair for DPoP. The trust relies on the signature done by OP, which the user designates via solid:oidcIssuer and the redirect_uri advertised in the Client ID Document which the client has to control to receive the authorization code from the OIDC Provider. All that alleviates the need for the on-device client (app) to manage nonephemeral keys.

For server-side clients, we don't have similar issues related to key management. I hope we can agree that they can manage their own keys without relying on the end-user's OIDC Provider. This leads us to the next design choice.

Server-side clients should be able to Authenticate independently from the End-user

It is a common scenario where a server-server side client, let's say identified by WebID/ClientID https://hermes.ex/#id can be used by any number of end users, each one identified by their own WebID/UserID https://alice.ex/#id, https://bob.ex/#id etc.

For the purpose of pure Authentication, I believe we should allow such server-side clients to authenticate independently from any of the end-users on whose behalf it can act. Of course, we still need to ensure that the client can act on behalf of the end user. For that, we step into delegation / client authorization (AuthZ).

In Solid-OIDC the ID token includes both identifiers:

While it doesn't convey any details about what the End-user is authorizing the client to do on their behalf. It is still used to simply express that the client https://projectron.ex/#id is acting on behalf of the End-user https://alice.ex/#id.

If we can rely on some delegation/authorization mechanism to express that the client is acting on behalf of the specific end-user, preferably providing information on what exactly the end-user authorized (AuthZ) the client to do on their behalf, we can let the client authenticate (AutnN) independently of any end-user which uses that client.

Resource Server has an associated Authorization Server

Solid-OIDC: Authorization Server Discovery follows a simple OAuth mechanism. It enables separating responsibilities between Authorization Server and the Resource Server. I believe that if we follow the direction of HttpSig Authentication for SoLiD we should still have this possibility of separating concerns between RS and AS.

Solid-OIDC: Obtaining an Access Token describes how the client with an ID Token (a sender-constrained token using DPoP) can obtain an access token from RS associated AS to use it with that RS.

Currently, it offers User-Managed Access (UMA) 2.0 Grant for OAuth 2.0 Authorization as a general mechanism to push credentials/claims like ID Token to the Authorization Server. This can allow pushing additional credentials, for example, an Access Grant to properly represent delegation/authorization. This could also be used to push a different claim/credential instead of Solid-OIDC ID Token, for example, simple self-signed JWT as in original WebhookSubscription2021

SAI is yet to define how Data Grants can be expressed as VC and included in VP pushed as a claim to UMA AS. We want to coordinate that work with an ongoing effort at Inrupt with Access Grants and the use of Verifiable Credentials API

Next steps

I would like that we focus on the problem described above. Working with existing implementation experience, also adding other implementations which I have not mentioned above.

Preferably we can agree on at least some of the design choices and flesh out all the details around them.

Shout outs

@acoburn, @justinwb, @jaxoncreed, @laurensdeb, @matthieubosquet, @NSeydoux, @woutermont, @dmitrizagidulin, @joachimvh

jaxoncreed commented 1 year ago

Thanks for starting the effort @elf-pavlik . I'd be excited to participate and build a reference implementation as well as supporting tools for this.

elf-pavlik commented 1 year ago

Server-side clients should be able to Authenticate independently from the End-user

As I just mentioned during the 2023-03-01 CG weekly call, I think we should put extra emphasis on this point since it also relates to where AuthN and AuthZ meet and to what degree we can approach it separately. Specifically, the delegation part where we express The client X is acting on behalf of the user A, which usually will go along with what specifically the user authorized the client to do on their behalf.

EDIT: specific use case with sequence diagrams with a Notifications Sender can be found in https://github.com/solid/notifications/issues/134#issuecomment-1429933422

laurensdeb commented 1 year ago

I agree with many of the points made in this issue, @elf-pavlik, and think we should pick up this discussion asap in the authentication panel.

Crucially, I think a layering of the Solid-OIDC protocol would be desireable in this respect, with a separate definition of a JWT containing a WebID claim in a distinct document we can reference. Various implementations, e.g. Solid OIDC, a future Solid-OAuth with JWT Access Tokens supporting delegation, ... could then base themselves on this JWT profile.

I know @tomhgmns has an OAuth implementation yielding JWT access tokens, and we have internally developed similar server side applications having their own WebID by just implementing the minimal requirements for validation from the Solid-OIDC specifications in our IdP.

I personally am not really convinced by HttpSignatures as an easy-to-use authentication option for server-side clients yet, as the specification still has flaws (e.g. no versioning, complexity of canonicalization, ...) and goes much broader than just authentication to also include data integrity guarantees. I don't have any strong opinions on including it as a potential future authentication option, but would rather start from a JWT-based mechanism that can more easily align with existing specification.

elf-pavlik commented 1 year ago

Thank you for your input @laurensdeb, I think there is a general direction of having some kind of JWT.

In my last suggested design choice (already present in Solid-OIDC)

Resource Server has an associated Authorization Server

I see it as important to remember that the final Access Token should be issued by AS associated with the RS. This follows Nat Sakimura's explanation of the main difference between ID Token and Access Token in this video. In a similar way as we use ID Token (JWT) which can be used across security domains and pushed as a claim to UMA AS, we could use some other JWT (VC/VP?) to represent the delegation.

When it comes to making the JWT Sender Constrained (rather than just Barer) with proof-of-possession technologies as DPoP we discussed with @jaxoncreed that it possibly only comes into play if the JWT doesn't have a specific audience. Probably we should still consider scenarios if the barer token could be reused by malicious party with the specified audience, probably we should write down some specific scenarios.

woutermont commented 1 year ago

Dropping my 2c here, as preparation for a call with @elf-pavlik; might come back on it afterward.

As a general remark, I'd like to refrain from talking about AuthN/OIDC/HTTPSig as much as possible when discussing AuthZ. They are orthogonal concerns: AuthN is only one way to get AuthZ; the latter should only provide a hook (e.g. token exchange) to enable them to work together.


Re server-side credentials: I agree that there is no issue.


Re RS-bound AS

As I have indicated many times before, this makes no sense to me. None of our reference authz frameworks (Oauth/UMa/GNAP) have RS-bound AS's. An RO-bound AS makes much more sense, i.m.o. RO registers AS with RS and RS with AS, creating the necessary trust relation, and can then manage all their RS's, Resources and Authorizations via one AS, providing an AA as UI.


Re delegation and independent access

These are also two orthogonal concerns (both to be discussed under AuthZ though).

Independent software agents should probably be registered at the AS (statically or dynamically) with a flag to indicate their status.

Delegation can then happen along the following lines (using the terms RO, User and Indep as "RO having synchronous access (through a client)", "End-User different from RO having synchronous access (through a client)" and "Independent agent having asynchronous access ('without' an End-User)").

Any AS or Independent agent possessing a delegation token can request the RO's AS to exchange it for a signed derivative, possibly limited in scope. This way, on an access request, the requesting agent only has to know the delegation token (contact its own AS in case it is an End-User), and exchange it for an access token at the RO's AS, which only has to check its own signature. When any AS or Independent agent in the delegation chain wants to break the chain, it can request the RO's AS to revoke the tokens derived from their derivative.

elf-pavlik commented 1 year ago

@woutermont I think we need to get more diagrams to clearly illustrate what you describe here.

Re RS-bound AS

As I have indicated many times before, this makes no sense to me. None of our reference authz frameworks (Oauth/UMa/GNAP) have RS-bound AS's. An RO-bound AS makes much more sense, i.m.o. RO registers AS with RS and RS with AS, creating the necessary trust relation, and can then manage all their RS's, Resources and Authorizations via one AS, providing an AA as UI.

I think this is taking it even a step further. Still in the end it yields RS-bound AS after the RO does that binding.


I was thinking about the smallest change to what we already have in Solid-OIDC. I wonder what everyone thinks about the following approach.

We keep DPoP Proof mechanism between the client and the AS, but without the ID Token (so also no OIDC Provider/Issuer). Instead, each client's Client ID (WebID) can link to its JSON Web Key Set

In practice verifying the DPoP Proof AS will:

This happens in place of Solid-OIDC:

Again, this only tries to remove OIDC Provider and ID Token. Instead, let the client manage their own public keys and use corresponding private keys to sign the DPoP Proof.

This way the client can authenticate itself to the AS. As I suggested, the delegation from the End-user to the client should be handled as AuthZ. This means we don't expect any webid of the end-user from the process of authenticating the client.

I also hope to exemplify here that we should aim to only provide alternative flow between Client <-> AS. The flow between Client <-> RS should work exactly the same as it does with the current Solid-OIDC.

woutermont commented 1 year ago

@elf-pavlik, that definitely seems like a feasible approach.

Just noting here that also for autonomous clients, we need to pay attention to https://github.com/solid/data-interoperability-panel/issues/314.


Re RS-bound vs RO-bound AS: I'd propose to cover that AuthZ part in another issue/discussion, and focus on the 'autonomous' AuthN aspect here.

elf-pavlik commented 1 year ago

I think for Client Authentication we could simply rely on private_key_jwt, with the difference that the public key would not be registered but instead discoverable from ClientID/WebID Documet of the client. Again for AuthZ purposes we still need to know on behalf of which user the client is acting, with the details of what the user is authorizing the client to do on their behalf.

from https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication

private_key_jwt Clients that have registered a public key sign a JWT using that key. The Client authenticates in accordance with JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants [OAuth.JWT] and Assertion Framework for OAuth 2.0 Client Authentication and Authorization Grants [OAuth.Assertions]. The JWT MUST contain the following REQUIRED Claim Values and MAY contain the following OPTIONAL Claim Values:

    iss
        REQUIRED. Issuer. This MUST contain the client_id of the OAuth Client. 
    sub
        REQUIRED. Subject. This MUST contain the client_id of the OAuth Client. 
    aud
        REQUIRED. Audience. The aud (audience) Claim. Value that identifies the Authorization Server as an intended audience. The Authorization Server MUST verify that it is an intended audience for the token. The Audience SHOULD be the URL of the Authorization Server's Token Endpoint. 
    jti
        REQUIRED. JWT ID. A unique identifier for the token, which can be used to prevent reuse of the token. These tokens MUST only be used once, unless conditions for reuse were negotiated between the parties; any such negotiation is beyond the scope of this specification. 
    exp
        REQUIRED. Expiration time on or after which the ID Token MUST NOT be accepted for processing. 
    iat
        OPTIONAL. Time at which the JWT was issued. 

The JWT MAY contain other Claims. Any Claims used that are not understood MUST be ignored. The authentication token MUST be sent as the value of the [OAuth.Assertions] client_assertion parameter. The value of the [OAuth.Assertions] client_assertion_type parameter MUST be "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", per [OAuth.JWT].

For example (with line wraps within values for display purposes only):

POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
        code=i1WsRn1uB1&
        client_id=s6BhdRkqt3&
        client_assertion_type=
        urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&
        client_assertion=PHNhbWxwOl ... ZT
woutermont commented 1 year ago

I'm a fan! (Although I would rather see us using OAuth docs directly than using OIDC ones for this: private-key-jwt, Assertion framework, JWT profile)

elf-pavlik commented 1 year ago

https://www.iana.org/assignments/oauth-parameters/oauth-parameters.xhtml#token-endpoint-auth-method

Token Endpoint Authentication Method Name Change Controller Reference
private_key_jwt OpenID_Foundation_Artifact_Binding_Working_Group OpenID Connect Core 1.0 incorporating errata set 1
woutermont commented 1 year ago

Ha, I stand corrected! 😅

Nevertheless, since OAuth thus seems to have picked it up as more general authentication method, I still suggest that we use it as described in that context of authorization, rather than one of user-authentication 😉

elf-pavlik commented 1 year ago

I'm focusing specifically on Client Authentication since we deal with confidential (server-side) clients, I think verifying the identity of the user delegating access to the client would fit under AuthZ. For example, if some party issues a VC (or other credential/claim) from the end user to the client, it would allow verifying WebID of the end user.

woutermont commented 1 year ago

I have the feeling that we're talking past each other... Are you talking about getting an ID Token for an autonomous client? 🤔 Because I don't really see why we would need that, and that's precisely what an OIDC flow would get us.

As I see it, the scenario you were interested in here is the one where an autonomous client would seek authorization in the form of an Access Token from the AS. For this, the client could indeed identify itself using a key. But such an exchange is described by OAuth, not by OIDC.

Or am I completely missing your point?

elf-pavlik commented 1 year ago

I focus on confidential (server-side) clients. They will still act on behalf of a Social Agent with WebID. While the confidential (server-side) client can authenticate itself with RS bound AS (RO side #517), there will be still a need for resolving the access policy to know on whose behalf the client is acting.

In the case where Alice is using a confidential (server-side) client, to access data on storage owned by ACME. ACME's AS could authenticate the client based on private_key_jwt but it will still need the information that the client acts on behalf of Alice and what Alice authorized it to do.

Allice would authorize that client using her Authorization Agent (or her AS). Alice's AA/AS could issue a credential/claim, which later could be pushed by the client to ACME's AS. In the end, only ACME's AS can issue final Access Tokens, but Alice never directly interacts with ACME's (and 100s other) AS.


I don't really talk about autonomous clients, currently, any software agent acts on behalf of some social agent, even if based on configuration or custom scripting used by that social agent to run the software agent. If the software agent violates some law, most likely the social agent on whose behalf it acted will bear legal consequences.

bblfish commented 1 year ago

This thread started with an emphasis on authorization of software agents that may run on the user's POD, fetching data on his behalf or posting notification messages. Those agents may need to authenticate to some resources as part of doing their job.

What is the problem that is trying to be solved?

  1. Could the software agent authenticate as itself, just as any other agent, assuming the access control rules recognize it?
  2. Or is it that we are meant to think of the AuthZ rules applying to the pod owner, and so the software agent needs to proove it has delegated access?
elf-pavlik commented 1 year ago

This thread started with an emphasis on authorization of software agents that may run on the user's POD, fetching data on his behalf or posting notification messages.

This is just one of the cases, in a more general case the server-side client is run by a 3rd party that offers it as a service. Various independent social agents can use that client, in that case, the client always acts on behalf of a specific social agent.

Or is it that we are meant to think of the AuthZ rules applying to the pod owner, and so the software agent needs to proove it has delegated access?

I think we need to support this common scenario. Let's take an example with

Both Alice and Bob can use Performchart to access specific data that ACME shared with them. When the ACME resource server receives a request from Performchart, it needs to know (with the help of its AS) if Performchart is acting on behalf of Alice, on behalf of Bob, or someone else.

At the same time, Performchart can authenticate itself independently from Alice, Bob, or any other social agent who uses that client as a service provided by Yoyodyne. In this issue, I hope we can acknowledge that:

bblfish commented 1 year ago

I find your scenario difficult to understand, as I don't know what perform chart, yoyodyne or acme do. Does the following attempt at a more realistic scenario that everyone can relate to, help?

Let us say that Alice and Bob and their accountant firm You Own Your Outcome (Yoyo) all have a bank account with the Advanced Commerce and Money Exchange at https://acme.bank/. The bank gives access to each user to resources under their name (so we can avoid the unnecessary obfuscation a bank account number would bring). So

Now, Alice and Bob want to give Yoyo read access to their account so that the accounting company can help them with their finances. Yoyo has a bot - yoyoBt - that fetches the information published there in RDF regularly.

Is that the scenario?

elf-pavlik commented 1 year ago

I think what you describe is a different case which also should be supported. In your example, it sounds like delegation happens between social agents Alice -> YoYo and Bob -> YoYo. The way you described it seems like in the end YoYo delegates access further to yoyoBt which acts as the client used exclusively by YoYo.

I find your scenario difficult to understand, as I don't know what perform chart, yoyodyne or acme do.

If we want all the details of the story, let's go with:

BTW this discussion should be probably happening in #517 which has the original diagram showing Alice, Performchart, and ACME.

sequenceDiagram
  autonumber
  box green Alice
  actor Alice
  participant OP as OpenID Provider
  participant AA as Authorization Agent
  end
  box orange PerformChart
  participant C as Client
  end
  box blue ACME
  participant RS as Resource Server
  participant AS as Authorization Server
  actor ACME
  end
  Note over C: acts on behalf of Alice
  C -->> RS: GET /project-x
  RS -->> C: Project X
sequenceDiagram
  autonumber
  box purple Bob
  actor Bob
  participant OP as OpenID Provider
  participant AA as Authorization Agent
  end
  box orange PerformChart
  participant C as Client
  end
  box blue ACME
  participant RS as Resource Server
  participant AS as Authorization Server
  actor ACME
  end
  Note over C: acts on behalf of Alice
  C -->> RS: GET /project-x
  RS -->> C: Project X
elf-pavlik commented 1 year ago

@bblfish - One of the differences is that your delegation chain would be longer, in my scenario, we have the following delegation

Performchat is the app making the request to ACME's RS on behalf of either Bob or Alice. IMPORTANT: ACME only gives read-write access to Alice and Bob, besides delegating their access further, they can't directly modify ACME's access policies.

In your example we would have:

Here YoYoBot is the app making the requests to ACME's RS always on behalf of YoYo. We need an additional delegation step where both Alice and Bob authorize YoYo to access subsets of data owned by ACME. IMPORTANT: Whenever ACME revokes Alice's access, anything down the delegation chain also loses that access, so in our scenario, YoYo would only have left access delegated to them by Bob.

woutermont commented 1 year ago

For those not present at today's meeting (minutes upcoming), a special topic meeting on this issue has been proposed on Tuesday 2023-09-12 (at 14:00 UTC).

TallTed commented 1 year ago

special topic meeting on this issue has been proposed on Tuesday 2023-09-12 (at 14:00 UTC).

... and subsequently changed to today, Tuesday 2023-09-05 (at 14:00 UTC) (i.e., NOW!)

elf-pavlik commented 1 year ago

We are meeting now in https://meet.jit.si/solid-cg