Authenticate

To access private data on Solid Pods, you must authenticate as a user/agent who has been granted appropriate access to that data. For example, to write to a resource, the user must have Write Access on the Resource.

Authentication in Solid is based on OpenID Connect (OIDC), which means the authentication has the following flow:

  1. The application sends 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.

    Important

    The login is only complete after the user is redirected back to the app; i.e., your application must be reloaded between the start of the login and its completion.

Inrupt provides the following libraries for authentication:

  • solid-client-authn-browser library to authenticate in a browser.

  • solid-client-authn-node library to authenticate in Node.js.

In a Browser Environment

To authenticate in a browser environment, you can use the solid-client-authn-browser library.

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

    oidcIssuer

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

    redirectUrl

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

    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.

  2. To complete the login process, call handleIncomingRedirect() to collect the information provided by the Solid Identity Provider when it redirects the user back to your application. The session is logged in only after it handles the incoming redirect from the Solid Identity Provider.

  3. Once logged in, the fetch() function can retrieve data using the 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.

Note

By default, refreshing the current page logs out the user. To mitigate this and offer a better user experience, see Session Restore upon Browser Refresh.

Example

The following example uses the solid-client-authn-browser library to login to a Solid server. It passes the fetch() function as an option to the solid-client functions getSolidDataset and saveSolidDatasetAt to read and write data to a Pod where the logged-in user has the appropriate access.

import { handleIncomingRedirect, login, fetch, getDefaultSession } from '@inrupt/solid-client-authn-browser'
import { getSolidDataset, saveSolidDatasetAt } from "@inrupt/solid-client";

async function loginAndFetch() {
  // 1. Call the handleIncomingRedirect() function to complete the authentication process.
  //   If the page is being loaded after the redirect from the Solid Identity Provider
  //      (i.e., part of the authentication flow), the user's credentials are stored in-memory, and
  //      the login process is complete. That is, a session is logged in 
  //      only after it handles the incoming redirect from the Solid Identity Provider.
  //   If the page is not being loaded after a redirect from the Solid Identity Provider, 
  //      nothing happens.
  await handleIncomingRedirect();

  // 2. Start the Login Process if not already logged in.
  if (!getDefaultSession().info.isLoggedIn) {
    // The `login()` redirects the user to their identity provider;
    // i.e., moves the user away from the current page.
    await login({
      // Specify the URL of the user's Solid Identity Provider; e.g., "https://broker.pod.inrupt.com" or "https://inrupt.net"
      oidcIssuer: "https://broker.pod.inrupt.com",
      // Specify the URL the Solid Identity Provider should redirect to after the user logs in,
      // e.g., the current page for a single-page app.
      redirectUrl: window.location.href,
      // Pick an application name that will be shown when asked 
      // to approve the application's access to the requested data.
      clientName: "My application"
    });
  }

  // 3. Make authenticated requests by passing `fetch` to the solid-client functions.
  // The user must have logged in as someone with the appropriate access to the specified URL.
  
  // For example, the user must be someone with Read access to the specified URL.
  const myDataset = await getSolidDataset(
    "https://pod.inrupt.com/docsteam/profile/card", {
    fetch: fetch
  });

  // ...
  
  // For example, the user must be someone with Write access to the specified URL.
  const savedSolidDataset = await saveSolidDatasetAt(
    "https://pod.inrupt.com/docsteam/profile/card",
    myChangedDataset, {
    fetch: fetch
  });
}

loginAndFetch();

For working examples, you can look at the sample apps distributed with the code:

In a Node.js Environment

To authenticate in a Node.js environment, you can use the solid-client-authn-node library.

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.

Session Management

To manage sessions, solid-client-authn-node automatically stores a mapping of the Session ID to Session when starting the login process. To retrieve the correct session for a user, solid-client-authn-node provides the getSessionFromStorage() function that takes a Session ID as its argument.

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

  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.

  5. After Session.handleIncomingRedirect() returns, your session is logged in.

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

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

  8. To get a list of all the sessions currently in storage, you can 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 redirectHandler = (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}/handle-redirect`,
    // Set to the user's Solid Identity Provider; e.g., "https://broker.pod.inrupt.com" 
    oidcIssuer: "https://broker.pod.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: redirectHandler,
  });
});

app.get("/handle-redirect", 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 an 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) => {
  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 [broker.pod.inrupt.com].`
  );
});

Node.js Script: Single-User Script

A Node.js script can use the solid-client-authn-node library to handle the user authentication flow.

  1. Create a new Session for the user.

  2. Call the Session.login() function, passing in ILoginInputOptions.

    Although you can pass in options to start the authentication flow, you can also pass in the following options to have an authenticated session without the manual, in-browser redirect interactions:

    • An already-registered clientId, which identifies your application to the Solid Identity Provider.

    • An already-registered clientSecret, associated to the Client ID.

    • An already-registered refreshToken, which your application can use to get an Access Token. Access Tokens allows you to access Resources for which you have been authorized.

    • oidcIssuer, the Solid Identity Provider where your Client ID, Client Secret, and Refresh Token have been registered.

    When login() returns, your session should be logged in and able to make authenticated requests.

Warning

Safeguard your clientId, clientSecret, and refreshToken values. Do not share these with any third parties as anyone with your clientId, clientSecret, and refreshToken values can impersonate you and act fully on your behalf.

Example

The following single-user script calls Session.login() with:

  • clientId, clientSecret, refreshToken, and

  • oidcIssuer.

Warning

Safeguard your clientId, clientSecret, and refreshToken values. Do not share these with any third parties as anyone with your clientId, clientSecret, and refreshToken values can impersonate you and act fully on your behalf.

const { Session } = require("@inrupt/solid-client-authn-node");

// 1. Get the authenticated credentials: myClientId, myClientSecret, myRefreshToken 
// ...
// ...
// Important: Safeguard these credentials.

const session = new Session();
session.login({
  // 2. Use the authenticated credentials to log in the session.
  clientId: myClientId,
  clientSecret: myClientSecret,
  refreshToken: myRefreshToken,
  // Set oidcIssuer to the Solid Identity Provider associated with the credentials.
  oidcIssuer: "https://broker.pod.inrupt.com",
  // If the refresh token is updated by the Identity Provider, this callback gets invoked.
  onNewRefreshToken: (newToken) => { 
    console.log("New refresh token: ", newToken);
  }
}).then(() => {
  if (session.info.isLoggedIn) {
    // 3. Your session should now be logged in, and able to make authenticated requests.
    session
      // You can change the fetched URL to a private resource, such as your Pod root.
      .fetch(session.info.webId)
      .then((response) => {
        return response.text();
      })
      .then(console.log);
  }
});

Handling Refresh Token Rotation

Refresh Tokens allow you to get a new Access Token when the previous Access Token expires. Refresh Tokens should be treated as sensitive data since the new Access Tokens allow authenticated access to resources. As such, it is considered good practice for Solid Identity Provider to implement a “Refresh Token rotation” process. That is, each time the client application uses a Refresh Token to get a new Access Token, a new Refresh Token is also returned. The previous Refresh Token is invalidated, and can no longer be used to get Access Tokens.

@inrupt/solid-client-authn-node has an internal mechanism to manage refresh tokens in the Session’s storage. For the Session’s storage, you can pass in a storage as an option to the Session constructor or use the default storage, which is an in-memory storage. If you pass in a persistent storage, the refresh token management is transparent. However, for NodeJS scripts that use the Session’s default in-memory storage, the storage (and hence the refresh token) is lost when the program stops.

As an alternative to providing a persistent storage to the Session constructor, you can pass in the onNewRefreshToken callback to the constructor instead. Then, each time a new refresh token is issued, the onNewRefreshToken callback is invoked with the new refresh token as a parameter. The onNewRefreshToken option allows you to run custom code to handle the refresh token as appropriate.

Additional Reference

@inrupt/generate-oidc-token Utility

As mentioned in the introduction, Solid authentication involves redirecting the user in a browser to the Solid Identity Provider, and after the user logs in, redirecting the user back to the specified URL.

To help during the development of a single-user Node.js script, Inrupt provides a utility @inrupt/generate-oidc-token. The standalone utility takes a user through the authentication flow and upon successful authentication, outputs the Client ID, Client Secret , and Refresh Token.

Note

  • The utility only supports logging in to Solid-OIDC compliant Identity Provider, such as https://broker.pod.inrupt.com, https://broker.pod-compat.inrupt.com, or https://solidcommunity.net.

  • The utility outputs credentials that expire after 3 days.

  1. In a terminal, run generate-oidc-token utility to log in to a

    npx @inrupt/generate-oidc-token
    

    The utility prompts the user for some information.

  2. Enter the appropriate values to the prompts.

    When finished, the utility asks the user to visit a site in a browser.

  3. Open the browser and visit the site. Log in to the Solid Identity Provider.

  4. Upon login, return to the terminal and you should see the following information:

    • Client ID

    • Client Secret

    • Refresh Token

Warning

Safeguard your Client ID, Client Secret, and Refresh Token values. Do not share these with any third parties as anyone with your Client ID, Client Secret, and Refresh Token values can impersonate you and act fully on your behalf.