Requesting an Access token with clientAppId and clientAppSecret

Hi,

I have a question related to the process of requesting a token described in this article:
Automating authentication with Client Credentials.

If I follow the steps described above I can successfully get a DPoP token to make an authenticated request, but it looks like it’s mandatory to start by the following “login” request:

const response = await fetch('http://localhost:3000/idp/credentials/', {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({ email: 'my-email@example.com', password: 'my-account-password', name: 'my-token' }),
});

const { id, secret } = await response.json();

If I try to do a user-like login, as described in https://solidproject.org/developers/tutorials/first-app:

await session.login({
       oidcIssuer: SOLID_IDENTITY_PROVIDER,
       clientName: "Inrupt tutorial client app",
       redirectUrl: window.location.href
     });

and from the session object I try to use the clientAppId and clientAppSecret values:

let id 
let secret 
if (session.info.isLoggedIn) {            
     const sessionInfo = await session.clientAuthentication.getSessionInfo(session.info.sessionId)    
     console.log(sessionInfo)        
     id = sessionInfo.clientAppId
     secret = sessionInfo.clientAppSecret
} 

which looks like this:

{
    "sessionId": "83cc23b7-2ead-4aca-a54b-3be8a6dc2f2d",
    "webId": "http://localhost:3000/fandroide/profile/card#me",
    "isLoggedIn": true,
    "redirectUrl": "http://localhost:1234/index.html?state=d7d19dd52b0542b8909d89d139fdbdca",
    "issuer": "http://localhost:3000/",
    "clientAppId": "hqbqNjaxZ93o5yQuzUfk6",
    "clientAppSecret": "m2UoPL8_RJLUfdKJoNUAXyryZwxcu7KBM3E3SVLhKB6BX4GbIYQtKhPjstCsmvwym1nUBDShYzo9k6iL4pmHMQ",
    "tokenType": "DPoP"
}

then in the reply I will get a 401 Unauthorized error.

Is this error expected even being the same issuer and user though a different authentication request? Is there any way to get a token, in an automated way, without losing the flow of a user manual login?

Thanks in advance for your time and support!

I’ll have to remember to follow up here, but the summary I think is: client credentials are designed only for use on CLIs / maybe on servers, not on browser based apps. I think our docs on https://docs.inrupt.com might explain this better in the getting started guide.

Thanks for your reply @ThisIsMissEm !
I’ve checked the documentation but unfortunately I can’t find a clear reference to this topic, only this:
Authenticate (Node.js: Single-User App) - Authenticate with Statically Registered Client Credentials
I assume that clientAppId and clientAppSecret can’t be used to request a token as described in Client credentials - Community Solid Server.

Another question related to this issue, but I’m not sure if I should better create a new topic for it, is regarding the endpoint:

http://localhost:3000/idp/credentials/

It seems it’s not available by default in all SOLID servers IDP configuration

For example, if I try to reach:

https://solidcommunity.net/idp/credentials/

I get 403 (Forbidden) error. Or in the case of Inrupt:

https://login.inrupt.com/idp/credentials/

It directly throws:

{
    "error": "invalid_request",
    "error_description": "404 Not Found"
}

So my general question is how can I be sure that I will get a valid token independent of the provider/issuer in order to request not public resources on behalf of a client/user?

I will try to summarize all the problems I’ve faced which are related with the topic Music Platform and having a middleware server to interact with a SOLID Pod, but first I need to gather all existing related topics and information.

Yes, they can, you’ll want to follow: Authenticate (Node.js: Single-User App) — Inrupt JavaScript Client Libraries

Specifically:

await session.login({
       oidcIssuer: SOLID_IDENTITY_PROVIDER,
       clientId: id,
       clientSecret: secret
});

If you pass just clientName and redirectUrl, then we do Dynamic Client Registration, if you just pass a clientId and that is a URL, then we treat it as doing Solid OIDC via Client Identifier Document, if you pass both clientId and clientSecret (node.js only) then we treat it as “static” client credentials (essentially a pre-registered client).

Edit: I also want to point out that whilst you might be able to access session.clientAuthentication, this is actually a private API, and not part of our supported public API; You can see this in our code here: solid-client-authn-js/Session.ts at main · inrupt/solid-client-authn-js · GitHub

