Authenticate (Node.js Web Server)#

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:

  1. The application starts the login process by sending the user to the user’s Solid Identity Provider.

  2. The user logs in to the Solid Identity Provider.

  3. The Solid Identity Provider sends the user back to your application, where the application handles the returned authentication information to complete the login process.

Login Flow: 1) Start Login by redirecting user to Solid Identity Provider. 2) User logs in.  3) Solid Identity Provider redirects the user back to the application to handle the returned authentication information.

Node.js Web Server: Multi-session Management#

A Node.js web server can use the solid-client-authn-node library to handle the user authentication flow and manage multiple sessions.

  1. Create a new Session for the user at the login endpoint.

  2. Call the Session.login() function to start the login process. Pass in the following login options:

    oidcIssuer

    Set to the user’s Solid Identity Provider (where handleRedirect will send the user).

    redirectUrl

    Set to the location that the Solid Identity Provider will send the user back once logged in.

    handleRedirect

    Set to a callback function that sends users to their Solid Identity Provider.

    clientName

    (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. If clientName is not provided, a random identifier is generated and used for the name.

    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.

  3. Use getSessionFromStorage() to retrieve the correct session for the user. Pass in the Session ID.

  4. 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.

To make 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 the solid-client functions (e.g., getSolidDataset, saveSolidDatasetAt) to include the user’s credentials with a request.

To log out a session:

Call the session’s logout() method.

Logging out a session removes the session from the storage as well as disables its access to private resources.

To get a list of all the sessions currently in storage:

Call getSessionIdFromStorageAll().

Example#

The following Express server example uses the solid-client-authn-node library to log in to a Solid server.

Note

In the example, the cookie-session Express middleware is used to associate the session ID to the user’s browser through a cookie.

const express = require("express");
const cookieSession = require("cookie-session");

const { 
  getSessionFromStorage,
  getSessionIdFromStorageAll,
  Session
} = 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({
    name: "session",
    // These keys are required by cookie-session to sign the cookies.
    keys: [
      "Required, but value not relevant for this demo - key1",
      "Required, but value not relevant for this demo - key2",
    ],
    maxAge: 24 * 60 * 60 * 1000, // 24 hours
  })
);


app.get("/login", async (req, res, next) => {
  // 1. Create a new Session
  const session = new Session();
  req.session.sessionId = session.info.sessionId;
  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 depend 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; 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",
    // Pick an application name that will be shown when asked 
    // to approve the application's access to the requested data.
    clientName: "Demo app",
    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 in storage, 
  //    which means it can be retrieved as follows.
  const session = await getSessionFromStorage(req.session.sessionId);

  // 4. 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}`);

  // 5. `session` now contains an authenticated Session instance.
  if (session.info.isLoggedIn) {
    return res.send(`<p>Logged in with the WebID ${session.info.webId}.</p>`)
  }
});

// 6. Once you are logged in, you can retrieve the session from storage, 
//    and perform authenticated fetches.
app.get("/fetch", async (req, res, next) => {
  if(typeof req.query["resource"] === "undefined") {
    res.send(
      "<p>Please pass the (encoded) URL of the Resource you want to fetch using `?resource=&lt;resource URL&gt;`.</p>"
    );
  }
  const session = await getSessionFromStorage(req.session.sessionId);
  console.log(await (await session.fetch(req.query["resource"])).text());
  res.send("<p>Performed authenticated fetch.</p>");
});

// 7. To log out a session, just retrieve the session from storage, and 
//    call the .logout method.
app.get("/logout", async (req, res, next) => {
  const session = await getSessionFromStorage(req.session.sessionId);
  session.logout();
  res.send(`<p>Logged out.</p>`);
});

// 8. On the server side, you can also list all registered sessions using the
//    getSessionIdFromStorageAll function.
app.get("/", async (req, res, next) => {
  const sessionIds = await getSessionIdFromStorageAll();
  for(const sessionId in sessionIds) {
    // Do something with the session ID...
  }
  res.send(
    `<p>There are currently [${sessionIds.length}] visitors.</p>`
  );
});

app.listen(port, () => {
  console.log(
    `Server running on port [${port}]. ` +
    `Visit [http://localhost:${port}/login] to log in to [login.inrupt.com].`
  );
});