Authentication Server Side
Inrupt provides the solid-client-authn-node
library to authenticate in Node.js.
npm install @inrupt/solid-client-authn-node
For applications implementing Authorization Code Flow:
The application starts the login process by sending the user to the user’s Solid Identity Provider.
The user logs in to the Solid Identity Provider.
The Solid Identity Provider sends the user back to your application, where the application handles the returned authentication information to complete the login process.

Node.js Web Server: Multi-session Management
A Node.js web server can use the @inrupt/solid-client-authn-node
library to handle the user authentication flow and manage multiple sessions. In a multi-session context, the server maps requests to sessions. Typically, this is done attaching a cookie to the user’s browser.
From a session lifecycle perspective, there are two main types of requests:
those changing the session status (logging in or out),
and those performing an authenticated request from the session, without modifying its status.
By default, within the library code, all the session state is stored in memory, and lost on server restart. To persist the session state in external storage, you will need to register listeners for the authorizationRequest
and newTokens
events (see the dedicated section). These events allow you to capture the state needed to complete the login process and retrieve sessions in a clustered deployment where a sequence of requests may be directed to different nodes.
Starting the authentication flow:
Create a new Session for the user at the login endpoint. By default, the Session is periodically refreshed in the background using the refresh token. You should override this legacy behavior by specifying
keepAlive: false
as a Session option to the Session constructor. At this point, you should associate the user’s browser to theSession
identifier via a cookie, as the identifier is required in subsequent steps. You should also ensure that you capture theauthorizationRequest
event and persist the object it returns in external storage. This event is emitted as the user is redirected to their OpenID Provider. The payload contains the state of the session at this stage of the login process so that the login can be completed upon redirect from the OpenID Provider back to the application.Call the Session.login() function to start the login process. Pass in the following login options:
Set to the user’s Solid Identity Provider (where
handleRedirect
will send the user).Set to the location that the Solid Identity Provider will send the user back once logged in.
Set to a callback function that sends users to their Solid Identity Provider.
(Optional) Set to the display name for the client during the login process. When logging in, the user has to approve the client’s access to the requested data. The
clientName
is the name displayed during the approval step. IfclientName
is not provided, a random identifier is generated and used for the name.customScopes
(Optional) Set of custom scopes requested by the client in addition to the default ones. This allows for application-specific claims to be added to the ID Token by the OpenID Provider.
For other options available to the function, see ILoginInputOptions. This process redirects the user from your application to the Solid Identity Provider. Once redirected to the Solid Identity Provider, the user logs in. Upon successful login, the Solid Identity Provider sends the user back to your application.
Building a Session
object
Once the login process has been initiated, the Session
needed to handle the redirect from the Solid Identity Provider has to be rebuilt in the context of the operations managing the login process. There are multiple approaches to building the Session
object at that stage:
This requires using a Solid-OIDC Client ID. Make sure your Client Identifier is a URL pointing to a Client Identifier Document.
The Session
class has a static method Session.fromAuthorizationRequestState
returning a Session
instance. You should call it providing the authorizationRequestState
from your server’s persistent storage.
Completing the authentication flow:
Retrieve the session using one of the methods above.
Set up a listener to capture the tokens created during the login process and persist them in external storage if you are using this approach (preferred).
To complete the login process, call Session.handleIncomingRedirect(), passing in the URL of the page handling the redirect. Session.handleIncomingRedirect() collects the session information provided by the Solid Identity Provider. Because this information is appended to the URL as query parameters, pass the function the full URL.
After Session.handleIncomingRedirect() returns, your session is logged in.
Building a Session
object
Once the Session
has been logged in, it has to be rebuilt in the context of the operations making use of the user’s credentials to perform authenticated requests. There are multiple approaches to building the Session
object at that stage:
This requires using a Solid-OIDC Client ID. Make sure your Client Identifier is a URL pointing to a Client Identifier Document.
The Session
class has a static method Session.fromTokens
returning a Session
instance. You should call it providing tokens from your server’s persistent storage.
Making an authenticated requests:
Once logged in, the
Session
object provides a fetch() function that retrieves data using available login information. You can pass this fetch() function as an option to thesolid-client
functions (e.g., getSolidDataset, saveSolidDatasetAt) to include the user’s credentials with a request.
Logging a session out:
By default, the application may log the user out by clearing the resources associated to the user session. This is an ad-hoc process, specific to the application session management mechanism.
8. In addition, the application may log the user out of their OpenID Provider (see the Session Lifecycle section) using the logout
function exposed by @inrupt/solid-client-authn-node
.
Getting a list of all the sessions currently in storage:
How session identifiers and tokens are managed by the external persistent storage is out of scope of the library. Listing these sessions is dependent of the specifics of the chosen storage.
Managing the Session tokens
Tokens are very sensitive pieces of information because they allow access to user data. They must be stored securely: no third-party should have access to the tokens in storage.
Exchanging tokens with a Session
Session
Getting the tokens from the Session
When new tokens are issued (on login or on refresh), the newTokens
event is emitted by the events
emitter of the Session
instance. This event is specific to the Node.js environment, it is available in addition to the common events described in the Session Events section of the Authentication documentation.
When listening for the newTokens
event, your callback will receive a SessionTokenSet
object containing information about the new tokens, including the access token, ID token, refresh token, and expiration information.
Injecting the tokens into a Session
Session.fromTokens
is a static function that builds a Session
instance from a SessionTokenSet
object. If the tokens are not expired, the obtained Session
instance is able to make authenticated requests: session.info.isLoggedIn
is true
. If the tokens have expired, first use the refreshTokens
function to refresh the tokens, and then call Session.fromTokens
with the new tokens. Do not forget to update persistent storage with the new tokens as well.
Storing tokens
As part of its authentication lifecycle, the session makes use of two types of tokens:
Short-lived tokens
Access Token: Used to authenticate API requests to protected resources. Typically valid for a short period (minutes to hours).
ID Token: Contains user identity information, used for authentication purposes. Has a similar short lifespan to the Access Token.
Long-lived token
Refresh Token: Used to obtain new Access and ID tokens when they expire, without requiring the user to log in again. Usually valid for a longer period (days to weeks).
The Access Token is used directly by the Session
to perform authenticated requests. For performance reasons, your application may cache the short-lived tokens to reuse them across multiple requests from an authenticated user.
The Refresh Token is used by the Session
to refresh an expired Access Token. Refreshing a token requires a network round-trip with the Identity Provider. The Refresh Token is typically rotated when used: the Identity Provider issues a new Refresh Token when refreshing an Access Token, and the previous Refresh Token can no longer be used. In order to be able to perform authenticated operations without the user being present, the Refresh Token should be stored in a persistent storage.
Example
The following Express server example uses the @inrupt/solid-client-authn-node
library to log in to a Solid server.
const express = require("express");
const cookieSession = require("cookie-session");
const {
Session,
logout
} = require("@inrupt/solid-client-authn-node");
const app = express();
const port = 3000;
// The following snippet ensures that the server identifies each user's session
// with a cookie using an express-specific mechanism
app.use(
cookieSession({
keys: ["some secret used to sign cookies"],
})
);
// For simplicity, all tokens and session state are stored in-memory. In a real case,
// persistent storage would be used for long-lived tokens.
const sessionCache = new Map();
app.get("/login", async (req, res) => {
// 1. Create a new Session and ensure the request state is captured.
const session = new Session({ keepAlive: false }); // Turn off periodic refresh of the Session in background
req.session.sessionId = session.info.sessionId;
session.events.on("authorizationRequest", (authorizationRequestState) => {
sessionCache.set(req.session.sessionId, authorizationRequestState);
});
const redirectToSolidIdentityProvider = (url) => {
// Since we use Express in this example, we can call `res.redirect` to send the user to the
// given URL, but the specific method of redirection depends on your app's particular setup.
// For example, if you are writing a command line app, this might simply display a prompt for
// the user to visit the given URL in their browser.
res.redirect(url);
};
// 2. Start the login process; the redirect handler will handle sending the user to their
// Solid Identity Provider.
await session.login({
// After login, the Solid Identity Provider will send the user back to the following
// URL, with the data necessary to complete the authentication process
// appended as query parameters:
redirectUrl: `http://localhost:${port}/login/callback`,
// Set to the user's Solid Identity Provider; e.g., "https://login.inrupt.com"
oidcIssuer: "https://login.inrupt.com",
// Set to you application's Client Identifier
clientId: "https://example.org/client-id",
handleRedirect: redirectToSolidIdentityProvider,
});
});
app.get("/login/callback", async (req, res) => {
// 3. If the user is sent back to the `redirectUrl` provided in step 2,
// it means that the login has been initiated and can be completed. In
// particular, initiating the login stores the session state in storage,
// which means it can be retrieved as follows.
const authorizationRequestState = sessionCache[req.session.sessionId];
const session = await Session.fromAuthorizationRequestState(
authorizationRequestState,
req.session.sessionId
);
// 4. Ensure the tokens are cached.
session.events.on("newTokens", (tokenSet) => {
sessionCache.set(req.session.sessionId, tokenSet);
});
// 5. With your session back from storage, you are now able to
// complete the login process using the data appended to it as query
// parameters in req.url by the Solid Identity Provider:
await session.handleIncomingRedirect(`http://localhost:${port}${req.url}`);
// 6. `session` now contains an authenticated Session instance.
if (session.info.isLoggedIn) {
return res.send(`<p>Logged in with the WebID ${session.info.webId}.</p>`)
}
});
// 7. Once you are logged in, you can retrieve the tokens from the cache,
// and perform authenticated fetches.
app.get("/fetch", async (req, res) => {
if(typeof req.query["resource"] === "undefined") {
res.send(
"<p>Please pass the (encoded) URL of the Resource you want to fetch using `?resource=<resource URL>`.</p>"
);
}
const sessionTokenSet = sessionCache.get(req.session.sessionId);
const session = await Session.fromTokens(
sessionTokenSet,
req.session.sessionId,
);
console.log(
await session.fetch(req.query["resource"])
.then((response) => response.text())
);
res.send("<p>Performed authenticated fetch.</p>");
});
// 8. To log out a session, just retrieve the session, and
// call the .logout method.
app.get("/logout", async (req, res) => {
const sessionTokenSet = sessionCache.get(req.session.sessionId);
sessionCache.delete(req.session.sessionId);
await logout(
sessionTokenSet,
(url) => {
res.redirect(url);
}
);
});
app.listen(port, () => {
console.log(
`Server running on port [${port}]. ` +
`Visit [http://localhost:${port}/login] to log in to [login.inrupt.com].`
);
});
Last updated