The Inrupt SDKs deliberately do not expose the credentials to the client, they’re kept in an in-memory closure, and deliberately not accessible outside of the SDK code, we only provide a session.fetch that applies the credentials to a fetch operation (in node.js this is currently powered by the cross-fetch package, but this is an implementation detail and changing soon-ish.

1 Like

Thank you so much for your reply, @ThisIsMissEm!

Now it’s clear for me that I should not use session.clientAuthentication to retrieve clientAppId and clientAppSecret after the user is logged in, but then my question is, how can I get that info from the user (id and secret) in the browser or it’s simply not possible?

On the other hand, I’ve been able to make the code below work in a separate node.js instance:

await session.login({
       oidcIssuer: SOLID_IDENTITY_PROVIDER,
       clientId: id,
       clientSecret: secret
});

The only problem is that it only works when I use the client Id and secret from the following screen (Inrupt’s server):

which means that I need to access https://login.inrupt.com/registration.html and manually register my app there. This is also a concern because it’s not an option for any SOLID server, only those which allow this feature, if I understand it correctly and eventually it can grant the app access to resources of users who have info in an Inrupt Pod but not “solidcommunity.net”, for example, or my own public Community Solid Server instance.

In summary to briefly describe my goal and not complicate things, I’ll try to explain the objective of the topic and the way I thought it would be possible to achieve.

I have a SOLID web app and a user that logs in within the app using its SOLID issuer account. After a valid authentication it has some credentials (client ID, secret, token, whatever…) that can be sent to a middleware server in order to act on behalf of this user. The middleware server can use clientId and clientSecret for example to get a token and from there interact with the SOLID Pod.

Here you can find also a diagram and the related inspiration from the original Music Platform thread

Maybe there’s a better approach to the described problem. I can think of the existing thread: Third-party resource verifying a session token to find similar references.

So, if I’m not wrong, it should be possible to run an API call about a user’s piece of information in a node.js app, including clientId and clientSecret, like the described at:

node dist/authenticatedScript.js --clientId <the client id> --clientSecret <the client secret>  --oidcIssuer <the issuer that issued the token> --resource <the private resource you want to access>

Thanks in advance and sorry for the verbose :stuck_out_tongue:

That is not a credential flow we support — sharing credentials between client and server like this isn’t secure, you’d instead need to do two authentication flows, one for the client, and one for the server-side, if you really need server-side processing.

We explicitly don’t expose the credentials or DPoP token to the user, as to limit the ability for something to exfiltrate those credentials from the page within the browser.

1 Like

Another option is to have your server component have it’s own WebID and to grant that WebID access to specific parts of the pod, such that the server acts as it’s own agent & not on behalf of the user

1 Like

Wouldn’t a possible flow be to only do auth on the server, and have the server mediate all Pod interactions? (i.e. removing the line between “Web App” and “Solid Server” in the above diagram?)

1 Like

You can indeed do completely server-rendered apps, if you so choose; but then your client is completely locked to your server working & being up.

2 Likes

Thanks @Vincent and @ThisIsMissEm for your input :slight_smile:

I’ll try to recap and propose a different approach based on the architecture that was mentioned in the Music Platform thread, which means the web app needs a centralized/common server where it can grab all the info about people registered sharing resources. With a decentralized network as SOLID aims, it is required to find solutions, as far as I’ve learnt, so services like YouTube, Spotify, etc can be built, otherwise there’s no way for the users to exchange assets among them without previously knowing each other, correct me if I’m wrong, please.

OPTION 1
The following animation describes the process of authentication with a SOLID server and shows how the web app is relying on a third party custom service to perform actions on behalf of the authenticated user. The Common Server can track all interactions made by the users because it’s responsible for them.

option-1

OPTION 2

On the other hand, as an alternative (see animation below) , the web app is talking directly to the SOLID server and notifying the Common Server about the interactions afterwards. The only issue or concern I see in this approach, is how can the Common Server verify when it’s notified by the client that the action was actually made by it ? (Steps 7 and 8 of the animation).

option-2

In summary, to make possible Option 1:

  1. “need to do two authentication flows, one for the client, and one for the server-side” @ThisIsMissEm

How can I do authentication of a specific user in the server after the user is authenticated in the browser? I’m not sure if it refers to creating a request for the client so it passes {issuer, user and password (encrypted)} and then handling login in the server. Or maybe something like this, @ThisIsMissEm ? => Authenticate (Node.js Web Server)
Is it what you mentioned with your comment “You can indeed do completely server-rendered apps” ?

  1. “server component have it’s own WebID and to grant that WebID access to specific parts of the pod, such that the server acts as it’s own agent & not on behalf of the user” @ThisIsMissEm

I’m not aware of this possibility, sorry. I’d need to dig into it a bit more.

  1. " to only do auth on the server, and have the server mediate all Pod interactions" @Vincent

How can you do auth only in the server @Vincent and still track clients interactions in a trusty way?

No, these are separate, you can just do authentication & data access through your own proprietary server, this does mean the client & server are strictly coupled, and that the server acts as the mediator to the data (i.e., the server acts on behalf of the user), and the client doesn’t directly interact with the pod. Some may deem this “less in the spirit of Solid”, but it is a design path that can be taken.

With OIDC, if you want the server-side to maintain the users’ session, typically, you’ll have the client create a session with the server (via session cookie) and then the server would redirect the client to start an OIDC flow, the server would then handle the return redirect, store the exchange the code for an access token / ID token & refresh token, which it stores securely (treat these as highly sensitive information: they are equivalent to passwords) in a database. Then all operations that the user wishes to do against the solid storage must go through your server to the resource server.

E.g.,

client (browser) <-> application server <-> authorization server 
                                            & resource server

This does make your client (browser) unable to directly interact with Solid resources in an authenticated context, and you’re essentially building an application against a REST API (that your application server provides).

The documentation at Authenticate (Node.js Web Server) — Inrupt JavaScript Client Libraries does explain it, but keep in mind that you’ll need to implement database-backed session storage (we only do in-memory out of the box), here is the interface you’ll need to conform to for that: https://github.com/inrupt/solid-client-authn-js/blob/main/packages/core/src/storage/IStorage.ts and you then pass it into the session code as the second argument to the various methods that work with sessions, e.g.,

// in memory:
const session = await getSessionFromStorage(req.session.sessionId);

// in database:
const session = await getSessionFromStorage(
  req.session.sessionId,
  myDatabaseBackedSessionStorage
);

There is also an alternative which is where the server acts on behalf of itself and has it’s own WebID and provisions it’s own JWT, which can then be used to interact with the pod as the servers’ WebID, and then the client interacts with their pod directly, but adds the servers’ WebID to the ACP/ACR/WAC permissions to allow the server to interact with resources using it’s WebID.

This is a pattern I’ve seen, but it is not widely documented, but is technically possible (the ESS Query Service actually does this).

2 Likes

OK, I think I get the point, thanks again for your great support @ThisIsMissEm! It looks like I have some homework for this week :stuck_out_tongue_winking_eye: