Automating authentication with Client Credentials¶
One potential issue for scripts and other applications is that it requires user interaction to log in and authenticate. The CSS offers an alternative solution for such cases by making use of Client Credentials. Once you have created an account as described in the Identity Provider section, users can request a token that apps can use to authenticate without user input.
All requests to the client credentials API currently require you to send along the email and password of that account to identify yourself. This is a temporary solution until the server has more advanced account management, after which this API will change.
Below is example code of how to make use of these tokens.
It makes use of several utility functions from the
Solid Authentication Client.
Note that the code below uses top-level await, which not all JavaScript engines support,
so this should all be contained in an async function.
Generating a token¶
A token can be created either through the account page UI or programmatically via the API. This only needs to be done once — afterwards the token can be used for all future requests.
Via the account page¶
Navigate to your account page, by default at http://localhost:3000/.account/.
From there you can create a new client credentials token by providing a name and selecting
the WebID you want to associate with it.
The page will display the generated id and secret.
Store the secret somewhere safe as there is no way to request it again from the server!
Via the API¶
You can also generate a token programmatically by calling the client credentials API.
Before doing the step below, you already need to have an authorization value that you get after logging in to your account.
Below is an example of how this would work with the email/password API from the default server configurations.
// All these examples assume the server is running at `http://localhost:3000/`.
// First we request the account API controls to find out where we can log in
const indexResponse = await fetch('http://localhost:3000/.account/');
const { controls } = await indexResponse.json();
// And then we log in to the account API
const response = await fetch(controls.password.login, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ email: 'my-email@example.com', password: 'my-password' }),
});
// This authorization value will be used to authenticate in the next step
const { authorization } = await response.json();
The next step generates the token and assumes you have an authorization value as generated in the example above.
// Now that we are logged in, we need to request the updated controls from the server.
// These will now have more values than in the previous example.
const indexResponse = await fetch('http://localhost:3000/.account/', {
headers: { authorization: `CSS-Account-Token ${authorization}` }
});
const { controls } = await indexResponse.json();
// Here we request the server to generate a token on our account
const response = await fetch(controls.account.clientCredentials, {
method: 'POST',
headers: { authorization: `CSS-Account-Token ${authorization}`, 'content-type': 'application/json' },
// The name field will be used when generating the ID of your token.
// The WebID field determines which WebID you will identify as when using the token.
// Only WebIDs linked to your account can be used.
body: JSON.stringify({ name: 'my-token', webId: 'http://localhost:3000/my-pod/card#me' }),
});
// These are the identifier and secret of your token.
// Store the secret somewhere safe as there is no way to request it again from the server!
// The `resource` value can be used to delete the token at a later point in time.
const { id, secret, resource } = await response.json();
In case something goes wrong the status code will be 400/500 and the response body will contain a description of the problem.
Using the token to authenticate¶
Once you have the id and secret from the previous step,
there are two ways to use them to make authenticated requests.
Option A: Using @inrupt/solid-client-authn-node (recommended)¶
The @inrupt/solid-client-authn-node library
provides a Session class that handles the access token negotiation for you.
This is the simplest approach and is recommended for most use cases.
npm install @inrupt/solid-client-authn-node
import { Session } from '@inrupt/solid-client-authn-node';
// These are the ID and secret generated in the previous step.
const session = new Session();
await session.login({
clientId: id,
clientSecret: secret,
oidcIssuer: 'http://localhost:3000/',
});
if (session.info.isLoggedIn) {
// session.fetch is a standard fetch function that authenticates as your WebID.
const response = await session.fetch('http://localhost:3000/private');
}
// When done, log out to clean up the session.
await session.logout();
By default the session is periodically refreshed in the background.
You can disable this by passing keepAlive: false to the Session constructor.
Option B: Manual token negotiation with @inrupt/solid-client-authn-core¶
If you prefer to manage access tokens yourself,
you can use the lower-level utilities from
@inrupt/solid-client-authn-core.
Requesting an Access token¶
The ID and secret combination generated above can be used to request an Access Token from the server. This Access Token is only valid for a certain amount of time, after which a new one needs to be requested.
import { createDpopHeader, generateDpopKeyPair } from '@inrupt/solid-client-authn-core';
// A key pair is needed for encryption.
// This function from `solid-client-authn` generates such a pair for you.
const dpopKey = await generateDpopKeyPair();
// These are the ID and secret generated in the previous step.
// Both the ID and the secret need to be form-encoded.
const authString = `${encodeURIComponent(id)}:${encodeURIComponent(secret)}`;
// This URL can be found by looking at the "token_endpoint" field at
// http://localhost:3000/.well-known/openid-configuration
// if your server is hosted at http://localhost:3000/.
const tokenUrl = 'http://localhost:3000/.oidc/token';
const response = await fetch(tokenUrl, {
method: 'POST',
headers: {
// The header needs to be in base64 encoding.
authorization: `Basic ${Buffer.from(authString).toString('base64')}`,
'content-type': 'application/x-www-form-urlencoded',
dpop: await createDpopHeader(tokenUrl, 'POST', dpopKey),
},
body: 'grant_type=client_credentials&scope=webid',
});
// This is the Access token that will be used to do an authenticated request to the server.
// The JSON also contains an "expires_in" field in seconds,
// which you can use to know when you need request a new Access token.
const { access_token: accessToken } = await response.json();
Using the Access token to make an authenticated request¶
Once you have an Access token, you can use it for authenticated requests until it expires.
import { buildAuthenticatedFetch } from '@inrupt/solid-client-authn-core';
// The DPoP key needs to be the same key as the one used in the previous step.
// The Access token is the one generated in the previous step.
const authFetch = await buildAuthenticatedFetch(accessToken, { dpopKey });
// authFetch can now be used as a standard fetch function that will authenticate as your WebID.
// This request will do a simple GET for example.
const response = await authFetch('http://localhost:3000/private');
Other token actions¶
You can see all your existing tokens on your account page or by doing a GET request to the same API to create a new token. The details of a token can be seen by doing a GET request to the resource URL of the token.
A token can be deleted by doing a DELETE request to the resource URL of the token.
All of these actions require you to be logged in to the account.