# Inrupt Documentation

Documentation for Inrupt's Enterprise Solid Server and developer tools.

Inrupt builds enterprise software for secure, interoperable data management based on the [Solid Protocol](https://solidproject.org/TR/protocol). This site covers the Enterprise Solid Server, client SDKs, and supporting guides.

## Enterprise Solid Server

The Enterprise Solid Server (ESS) is an enterprise-grade data platform for secure, interoperable data storage. ESS gives individuals and organizations Pods — personal data stores they control — with fine-grained access control, auditing, and support for AI agent integration via MCP.

[Go to ESS documentation →](https://docs.inrupt.com/ess/)

## Developer Tools

Client libraries for building Solid applications on top of ESS or any Solid-compliant server.

{% content-ref url="/pages/ZmhhnEfVdnvV5bB665sV" %}
[Javascript SDK](/sdk/javascript-sdk)
{% endcontent-ref %}

{% content-ref url="/pages/Q6XSMDuQF610D74CTZjy" %}
[Java SDK](/sdk/java-sdk)
{% endcontent-ref %}

## Guides and Reference

{% content-ref url="<https://github.com/inrupt/docs-gitbook/tree/main/guides/README.md>" %}
<https://github.com/inrupt/docs-gitbook/tree/main/guides/README.md>
{% endcontent-ref %}

{% content-ref url="/pages/IYgBFsXmb5vbEKSBCLoE" %}
[Authorization/Access Control](/security/authorization)
{% endcontent-ref %}

{% content-ref url="/pages/7PKZaxnnUDGdJ8gPDOqL" %}
[Glossary](/reference/glossary)
{% endcontent-ref %}

## Support

For support, visit the [Inrupt Support Center](https://inrupt.atlassian.net/servicedesk/customer/portals).


# WebID Document Best Practices

This guide provides advice and best practices for creating and maintaining WebID Documents.

It will cover the following:

* Basic makeup of a WebID Document for use in a managed ecosystem
* How it is used in Solid Identity
* Considerations for architects on protecting WebID Documents on behalf of users in managed ecosystems.

## What is a WebID? <a href="#i81ddfcv2y03" id="i81ddfcv2y03"></a>

A WebID is a URI that uniquely identifies an entity in a Solid ecosystem.

{% embed url="<https://youtu.be/ONwLqbiuPAM>" %}

A WebID Document is an [RDF](/reference/glossary#rdf-resource) document dereferenced from the WebID URI. This WebID Document allows Solid applications and services to discover services used or trusted by the entity that controls the WebId, such as their trusted Identity Providers, Solid Pods, and potentially some public personal information.

The WebID Document is an RDF public document and should be expressed in [Turtle](/reference/glossary#turtle) format, although other formats may be available.

### **WebID Document Content**

The authority governing the Solid ecosystem should have control over and define the content of the WebID Document.

A WebID Document will likely contain three pieces of information:

1. A list of trusted OIDC issuers used to assert their identity for access to Solid servers and apps.
2. Any Solid Pods associated with that particular WebID.
3. The type of entity the WebID belongs to. This could be an agent, person, or organization. By default, a WebID identity is classified as an [Agent](/reference/glossary#agent) (this could be a human or machine), and we use the [FOAF](http://xmlns.com/foaf/0.1/) vocabulary to describe it.

Optionally, the profile could include a list of extended WebID Documents. These [extended profiles](/guides/webid-document-best-practices/managing-webid-profiles#extended-profiles) are stored in Pods and could be publicly accessible or protected by access control.

#### **Example of the minimum requirement for a WebID Document**

```turtle
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix schema: <http://schema.org/> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix person: <http://localhost:8080/data/person/> .

<https://id.inrupt.com/dave> a foaf:Agent ;
	<http://www.w3.org/ns/pim/space#storage> <https://storage.inrupt.com/bb79f34c-3e25-4b9d-9b68-0193e5491111/> ;
	<http://www.w3.org/ns/solid/terms#oidcIssuer> <https://login.inrupt.com> ;
	foaf:isPrimaryTopicOf <https://storage.inrupt.com/bb79f34c-3e25-4b9d-9b68-0193e5491111/profile> .

```

## **OIDC Issuer**

`<http://www.w3.org/ns/solid/terms#oidcIssuer> <https://login.inrupt.com> ;`

The WebID Document must contain at least one Solid OIDC identity provider. Without this, the entity cannot use any Solid services that require authentication.

As part of the authentication flow, Solid Applications and Solid Servers will ensure the issuer providing the identity token is present in the WebID Document. If it is not present, the session will terminate. Only trusted IDPs must be added to the WebID Document.

The WebID Document can list multiple Identity Providers, which means more than one Solid OIDC provider can assert the same identity for a given entity or user. Assuming that all Identity Providers are onboarded, trusted, and are known providers of the WebID service, the user or application can choose which IDP to use.

**Solid Pod Storage**

The WebID Document is the primary mechanism to discover Solid Pods so that applications and services can write or fetch data. In most deployments, the user will have one Pod but also may have many. If multiple Pods are present, the WebID Document must contain some metadata to aid in selecting a Pod. A common way to achieve this is by using `rdfs:label` as a description property to provide human-readable labels written by the operator. For instance, if users utilize a health app, they could choose the "Health Wallet" rather than a "Finance Wallet."

```turtle
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix schema: <http://schema.org/> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix person: <http://localhost:8080/data/person/> .

<https://id.inrupt.com/LindaSmith> a foaf:Person ;
	<http://www.w3.org/ns/pim/space#storage> <https://storage.inrupt.com/bb79f34c-3e25-4b9d-9b68-0193e5491111/>, <https://storage.inrupt.com/bb79f34c-3e25-4b9d-9b68-0193e5491112/> ;
	<http://www.w3.org/ns/solid/terms#oidcIssuer> <https://login.inrupt.com> ;
	foaf:isPrimaryTopicOf <https://storage.inrupt.com/bb79f34c-3e25-4b9d-9b68-0193e5491111/profile> .

<https://storage.inrupt.com/bb79f34c-3e25-4b9d-9b68-0193e5491111/> a <http://www.w3.org/ns/pim/space#ControlledStorage> ;
	rdfs:label "National Health Service Solid Wallet Storage"@en .

<https://storage.inrupt.com/bb79f34c-3e25-4b9d-9b68-0193e5493912/> a <http://www.w3.org/ns/pim/space#ControlledStorage> ;
	rdfs:label "Ward Bank Solid Wallet Storage"@en .

```

## **Personal Information**

The WebID Document associated with a WebID may contain personal details about the user identified by the WebID. Users have the option to share their personal information in the WebID Document. For personal information that is better kept private (such as phone number or private address), ESS can create a Extended-Profile-Document in the user's Wallet which can be placed under access control.

## **Vocabulary**

To promote application interoperability, we recommend a set of [vocabularies](/reference/vocabulary) to use in the WebID Document. As mentioned above, three main concepts are referenced in a WebID Document:

* The type of entity
* The identity providers for the users to authenticate
* The list of Solid Pods that belong to that particular WebID.

The entities that hold WebID Documents can be classified as an agent, a person, or an organization using the [FOAF](http://xmlns.com/foaf/0.1/) vocabulary.

To describe the different Identity Providers, we use the[ Solid term](http://www.w3.org/ns/solid/terms) vocabulary and specifically utilize the property *oidcIssuer*. The Workspace Ontology is the recommended vocabulary for expressing Solid Wallet Storage, with *pim:storage* being the associated property. Finally, the WebID Service administrator can decide the term for a specific property (predicate) for a possible extended profile(s).

## **WebID Document Edits**

Adding untrusted OIDC Issuers or unauthorized non-user-controlled Wallet Storage to the WebID Document could constitute a security threat.

As it is unlikely that regular users will understand this significance, the governing body overseeing the managed Solid ecosystem must enforce strict controls over how modifications are made to the WebID Document. Secure tooling or applications must be created to wrap up the WebID service.

Considering the severe risks involved, regular users must be only granted permission to edit their WebID Document under extremely controlled circumstances. A WebID editor application built by the governing body should be responsible for validating and approving any potential changes. The validation process should ensure that the agent making the changes is allowed to do so and that the changes themselves are in line with the ecosystem’s security policies.

As mentioned in the document, multiple OIDC issuers can be listed in a single WebID Document.

Whilst ESS has additional guards to ensure untrusted Issuers cannot be used to authenticate users in the form of service allow lists, this should not be used as the only line of defense. Allow lists can become large and cumbersome in complex deployments.

If the Issuer Allow List grows significantly, one potential solution could be [OIDC Federation](https://openid.net/specs/openid-connect-federation-1_0.html), which would allow multiple federations and various Identity Providers. This specification is still in its early stages, but it would instruct establishing trust among a group of IdPs.


# Managing WebID Profiles

Per the [Solid WebID Profile specification](https://solid.github.io/webid-profile/#introduction), a Solid profile describes an agent and can consist of a [WebID Profile](/reference/glossary#webid-profile) document and [extended profile documents](https://solid.github.io/webid-profile/#extended-profile-documents).

For example, Inrupt’s ESS and [PodSpaces](https://github.com/inrupt/docs-gitbook/tree/main/guides/webid-document-best-practices/broken-reference/README.md) creates both a WebID Profile (upon creation of the WebID) and an extended profile (upon creation of the Pod). This extended profile is discoverable from the WebID Profile.

### WebID Profile

A WebID is a unique URL that identifies an agent in the Solid ecosystem. Dereferencing the [WebID](/reference/glossary#webid) yields the **publicly** readable WebID Profile. A WebID Profile is an [RDF Resource](/reference/glossary#rdf-resource) that contains data about the user.

{% hint style="info" %}
Although an RDF Resource, the WebID Profile is not necessarily hosted on a Solid Pod and may not necessarily be a Solid Resource per the [Solid Protocol](https://solidproject.org/TR/protocol). As such, Solid applications <mark style="color:red;">**cannot**</mark> rely on the [Solid Protocol to read and write](https://solidproject.org/TR/protocol#reading-writing-resources) the WebID profile.

For example, starting in version ESS 2.0, Inrupt’s ESS or PodSpaces WebIDs dereference to WebID Profiles that that are <mark style="color:red;">**not**</mark> hosted on a Solid Pod. ESS/PodSpaces places restrictions on WebID Profile modifications, including which applications can perform modifications. To read these WebID Profiles, applications must make <mark style="color:red;">**un**</mark>authenticated requests.
{% endhint %}

#### Extended Profiles

Like the WebID Profile, an [extended profile](https://solid.github.io/webid-profile/#extended-profile-documents) is also an RDF Resource that contains data about the user. However, the extended profile is hosted on a Solid Pod and is a Solid Resource per the Solid Protocol. That is, applications **can** rely on the [Solid Protocol to read and write](https://solidproject.org/TR/protocol#reading-writing-resources) the extended profile.

Unlike the WebID Profile, which is publicly readable, read and write access to the extended profile is managed as user would manage access to any other Solid Resource in the Pod.

For example, the extended profile created by Inrupt’s ESS and PodSpaces is private (read and writable by the agent only) by default.

### Read WebID Profile and Extended Profiles

The `@inrupt/solid-client` library provides [getProfileAll](https://inrupt.github.io/solid-client-js/modules/profile_webid.html#getprofileall) to perform the fetch of the WebID Profile and the linked extended profiles.

Note

Because an extended profile is a Solid Resource, it is possible to use [getSolidDataset](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#getsoliddataset) to get a specific extended profile. However, since the app would need to introspect the WebID Profile to get the extended profile’s URL, which is already handled by [getProfileAll](https://inrupt.github.io/solid-client-js/modules/profile_webid.html#getprofileall), prefer using [getProfileAll](https://inrupt.github.io/solid-client-js/modules/profile_webid.html#getprofileall) for extended profile as well.

The returned the WebID Profile and the extended profiles can be introspected using the read functions for SolidDataset.

<table data-header-hidden><thead><tr><th width="188.0128173828125"></th><th></th></tr></thead><tbody><tr><td><a href="https://inrupt.github.io/solid-client-js/modules/profile_webid.html#getprofileall">getProfileAll</a></td><td><p>Returns the WebID profile (as a SolidDataset) and the linked extended profiles (as an array of SolidDatasets).</p><ul><li>For the WebID profile, the function performs an <mark style="color:red;"><strong>un</strong></mark>authenticated fetch.</li><li>For the extended profiles linked from the WebID Profile, the function performs an authenticated fetch if an authenticated fetch function is passed as an option; otherwise, the function performs an unauthenticated fetch. Only those extended profiles that are readable by the user are returned.</li></ul></td></tr><tr><td><a href="https://inrupt.github.io/solid-client-js/modules/thing_thing.html#getthing">getThing</a></td><td>Gets a data entity/Thing from a WebID Profile or extended profile.</td></tr><tr><td><a href="https://inrupt.github.io/solid-client-js/modules/thing_thing.html#getthingall">getThingAll</a></td><td>Get all data entities/Things from a WebID Profile or extended profile.</td></tr><tr><td><a href="https://inrupt.github.io/solid-client-js/modules/thing_get.html">get*(thing, property)</a></td><td><p>Gets the value(s) for the specified Property from a Thing.</p><p>For a list of the <code>get</code> functions, see <a href="https://inrupt.github.io/solid-client-js/modules/thing_get.html">thing/get module</a>.</p></td></tr></tbody></table>

#### Example

The following example gets the WebID profile and the linked extended profile.

```javascript
import { getDefaultSession, fetch } from "@inrupt/solid-client-authn-browser";

import {
  getSourceUrl,
  getThing,
  getThingAll,
  getUrlAll,
  getSourceUrl
} from "@inrupt/solid-client";

import { SOLID } from "@inrupt/vocab-solid";

// Note: Login code has been omitted for brevity.

async function getMyProfiles(webId) {

  try {

    // 1. Get WebID of the logged in user.
    // The example assumes the user is logged in.
    // As such, getDefaultSession().info.webId is NOT null and
    // fetch (associated with the default Session) is an authenticated fetch.

    const webId = getDefaultSession().info.webId;

    // 2. Get the WebID Profile and the extended profiles listed in the WebID Profile.
    //
    // - For WebID Profile, getProfileAll performs an unauthenticated fetch.
    // - For extended profiles, getProfileAll performs either:
    //   - an unauthenticated fetch of the extended profiles if
    //     the passed in fetch is omitted or fetch is unauthenticated,
    //   - authenticated fetch if the passed in fetch is authenticated.

    const profiles = await getProfileAll(
      webId,
      { fetch }
    );

    // Step 3. Read from the WebID profile.
    // - Get the WebID profile from the returned profiles.
    // - Read the WebID Profile as a Thing.
    // - Read the OpenID Provider(s) listed in the WebID Profile.
    const webIDProfileSolidDataset = profiles.webIdProfile;
    const webIdThing = getThing(webIDProfileSolidDataset, webId);
    const issuers = getUrlAll(webIdThing, SOLID.oidcIssuer);

    // ...

    // Step 4. Read from the extended profiles.
    // - Get the array of extended profiles from the returned profiles.
    // - Loop through the extended profiles.

    const extendedProfilesSolidDatasets = profiles.altProfileAll;

    extendedProfilesSolidDatasets.forEach((extendedProfileSolidDataset) => {
      console.log(getSourceUrl(extendedProfileSolidDataset));
      const thingsInExtendedProfile = getThingAll(extendedProfileSolidDataset);
       thingsInExtendedProfile.forEach((thing) => {
         // .. do something
       });
    })

  } catch (error) {
    //...
  }
}
```

### Update Extended Profiles

The WebID Profile may not be a Solid resource. As such, Solid applications <mark style="color:red;">**cannot**</mark> rely on the [Solid Protocol to read and write](https://solidproject.org/TR/protocol#reading-writing-resources) the WebID Profile. In addition, the modification of WebID Profile may be governed by other restrictions, such that an arbitrary Solid applications cannot perform the modification.

Instead, Solid applications writing user profile data should target extended profiles. Extended profiles are Solid resources. As such, Solid applications **can** rely on the [Solid Protocol to read and write](https://solidproject.org/TR/protocol#reading-writing-resources) extended profiles, and users can manage read and write access to their extended profiles like any other Solid resource.

For example, WebIDs created by ESS/PodSpaces dereference to WebID Profiles that that are **not** hosted on a Solid Pod. Furthermore, ESS/PodSpaces places restrictions on WebID Profile modifications, including which applications can perform modifications. To support the modification of user data (as well as managing access control of that data) , ESS/PodSpaces creates a default extended profile.

To write update extended profiles, Solid applications can use the same functions as they would to write any SolidDatasets. For example:

<table data-header-hidden><thead><tr><th width="209.10650634765625"></th><th></th></tr></thead><tbody><tr><td><a href="https://inrupt.github.io/solid-client-js/modules/thing_thing.html#functions">Thing functions</a></td><td>To add/update/remove Things to the extended profile SolidDataset.</td></tr><tr><td><a href="https://inrupt.github.io/solid-client-js/modules/thing_set.html#functions">set*(thing, property)</a></td><td><p>To set the value(s) for the specified Property for a Thing.</p><p>For a list of the <code>set</code> functions, see <a href="https://inrupt.github.io/solid-client-js/modules/thing_set.html#functions">thing/set module</a>.</p></td></tr><tr><td><a href="https://inrupt.github.io/solid-client-js/modules/thing_remove.html#functions">remove*(thing, property)</a></td><td><p>To remove value(s) for the specified Property from a Thing.</p><p>For a list of the <code>remove</code> functions, see <a href="https://inrupt.github.io/solid-client-js/modules/thing_remove.html#functions">remove*(thing, property)</a>.</p></td></tr><tr><td><a href="https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#savesoliddatasetat">saveSolidDatasetAt</a></td><td>To save an existing extended profile.</td></tr></tbody></table>

#### Example

The following example gets the extended profile linked from a WebID Profile and updates the extended profile. The example assumes only 1 extended profile.

```javascript
import { getDefaultSession, fetch } from "@inrupt/solid-client-authn-browser";

import {
  getProfileAll,
  getSourceUrl,
  getThing,
  setUrl,
  setThing,
  saveSolidDatasetAt,
} from "@inrupt/solid-client";

import { FOAF, RDF } from "@inrupt/vocab-common-rdf";

// Note: Login code has been omitted for brevity. See the Prerequisite section above.
// ...

async function updateExtendedProfile() {

  try {
    // 1. Get WebID of the logged in user.
    // The example assumes the user is logged in.
    // As such, getDefaultSession().info.webId is NOT null and
    // fetch (associated with the default Session) is an authenticated fetch.

    const webId = getDefaultSession().info.webId;

    // 2. Get the WebID Profile and the extended profiles listed in the WebID Profile.
    //
    // - For WebID Profile, getProfileAll performs an unauthenticated fetch.
    // - For extended profiles, getProfileAll performs either:
    //   - an unauthenticated fetch of the extended profiles if
    //     the passed in fetch is omitted or fetch is unauthenticated,
    //   - authenticated fetch if the passed in fetch is authenticated.

    const profiles = await getProfileAll(webId, { fetch });

    // Step 3. Write to the extended profile.
    // The example assumes only 1 extended profile.
    // a. Get the extended profile.
    // b. Get the user data Thing (identified by the user's WebID) contained in the extended profile.
    // c. Set a Property to this user data.
    // d. Update the myExtendedProfile with the new Property
    // e. Save the updated extended profile

    let myExtendedProfile = profiles.altProfileAll[0];
    let userDataThing = getThing(myExtendedProfile, webId);

    userDataThing = setUrl(
      userDataThing,
      "https://some.property",
      "https://some.value"
    );

    myExtendedProfile = setThing(
      myExtendedProfile,
      userDataThing
    );

    await saveSolidDatasetAt(
      getSourceUrl(myExtendedProfile),
      myExtendedProfile,
      { fetch: fetch }             // fetch from authenticated Session
    );
  } catch (error) {
    //...
  }
}
```


# Identity in Solid

### Definitions <a href="#alfw5xej99kx" id="alfw5xej99kx"></a>

An **application** in Solid can be both a user-piloted app, like a mobile app, a traditional webapp with a user interface (either browser only or a browser app with a backend component) or a back-end service that acts autonomously with its own identity and has no user interface. Application is a broad term that just means software.

An **agent** in Solid is something that has autonomy. Both people and applications can be agents, as can organizations, devices or groups of people. We identify agents with a WebID.

An **entity** in Solid is a thing that exists. This can be a person, but it can also be a building or a car. Entities may or may not be agents. Entities have WebIDs.

A **client** in the broadest sense, is an application that utilizes the services of another. For instance, a backend application will be the *client* of an identity provider. A browser app will be the *client* of the Solid server and also a *client* of an identity provider. It is important to note that a client can mean different things in different contexts so the term is best avoided unless talking about specific contexts or flows (like the OAuth2.0 *client credential grant* for example).

**Piloted Apps**

A concrete example of this would be a browser-based web application. The user would be logged in with their own identity (using a WebID), while the web application (the client in this case) is distinctly identified in its own right (using its Client ID) and acts on behalf of the user in real-time. This is the most common interaction we see in Solid applications. In this case, the Solid server that the application is interacting with would see two identities: The user’s WebID and the application’s Client ID.

An **autonomous software agent** is an application that maintains its own identity and can act independently through pre-programmed logic. An example of this would be a back-end app accessing data in Pods to achieve some business function. Practically, this means that it has a WebID. It does not have a Client ID because it has a primary identity of its own, and there is no ‘medium’ through which it needs to act (like a user acting out their intentions through a browser app).

**Unpiloted autonomous Apps**

Unpiloted autonomous apps are applications that are written to act with the identity of a human user. For example, a script I might write to log into my photo app ‘as me’ and update photo metadata.

The app is autonomous but acts with the identity of the user as its primary identity.

One way in which authentication can be facilitated, is to assign the application a Client ID and secret (through an [OAuth 2.0 client credentials grant](https://datatracker.ietf.org/doc/html/rfc6749#section-4.4)). The Client ID is reflected in the ID tokens it receives alongside the primary WebID. If used in production, these credentials must be associated with a service account in the OIDC Provider.

### WebIDs and Client IDs: What's the difference? <a href="#hd9jrfsvpq45" id="hd9jrfsvpq45"></a>

**WebID**

A WebID is a URI that identifies an entity. It resolves to a WebID profile document, which contains information to aid other Solid applications in working with it (such as the entity’s trusted identity providers or Solid Pods). It is carried in Solid-OIDC ID Tokens in the ‘webid’ and ‘sub’ claims. WebID is the first-class identifier in Solid today and can be used to identify people, organizations or things.

**Client ID**

A Client ID is an OAuth 2.0 concept that describes a public identifier string for a client application. In OAuth 2.0 flows, the Client ID has typically been a UUID or other pseudorandom string. In statically registered applications, the Client ID forms one-half of a client credential set (along with the client secret) used to authenticate applications. It is defined by the identity provider in these static registration flows.

In Solid-OIDC flows, the Client ID is determined by the application developer and is the URL of the public Client ID Document. This document contains important information to aid discovery and guarantees client authenticity by virtue of DNS ownership (which negates the need for client credentials generated by the identity provider).

### User Experience: Who’s asking? <a href="#id-8plgq0bt4lk2" id="id-8plgq0bt4lk2"></a>

Client IDs and WebIDs are among several identifiers used by Solid servers to filter which agents and client applications can access them. When both of these identifiers are dereferenced, they also provide information to aid the user experience, such as displaying information about the agent, client, or organization that wants to act on data in a Pod.

**Client ID**

In traditional OAuth 2.0 flows, a Client ID would be a statically registered UUID provided by the IDP. This type of Client ID was never designed to be dereferenceable or to provide any other function than that of a unique identifier.

For Solid applications, a Client ID should be a URL that resolves to a JSON-LD (as a minimum, though additional, content-negotiated variants are permissible) [Client ID Document](https://solid.github.io/solid-oidc/#clientids-document). Whilst the primary purpose of the Client ID Document is to convey metadata to facilitate the OIDC flow, the Client ID Document can also provide information to allow the user to to make an informed decision about whether to proceed with allowing the application to access the users ID Token. This is typically in the form of a company logo or a description of the application.

Developers should make the information in Client ID Documents as descriptive as possible. It should be noted that the Solid OIDC Client ID Document is an OIDC Dynamic Client Registration Client Metadata set and must follow the rules laid [out in the specification.](https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata) The purpose of this document is to provide the same metadata that would be provided in the traditional OAuth static registration flow.

Depending on the access control policies on the Solid Server, once a user accepts, the application may or may not be able to act on data in the Pod using the user’s ID token.

**WebID**

In almost all enterprise Solid data sharing flows, an organization will ask for permission to access a user’s data via some back end autonomous agent. In order for the user to know who is asking for permission, the access management application needs to be able to display information from an organization's WebID profile so that the user can make an informed decision. To provide the best user experience, an organization or logical business unit of an organization will want to approach a user with a consistent identity, no matter which backend service is requesting the data.

There are no hard and fast rules about when to use a corporate WebID vs one for individual applications. The guiding principle should be to get the most transparent and understandable user experience. For example, if the customer of a bank expects the mortgage department to be a different entity than the one that deals with business bank accounts, then the services that act on behalf of these entities should identify themselves separately. Alternatively, if the mortgage department has multiple services that act on its behalf (such as deal calculation, payments and customer contact), then they should all probably act with the same identity.

**Trust in WebIDs**

The WebID embodies two facets of trust, that of the URI itself and of the Identity Provider (the ‘Issuer’) associated with it. The two are linked by the WebID profile, via the solid:oidcIssuer predicate. Providing the WebID provided in the ID token is correct AND the OIDC issuer of that token is exactly referenced in the WebID profile, the identity can be assumed to be valid.

### User Piloted Application <a href="#ug62r3pyh4jr" id="ug62r3pyh4jr"></a>

![](/files/U1I9KvkIRIWoZljOwko3)

Here, the user pilots an application running in a web browser. The browser application has a Client ID Document at ‘<https://example.com/client\\_id‘> which is determined and hosted by the application developer. This Client ID (<https://example.com/client\\_id>) will be dynamically registered on first contact with the IDP, although the Solid server must be configured to allow this client to act on the Pod. The WebID used to identify the agent interacting with the Solid Server is the user’s WebID. Before the Identity Provider allows the user to proceed with the login, it will use the information from the Client ID Document and display this information to the user to make sure they are happy to proceed.

The Client ID Document could look like this:

```json
{
   "@context":[
      "https://www.w3.org/ns/solid/oidc-context.jsonld"
   ],
   "client_id":"https://example.com/client_id",
   "client_name":"My Example app",
   "client_uri":"https://example.com/",
   "redirect_uris":[
      "https://example.com/login"
   ],
   "grant_types":[
      "authorization_code",
      "refresh_token"
   ],
   "scope":"openid webid offline_access",
   "token_endpoint_auth_method":"none",
   "logo_uri":"https://example.com/logo.png",
   "tos_uri":"https://example.com/terms.html",
   "policy_uri":"https://example.com/policy.html",
   "contacts":[
      "admin@example.com"
   ],
   "application_type":"web",
   "require_auth_time":false
}
```

The user’s WebID could look something like: <https://id.inrupt.com/adam>

It will resolve to a WebID profile document that could look something like this:

```turtle
@prefix foaf:  <http://xmlns.com/foaf/0.1/> .
@prefix rdfs:  <http://www.w3.org/2000/01/rdf-schema#> .
@prefix solid: <http://www.w3.org/ns/solid/terms#> .

[ a  foaf:PersonalProfileDocument ;
  foaf:primaryTopic  <https://id.inrupt.com/adam>
] .

<https://id.inrupt.com/adam>
    	a foaf:Agent ;
    	rdfs:seeAlso <https://storage.inrupt.com/5ce2a23e-a19c-47f6-a8c3-e1baa7529494/extendedProfile> ;
    	<http://www.w3.org/ns/pim/space#storage>
            	<https://storage.inrupt.com/5ce2a23e-a19c-47f6-a8c3-e1baa7529494/> ;
    	solid:oidcIssuer <https://login.inrupt.com>;
        foaf:isPrimaryTopicOf  <https://storage.inrupt.com/5ce2a23e-a19c-47f6-a8c3-e1baa7529494/extendedProfile> .
```

Thus - the ID token generated by the IDP and presented to the Solid Server will look like:

```json
{
  "sub": "https://id.inrupt.com/adam",
  "aud": [
    "solid",
    "https://example.com/clientid"
  ],
  "azp": "https://example/clientid",
  "webid": "https://id.inrupt.com/adam",
  "iss": "https://login.inrupt.com",
  "jti": "844a095c-9cdb-47e5-9510-1dba987c0a5f",
  "iat": 1603370123,
  "exp": 1603371007,
  "cnf": {
    "jkt": "8876fg6shTg-jsUsjKjshh873_sT65GtFfghsf7"
  }
}


```

Note the Client ID of the app in the aud and azp claims, and the users WebID in the sub and webid claims.

If a backend for frontend pattern is used, the Client ID, WebID and ID token would be the same, except that the backend for frontend holds the ID and Access tokens, and the browser app just maintains a session cookie.

### Autonomous Software Agent with IDP <a href="#id-93a41h2raw5f" id="id-93a41h2raw5f"></a>

When a backend service wants to interact with Pods either via an access request or using ACP, the preferred pattern is that it obtains an identity token containing its WebID via a trusted OIDC identity provider. How the autonomous entity authenticates to get its identity token is up to the identity provider eg - via certificates or the OIDC client\_secret\_jwt, private\_key\_jwt methods.

This arrangement allows the identity provider to create identity tokens with the right WebID for the given context, which allows for a more flexible application and organizational identity proposition.

For example, multiple back end services could be identified individually when interacting within the organization, but act with a unified organizational identity when interacting outside the organization. This would be managed by the organization's administrators via their identity provider. This would give a better experience for users, who would be presented with access requests from a single organization WebID, no matter what particular backend service needed the data.

![](/files/iefBze6lctf2Unpi8OV9)

The application’s WebID is the only identifier presented to the Solid Server. It could look something like this <https://webid.example.com> , with a WebID profile looking like:

```turtle
@prefix foaf: <http://xmlns.com/foaf/0.1/>.
@prefix skos: <http://www.w3.org/2004/02/skos/core#>.
@prefix solid: <http://www.w3.org/ns/solid/terms#>.

<https://webid.example.com>
	a foaf:Organization, foaf:Agent;
	skos:prefLabel "Example Corporation";
	foaf:logo <https://example.com/logo.png>;
	foaf:homepage <https://example.com>;
	solid:oidcIssuer <https://login.example.com/> .
```

This would result in an OIDC token looking like:

```json
{
  "sub": "https://webid.example.com",
  "aud": [
    "solid"
  ],
  "webid": "https://webid.example.com",
  "iss": "https://login.example.com",
  "jti": "844a095c-9cdb-47e5-9510-1dba987c0a5f",
  "iat": 1603370123,
  "exp": 1603371007,
  "cnf": {
    "jkt": "8876fg6shTg-jsUsjKjshh873_sT65GtFfghsf7"
  }
}

```

### Self Asserted autonomous agent <a href="#id-5cb3peu5pjtp" id="id-5cb3peu5pjtp"></a>

A similar pattern to Autonomous Agent with IDP, except in this case the back end application asserts its own identity and acts as its own identity provider.

![](/files/lU3hg2SIW7yDq9jO6LA2)

This can be effective if there is no advanced identity provider in the system, or where there is a small number of backend agents. With this pattern, it is also possible to have numerous backend agents share private keys so that they can all act with the same identity if required or act with the same issuer (although managing this can become difficult as the system scales).

{% hint style="info" %}
Self-asserting applications should be treated with great care, as they have the potential to mint identity tokens.
{% endhint %}

The application’s WebID is the only identifier presented to the Solid Server. It could look something like this <https://webid.example.com> , with a WebID profile looking like:

```turtle
@prefix foaf: <http://xmlns.com/foaf/0.1/>.
@prefix skos: <http://www.w3.org/2004/02/skos/core#>.
@prefix solid: <http://www.w3.org/ns/solid/terms#>.

<https://webid.app1.example.com/>
	a foaf:Organization, foaf:Agent;
	skos:prefLabel "Example Corporation - App 1";
	foaf:logo <https://example.com/logo.png>;
	foaf:homepage <https://example.com>;
	solid:oidcIssuer <https://app1.example.com/idp> .
```

This would result in an OIDC token looking like:

```json
{
  "sub": "https://webid.app1.example.com",
  "aud": [
    "solid"
  ],
  "webid": "https://webid.app1.example.com",
  "iss": "https://login.example.com",
  "jti": "844a095c-9cdb-47e5-9510-1dba987c0a5f",
  "iat": 1603370123,
  "exp": 1603371007,
  "cnf": {
    "jkt": "8876fg6shTg-jsUsjKjshh873_sT65GtFfghsf7"
  }
}
```

### Unpiloted Autonomous Apps <a href="#ocoibjj8jdro" id="ocoibjj8jdro"></a>

In this case, a user registers their application ahead of time and provides the client credentials created by the IDP to their application.

The WebID given to the Solid Server will be that of the User, and the unpiloted app will present a Client ID provided by the IDP. In this case, the Client ID may not be dereferenceable as it will have been generated directly by the identity provider and thus might not have any facility to host documents.

In this pattern, the app acts on behalf of the user in the IDP, which could be a human user or a service account.

![](/files/ZmC4QBdwWAp2XSEWJrB7)

WebID: <https://id.inrupt.com/adam>

**WebID Profile**

```turtle
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix solid: <http://www.w3.org/ns/solid/terms#> .

[ a foaf:PersonalProfileDocument ;
  foaf:primaryTopic <https://id.inrupt.com/adam>
] .

<https://id.inrupt.com/adam>
  a foaf:Agent ;
  rdfs:seeAlso <https://storage.inrupt.com/5ce2a23e-a19c-47f6-a8c3-e1baa7529494/extendedProfile> ;
  <http://www.w3.org/ns/pim/space#storage>
    <https://storage.inrupt.com/5ce2a23e-a19c-47f6-a8c3-e1baa7529494/> ;
  solid:oidcIssuer <https://login.inrupt.com> ;
  foaf:isPrimaryTopicOf <https://storage.inrupt.com/5ce2a23e-a19c-47f6-a8c3-e1baa7529494/extendedProfile> .
```

**ID Token**

```json
{
  "sub": "https://id.inrupt.com/adam",
  "aud": [
    "solid",
    "D8702B90-7367-407A-AD75-0951D58631C3"
  ],
  "azp": "D8702B90-7367-407A-AD75-0951D58631C3",
  "webid": "https://id.inrupt.com/adam",
  "iss": "https://login.inrupt.com",
  "jti": "844a095c-9cdb-47e5-9510-1dba987c0a5f",
  "iat": 1603370123,
  "exp": 1603371007,
  "cnf": {
    "jkt": "8876fg6shTg-jsUsjKjshh873_sT65GtFfghsf7"
  }
}
```

### IDP Anti Patterns <a href="#id-2yq8h41e94xw" id="id-2yq8h41e94xw"></a>

It is of course possible to generate ID tokens with a Client ID pertaining to a specific instance of software and a WebID of the hosting / authoring organization. We would call this an anti-pattern and not recommend this.

In Solid, Client IDs should be dereference to Client ID Documents. These documents are primarily for the purpose of facilitating OIDC flows with the necessary metadata. Where possible, entities in Solid ecosystems should identify themselves with a primary identity in the form of a WebID.


# The Client ID Document

An application identifies itself using a [client identifier (Client ID)](https://solid.github.io/solid-oidc/#clientids).

A Client ID can be:

* a URL that dereferences to a [Client ID Document](https://solid.github.io/solid-oidc/#clientids-document).
* a value that has been registered using either [OIDC dynamic or static registration](https://solid.github.io/solid-oidc/#clientids-oidc).

Inrupt’s [Javascript Client Libraries](/sdk/javascript-sdk) provide **`login`** APIs that can support the use of a Client ID that dereferences to a Client ID Document.

The Client ID Document is JSON-LD document that contains various metadata about the client.

### Perform Login with Client ID (of type URL)

{% hint style="info" %}
An application can host its JSON-LD document in any location; however, if possible, it is recommended that the application itself hosts the JSON-LD document as a static resource.
{% endhint %}

Inrupt’s client libraries provide **`login`** APIs that can support the use of a Client ID (of type URL) that dereferences to a Client ID Document. When calling the **`login()`** function, set the **`clientId`** option to the application’s Client ID.

For example, the following code snippet includes the application’s client ID **`https://my-app.example.com/myappid.jsonld`** to the **`login()`** function:

```javascript
// `login()` sends users to their Solid Identity Provider;
// Once logged in, returns users to the specified `redirectUrl`.
// Both the `redirectURL` and the `clientId` value must appear
// in the Client Identifier document located at the specified URL.
await login({
  oidcIssuer: "https://login.inrupt.com",      // Solid Identity Provider
  redirectUrl: "https://my-app.example.com/login/callback/", // Redirect back to application
  clientId: "https://my-app.example.com/myappid.jsonld"    // Client Identifier 
});
```

The Solid Identity Provider verifies the specified `clientId` and `redirectUrl` values with those found in the identifying JSON-LD document.

Once the login flow completes, the application includes both the user WebID and the application’s Client ID in its requests to the Solid Server. The Solid Server uses these identifiers to enforce access control.

{% hint style="info" %}
ESS OpenID Service caches the Client ID Document. As such, you may encounter an error if, within the caching period, you use the Client ID Document to log in, and later modify the Client ID Document and reuse the document for login.
{% endhint %}

### Client ID Document

The Resource found when dereferencing an application’s Client ID is a [JSON-LD document](https://solid.github.io/solid-oidc/#clientids-document) with:

* A **`@context`** value of **`https://www.w3.org/ns/solid/oidc-context.jsonld`**.
* Fields conformant to an [OIDC client registration](https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata).

For example, assume the following sample JSON-LD document may be found by dereferencing the Client ID **`https://my-app.example.com/myappid.jsonld`**:

<pre class="language-json"><code class="lang-json">{
  "@context": "https://www.w3.org/ns/solid/oidc-context.jsonld",
<strong>  "client_id": "https://my-app.example.com/myappid.jsonld",
</strong><strong>  "redirect_uris": [ "https://my-app.example.com/login/callback/" ],
</strong>  "client_name": "My Sample App",
  "client_uri": "https://my-app.example.com/",
  "logo_uri": "https://my-app.example.com/logo.png",
  "tos_uri": "https://my-app.example.com/terms.html",
  "policy_uri": "https://my-app.example.com/policy.html",
  "contacts": [
    "someone@example.com"
  ],
  "scope": "openid offline_access webid",
  "grant_types": [
    "refresh_token",
    "authorization_code"
  ]
}
</code></pre>

<table><thead><tr><th width="199.78839111328125">Field</th><th>Description</th></tr></thead><tbody><tr><td><strong><code>@context</code></strong></td><td>The context for the JSON-LD document. The expected <code>@context</code> value is <code>https://www.w3.org/ns/solid/oidc-context.jsonld</code>.</td></tr><tr><td><strong><code>client_id</code></strong></td><td>A string containing the application’s Client Identifier. The <code>clientId</code> value passed to the <a href="https://inrupt.github.io/solid-client-authn-js/browser/functions.html#login">login()</a> function <mark style="color:red;"><strong>must</strong></mark> match the specified value exactly.</td></tr><tr><td><strong><code>redirect_uris</code></strong></td><td><p>An array containing URIs where the Solid Identity Provider may redirect the user to complete the login process, i.e., where the application’s <a href="https://inrupt.github.io/solid-client-authn-js/browser/functions.html#handleincomingredirect">handleIncomingRedirect()</a> will be called. The <code>redirectUrl</code> value passed to the <a href="https://inrupt.github.io/solid-client-authn-js/browser/functions.html#login">login()</a> function <mark style="color:red;"><strong>must</strong></mark> match one of the specified URIs exactly.</p><p>During development, to test with an application that is only running locally, you can specify the localhost url (e.g., <code>https://localhost:&#x3C;port>/</code> or <code>https://localhost:&#x3C;port>/some/login/callback/route/</code>) in both:</p><ul><li>the <code>redirect_uris</code> in the Client Identifier, and</li><li>the <code>redirectUrl</code> in the application’s <code>login()</code> call.</li></ul><p><mark style="color:red;"><strong>Remove</strong></mark> the localhost url from <code>redirect_uris</code> when you are running in production.</p></td></tr><tr><td><strong><code>scope</code></strong></td><td><p>A string containing a space-delimited list of OAuth2.0 scopes your application is allowed to request. OAuth2.0 scopes include:</p><p>Custom values may also be specified.</p></td></tr><tr><td>Scope</td><td>Notes</td></tr><tr><td><strong><code>openid</code></strong></td><td>If <strong><code>scope</code></strong> specified, <strong><code>openid</code></strong> is mandatory.</td></tr><tr><td><strong><code>offline_access</code></strong></td><td><p>Include <strong><code>offline_access</code></strong> to be issued refresh tokens.</p><p>For the definition of <strong><code>offline_access</code></strong> scope, see <a href="https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess">OpenID Connect</a>.</p></td></tr><tr><td><strong><code>webid</code></strong></td><td><strong><code>webid</code></strong> is <a href="https://solid.github.io/solid-oidc/#clientids-oidc">mandatory</a>.</td></tr><tr><td>Scope</td><td>Notes</td></tr><tr><td><strong><code>openid</code></strong></td><td>If <strong><code>scope</code></strong> specified, <strong><code>openid</code></strong> is mandatory.</td></tr><tr><td><strong><code>offline_access</code></strong></td><td><p>Include <strong><code>offline_access</code></strong> to be issued refresh tokens.</p><p>For the definition of <strong><code>offline_access</code></strong> scope, see <a href="https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess">OpenID Connect</a>.</p></td></tr><tr><td><strong><code>webid</code></strong></td><td><strong><code>webid</code></strong> is <a href="https://solid.github.io/solid-oidc/#clientids-oidc">mandatory</a>.</td></tr><tr><td><strong><code>grant_types</code></strong></td><td><p>An array of OAuth 2.0 grant types that the client can use at the authorization server’s token endpoint.</p><p>For additional values, see the <strong><code>grant_types</code></strong> definition in <a href="https://datatracker.ietf.org/doc/html/rfc7591#section-2">https://datatracker.ietf.org/doc/html/rfc7591#section-2</a>.</p></td></tr><tr><td></td><td></td></tr><tr><td><strong><code>"authorization_code"</code></strong></td><td>The default authentication flow, based on redirections between the application and the Solid Identity Provider.</td></tr><tr><td><strong><code>"refresh_token"</code></strong></td><td><p>The flow where a refresh token is used to “refresh” an expired session.</p><p>Used for apps that have declared the <a href="#scope-offline-access">offline_access</a> scope (i.e., discouraged for in-browser apps).</p></td></tr><tr><td></td><td></td></tr><tr><td><strong><code>"authorization_code"</code></strong></td><td>The default authentication flow, based on redirections between the application and the Solid Identity Provider.</td></tr><tr><td><strong><code>"refresh_token"</code></strong></td><td><p>The flow where a refresh token is used to “refresh” an expired session.</p><p>Used for apps that have declared the <a href="#scope-offline-access">offline_access</a> scope (i.e., discouraged for in-browser apps).</p></td></tr><tr><td><strong><code>client_name</code></strong></td><td>Optional. A string containing a user-friendly name for the application.</td></tr><tr><td><strong><code>client_uri</code></strong></td><td>Optional. A string containing the application’s homepage URI.</td></tr><tr><td><strong><code>logo_uri</code></strong></td><td>Optional. A string containing the URI where the application’s logo is available.</td></tr><tr><td><strong><code>tos_uri</code></strong></td><td>Optional. A string containing the URI where the application’s terms of service are available.</td></tr><tr><td><strong><code>policy_uri</code></strong></td><td>Optional. A string containing the URI where the application’s privacy policy is available.</td></tr><tr><td><strong><code>contacts</code></strong></td><td>Optional. An array of contact information for the application.</td></tr></tbody></table>

<table data-header-hidden><thead><tr><th width="198.83740234375"></th><th></th></tr></thead><tbody><tr><td><strong><code>"authorization_code"</code></strong></td><td>The default authentication flow, based on redirections between the application and the Solid Identity Provider.</td></tr><tr><td><strong><code>"refresh_token"</code></strong></td><td><p>The flow where a refresh token is used to “refresh” an expired session.</p><p>Used for apps that have declared the <a href="#scope-offline-access">offline_access</a> scope (i.e., discouraged for in-browser apps).</p></td></tr></tbody></table>

<table data-header-hidden><thead><tr><th width="159.4730224609375">Scope</th><th>Notes</th></tr></thead><tbody><tr><td><strong><code>openid</code></strong></td><td>If <strong><code>scope</code></strong> specified, <strong><code>openid</code></strong> is mandatory.</td></tr><tr><td><strong><code>offline_access</code></strong></td><td><p>Include <strong><code>offline_access</code></strong> to be issued refresh tokens.</p><p>For the definition of <strong><code>offline_access</code></strong> scope, see <a href="https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess">OpenID Connect</a>.</p></td></tr><tr><td><strong><code>webid</code></strong></td><td><strong><code>webid</code></strong> is <a href="https://solid.github.io/solid-oidc/#clientids-oidc">mandatory</a>.</td></tr></tbody></table>

{% hint style="info" %}
For additional fields to include in the document as well as more information on the aforementioned fields, see [RFC7591](https://datatracker.ietf.org/doc/html/rfc7591#section-2).
{% endhint %}

See also:

* [Solid-OIDC specification: Client Identifiers](https://solid.github.io/solid-oidc/#clientids).
* [OAuth2.0 website](https://www.oauth.com/)
* [OpenID Connect (OIDC) Dynamic Client Registration](https://openid.net/specs/openid-connect-registration-1_0.html)
* [Solid OIDC Primer](https://solid.github.io/solid-oidc/primer/)

#### Hosting Client ID Document

*Recommended* Although an application can host its JSON-LD document in any location, it is recommended that the application itself hosts the JSON-LD document as a static resource if possible.

#### Redirect URI Value During Development

During development, to test with an application running only on localhost, specify the localhost url (e.g., **`https://localhost:<port>/`** or **`https://localhost:<port>/some/login/callback/route/`**) as the redirect url in the Client ID Document and the application; i.e.,

* the **`redirect_uris`** in the Client ID Document,

  <pre class="language-json"><code class="lang-json">{
    "@context": "https://www.w3.org/ns/solid/oidc-context.jsonld",
    "client_id": "https://my-app.example.com/myappid",
  <strong>  "redirect_uris": ["http://localhost:3000/"],
  </strong>  "client_name": "My Test App",
    "client_uri": "https://my-app.example.com/",
    "logo_uri": "https://my-app.example.com/logo.png",
    "tos_uri": "https://my-app.example.com/terms.html",
    "policy_uri": "https://my-app.example.com/policy.html",
    "scope" : "openid offline_access webid",
    "grant_types" : ["refresh_token","authorization_code"]
  }
  </code></pre>
* the **`redirectUrl`** in the application’s **`login()`** call.

  <pre class="language-javascript"><code class="lang-javascript">await login({
    oidcIssuer: 'https://login.inrupt.com',
    clientId: 'https://my-app.example.com/myappid',
  <strong>  redirectUrl: 'http://localhost:3000/'
  </strong>});
  </code></pre>

{% hint style="warning" %} <mark style="color:red;">**Remove**</mark> the localhost url from **`redirect_uris`** when you are running in production.
{% endhint %}

{% hint style="info" %}
ESS OpenID Service caches the Client ID Document. As such, you may encounter an error if, within the caching period, you use the Client ID Document to log in, and later modify the Client ID Document and reuse the document for login.
{% endhint %}


# Access Control Policies

The following page provides an overview of [Access Control Policies (ACP)](https://docs.inrupt.com/security/authorization/acp) as well as examples using `@inrupt/solid-client` library’s ACP-specific APIs.

### Access Control Policy (ACP)

While Access Grants are the main way users grant access to data on thier Pods, application developers can make use of ACPs to create more complex authorization schemes for resources on Pods. ACPs are the foundational language by which authorization is expressed in Solid.

With [Access Control Policies (ACP)](/security/authorization#acp), Pod owners can define Policies that determine access for their Pod’s resources.

* Each resource has an associated Access Control Resource (ACR).
* The ACR contains the Policies that determine authorization decisions for its associated resource.
* The Policies determine access for Pod resources. A policy statement consists of:
  * Matcher statements that specify conditions that must be satisfied for the Policy to take effect.
  * Access mode statements that specify which access modes are allowed and/or denied to the agent(s) satisfying the Matcher statements.

#### Access Control Resource (ACR)

Every [Resource](/reference/glossary#resource) has an associated Access Control Resource (ACR). The ACR specifies the Policies that apply to the resource; these Policies determine the access to the resource.

If the resource is a [Container](/reference/glossary#container) (analogous to a folder in a file system), you can also specify default Member Policies in the Container’s ACR. A Container’s Default Member Policies apply to resources (contained in the Container) that do not have their own ACP policies explicitly defined.

If a resource has no Policies that apply to it (neither explicitly for the resource nor implicitly through default Member Policies), the resource is inaccessible. However, the Pod owner and other agents with access to modify the resource’s ACR can add new Policies to provide access to the resource.

For examples on adding policies to a resource’s ACR, see [Examples](#examples).

#### Policies

Policies determine access for Pod resources.

{% hint style="info" %}
Policies are Things in [Structured Data (RDF Resources)](https://docs.inrupt.com/developer-tools/javascript/client-libraries/structured-data/) terminology and are identified by their URL, generally of the form `{ACR URL}#{policy-name}`. See also [URL as Identifiers](/reference/rdf/structured-data-rdf-resources).
{% endhint %}

A policy consists of:

* Matcher statements that specify conditions that must be satisfied for the Policy to take effect.
* Access mode statements that specify which access modes are allowed and/or denied to the agent(s) satisfying the Matcher statements.

> If
>
> < allOf | anyOf > (Matcher(s)) evaluates to true, **AND**
>
> < allOf | anyOf | noneOf > (Matcher(s)) evaluates to true, **AND**
>
> …
>
> Then
>
> <**allow** (AccessMode(s)) | **deny** (AccessMode(s)) | **allow** (AccessMode(s)) **AND** **deny** (AccessMode(s)) >

{% hint style="info" %}
**Important**

The noneOf() expression excludes matches from the allOf() and anyOf() expressions; i.e., you can use the noneOf() expression to refine the allOf() and anyOf() matches.

Because the noneOf() expression acts as a **secondary/supplementary** filter to the allOf() and anyOf() expressions, a Policy statement with only a noneOf() condition cannot be satisfied.

For examples on defining and applying policies to a resource, see [Examples](#examples).
{% endhint %}

#### Matcher Statements

> < allOf | anyOf > (Matcher(s)) evaluate to true, **AND**
>
> < allOf | anyOf | noneOf > (Matcher(s)) evaluates to true, **AND**
>
> …

**Matchers**

Matchers specify the conditions under which the Access Policy applies.

ESS supports the following types of Matchers:

**`allOf`, `anyOf`, `noneOf` Operators**

A policy specifies its matchers in `allOf()`, `anyOf()`, and `noneOf()` operator expressions.

For examples on adding matchers and policies, see [Examples](#examples).

#### ACP Access Modes

> <**allow** (AccessMode(s)) | **deny** (AccessMode(s)) | **allow** (AccessMode(s)) **AND** **deny** (AccessMode(s))>

**Access Modes**

Access Modes describe the permissions (i.e., access) that are allowed or denied for a resource.

The `@inrupt/solid-client`’s ACP APIs handle `<Access Modes>` specification as an object of the form:

```javascript
{ read: <boolean>, append: <boolean>, write: <boolean> }
```

The available Access Modes are:

**`allow`, `deny` Expressions**

A policy statement specifies its access modes in `allow(Access Modes)` or `deny(Access Modes)` expressions:

For examples on adding matchers and policies, see [Examples](#examples).

#### Evaluating Access

An agent is granted an access mode for a resource if:

* The agent satisfies a Policy that `allows` the access mode for the resource, **and**
* The agent does not satisfy any Policy that `denies` that access mode for the resource.

If **no** “allow access” policy is satisfied for a resource, then that resource is inaccessible to the agent. That is, an unsatisfied “deny access” policy does not confer access.

For examples on adding matchers and policies, see [Examples](#examples).

### CRUD Operations and ACP Modes

To create a resource, the user requires either an Append or Write access.

Note

* The creation operation creates the resource and updates the content of the **parent** Container with the new resource’s metadata.
* Although, a [Container](/reference/glossary#container) is itself a [SolidDataset](/reference/glossary#soliddataset), the table separates out the access for the Container and SolidDataset.

### API and Solid Server Support

Inrupt’s `solid-client` library provides various ACP-specific functions to manage ACP policies.

Inrupt’s [Enterprise Solid Server (ESS)](https://docs.inrupt.com/ess/latest/introduction) provides support for ACP.

To access and use ACP-specific functions compatible with ESS, import `acp_ess_2`.

```javascript
import { acp_ess_2 } from "@inrupt/solid-client";
```

ESS includes changes to ACP in line with the [Editor’s Draft of Access Control Policy](https://solid.github.io/authorization-panel/acp-specification/). These changes have the following implications for the ACP-specific APIs:

* Matchers replace Rules. As such, various `*Rule*` APIs have been deprecated.
* ACP no longer supports `Group` matching (where `Group` is identified by its own URL). As such, various `*Group*` APIs have been deprecated.
* Access Policies and Matchers can only be defined in an ACR. In addition,

  * ACRs must directly link to a Resource,
  * Access Controls and member Access Controls must link to Policies, and
  * Policies must link to Access Modes and Matchers they use.

  As such, you can no longer save unused ACP elements. Concretely, it means that Policies and Matchers must all be defined in an ACR.

### Examples

#### Create Policy to Match Agents and Clients

The following example sets up an `app-friends-policy` that allow Read and Write access to any Agent that satisfies the `match-app-friends` Matcher conditions; namely, Agents whose WebID matches one of the specified WebIDs and is using an application whose Client Identifier matches the specified Client IDs. When verifying against a policy that specifies a Client Application Matcher, the user must be logged in. A Policy that specifies a Client Application Matcher but no Agent Matcher does not match any agent.

```javascript
import { handleIncomingRedirect, login, fetch, getDefaultSession } from '@inrupt/solid-client-authn-browser';
import { acp_ess_2, asUrl } from "@inrupt/solid-client";


// ... Various logic, including login logic, omitted for brevity.
// ...


async function setupPolicyToMatchAgentsAndClients(resourceURL) {

  const agentsToMatch = [ "https://id.example.com/chattycarl", "https://id.example.com/busybee" ];
  const clientIDsToMatch = [ "https://myapp.example.net/appid" ];

  try {
    // 1. Fetch the SolidDataset with its Access Control Resource (ACR).
    let resourceWithAcr = await acp_ess_2.getSolidDatasetWithAcr(
      resourceURL,            // Resource whose ACR to set up
      { fetch: fetch }       // fetch from the authenticated session
    );

    // 2. Initialize a new Matcher.
    let appFriendsMatcher = acp_ess_2.createResourceMatcherFor(
      resourceWithAcr,
      "match-app-friends"
    );

    // 3. For the Matcher, specify the Agent(s) to match.
    agentsToMatch.forEach(agent => {
      appFriendsMatcher = acp_ess_2.addAgent(appFriendsMatcher, agent);
    })

    // 4. For the Matcher, specify the Client ID(s) to match.
    clientIDsToMatch.forEach(clientID => {
      appFriendsMatcher = acp_ess_2.addClient(appFriendsMatcher, clientID);
    })

    // 5. Add the Matcher definition to the Resource's ACR.
    resourceWithAcr = acp_ess_2.setResourceMatcher(
      resourceWithAcr,
      appFriendsMatcher
    );

    // 6. Create a Policy for the Matcher.
    let appFriendsPolicy = acp_ess_2.createResourcePolicyFor(
      resourceWithAcr,
      "app-friends-policy",
    );

    // 7. Add the appFriendsMatcher to the Policy as an allOf() expression.
    // Since using allOf() with a single Matcher, could also use anyOf() expression

    appFriendsPolicy = acp_ess_2.addAllOfMatcherUrl(
      appFriendsPolicy,
      appFriendsMatcher
    );

    // 8. Specify the access modes (e.g., allow Read and Write).
    appFriendsPolicy = acp_ess_2.setAllowModes(appFriendsPolicy,
      { read: true, write: true }
    );

    // 9. Apply the Policy to the resource.
    resourceWithAcr = acp_ess_2.addPolicyUrl(
      resourceWithAcr,
      asUrl(appFriendsPolicy)
    );

    // 10. Add the Policy definition to the resource's ACR. 
    resourceWithAcr = acp_ess_2.setResourcePolicy(
      resourceWithAcr,
      appFriendsPolicy
    );

    // 11. Save the modified ACR for the resource.
    const updatedResourceWithAcr = await acp_ess_2.saveAcrFor(
      resourceWithAcr,
      { fetch: fetch }          // fetch from the authenticated session
    );

  } catch (error) {
    console.error(error.message);
  }
}
```

**Details**

In particular, the example uses:

1. [acp\_ess\_2.getSolidDatasetWithAcr](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#getsoliddatasetwithacr) to retrieve the SolidDataset with its ACR.

   ```javascript
   let resourceWithAcr = await acp_ess_2.getSolidDatasetWithAcr(
     resourceURL,            // Resource whose ACR to set up
     { fetch: fetch }       // fetch from the authenticated session
   );
   ```

   To specify policies for files with other structures (such as .pdf or .jpeg files), use [acp\_ess\_2.getFileWithAcr](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#getfilewithacr) instead.
2. [acp\_ess\_2.createResourceMatcherFor](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#createresourcematcherfor) to initialize the Matcher that will be used by the policy.

   ```javascript
   let appFriendsMatcher = acp_ess_2.createResourceMatcherFor(
     resourceWithAcr,
     "match-app-friends"
   );
   ```

   When saved, the Matcher URL will be `{ACR URL}#match-app-friends`.
3. [acp\_ess\_2.addAgent](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#addagent) to specify the [WebID](about:blank/reference/glossary/#term-WebID) of the agent(s) to match:

   ```javascript
   agentsToMatch.forEach(agent => {
     appFriendsMatcher = acp_ess_2.addAgent(appFriendsMatcher, agent);
   })
   ```
4. [acp\_ess\_2.addClient](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#addclient) to specify the [Client ID](about:blank/authenticate-client/#authenticate-client-identifier-document) of the application(s) to match.

   ```javascript
   clientIDsToMatch.forEach(clientID => {
     appFriendsMatcher = acp_ess_2.addClient(appFriendsMatcher, clientID);
   })
   ```
5. [acp\_ess\_2.setResourceMatcher](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#setresourcematcher) to store the new matcher definition to the ACR:

   ```javascript
   resourceWithAcr = acp_ess_2.setResourceMatcher(
     resourceWithAcr,
     appFriendsMatcher
   );
   ```
6. [acp\_ess\_2.createResourcePolicyFor](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#createresourcepolicyfor) to initialize the policy:

   ```javascript
   let appFriendsPolicy = acp_ess_2.createResourcePolicyFor(
     resourceWithAcr,
     "app-friends-policy",
   );
   ```

   When saved, the policy URL will be `{ACR URL}#app-friends-policy`.
7. [acp\_ess\_2.addAllOfMatcherUrl](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#addallofmatcherurl) to add the matcher to the policy.

   ```javascript
   // Since using allOf() with a single Matcher, could also use anyOf() expression

   appFriendsPolicy = acp_ess_2.addAllOfMatcherUrl(
     appFriendsPolicy,
     appFriendsMatcher
   );
   ```
8. [acp\_ess\_2.setAllowModes](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#setallowmodes) to specify that the policy allows `Read` and `Write` modes:

   ```javascript
   appFriendsPolicy = acp_ess_2.setAllowModes(appFriendsPolicy,
     { read: true, write: true }
   );
   ```
9. [acp\_ess\_2.addPolicyUrl](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#addpolicyurl) to apply the new policy to the resource:

   ```javascript
   resourceWithAcr = acp_ess_2.addPolicyUrl(
     resourceWithAcr,
     asUrl(appFriendsPolicy)
   );
   ```
10. [acp\_ess\_2.setResourcePolicy](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#setresourcepolicy) to store the new policy definition to the ACR:

    ```javascript
    resourceWithAcr = acp_ess_2.setResourcePolicy(
      resourceWithAcr,
      appFriendsPolicy
    );
    ```
11. [acp\_ess\_2.saveAcrFor](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#saveacrfor) to save the modified ACR.

    ```javascript
    const updatedResourceWithAcr = await acp_ess_2.saveAcrFor(
      resourceWithAcr,
      { fetch: fetch }          // fetch from the authenticated session
    );
    ```

#### Make a Resource Public: Create Public Policy for a Resource

The following example uses the ACP-specific APIs to set up a `public-policy` that allows Read access to the public (i.e., everyone) for a resource.

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

// ... Various logic, including login logic, omitted for brevity.
// ...

async function setupPublicReadPolicyForResource(resourceURL) {
  try {
    // 1. Fetch the SolidDataset with its Access Control Resource (ACR).
    let resourceWithAcr = await acp_ess_2.getSolidDatasetWithAcr(
      resourceURL,              // Resource for which to set up the policies
      { fetch: fetch }          // fetch from the authenticated session
    );

    // 2. Create a Matcher for the Resource.
    let resourcePublicMatcher = acp_ess_2.createResourceMatcherFor(
      resourceWithAcr,
      "match-public"  // Matcher URL will be {ACR URL}#match-public
    );

    // 3. Specify that the matcher matches the Public (i.e., everyone).
    resourcePublicMatcher = acp_ess_2.setPublic(resourcePublicMatcher);

    // 4. Add Matcher to the Resource's ACR.
    resourceWithAcr = acp_ess_2.setResourceMatcher(
      resourceWithAcr,
      resourcePublicMatcher,
    );

    // 5. Create the Policy for the Resource.
    let resourcePolicy = acp_ess_2.createResourcePolicyFor(
      resourceWithAcr,
      "public-policy",  // Policy URL will be {ACR URL}#public-policy
    );

    // 6. Add the Public Matcher to the Policy as an allOf() expression.
    resourcePolicy = acp_ess_2.addAllOfMatcherUrl(
      resourcePolicy,
      resourcePublicMatcher
    );

    // 7. Specify the access modes for the Policy.
    resourcePolicy = acp_ess_2.setAllowModes(
      resourcePolicy,
      { read: true, append: false, write: false },
    );

    // 8. Apply the Policy to the Resource.
    resourceWithAcr = acp_ess_2.addPolicyUrl(
       resourceWithAcr,
       asUrl(resourcePolicy)
     );

    // 9. Add the Policy definition to the Resource's ACR. 
    resourceWithAcr = acp_ess_2.setResourcePolicy(
       resourceWithAcr,
       resourcePolicy,
    );

    // 10. Save the ACR for the Resource.
    const updatedResourceWithAcr = await acp_ess_2.saveAcrFor(
      resourceWithAcr,
      { fetch: fetch }          // fetch from the authenticated session
    );
  } catch (error) {
    console.error(error.message);
  }
}
```

**Details**

In particular, the example uses:

1. [acp\_ess\_2.getSolidDatasetWithAcr](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#getsoliddatasetwithacr) to retrieve the SolidDataset (the SolidDataset can be a Container) with its ACR.

   ```javascript
   let resourceWithAcr = await acp_ess_2.getSolidDatasetWithAcr(
     resourceURL,              // Resource for which to set up the policies
     { fetch: fetch }          // fetch from the authenticated session
   );
   ```

   To specify policies for files with other structures (such as .pdf or .jpeg files), use [acp\_ess\_2.getFileWithAcr](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#getfilewithacr) instead.
2. [acp\_ess\_2.createResourceMatcherFor](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#createresourcematcherfor) to initialize the Matcher that will be used by the policy.

   ```javascript
   let resourcePublicMatcher = acp_ess_2.createResourceMatcherFor(
     resourceWithAcr,
     "match-public"  // Matcher URL will be {ACR URL}#match-public
   );
   ```

   When saved, the Matcher URL will be `{ACR URL}#match-public`.
3. [acp\_ess\_2.setPublic](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#setpublic) to specify that the matcher is a Public matcher; i.e., matches everyone.

   ```javascript
   resourcePublicMatcher = acp_ess_2.setPublic(resourcePublicMatcher);
   ```
4. [acp\_ess\_2.setResourceMatcher](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#setresourcematcher) to store the matcher definition to the ACR:

   ```javascript
   resourceWithAcr = acp_ess_2.setResourceMatcher(
     resourceWithAcr,
     resourcePublicMatcher,
   );
   ```
5. [acp\_ess\_2.createResourcePolicyFor](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#createresourcepolicyfor) to initialize the policy for the Resource:

   ```javascript
   let resourcePolicy = acp_ess_2.createResourcePolicyFor(
     resourceWithAcr,
     "public-policy",  // Policy URL will be {ACR URL}#public-policy
   );
   ```

   When saved, the policy URL will be `{ACR URL}#public-policy`.
6. [acp\_ess\_2.addAllOfMatcherUrl](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#addallofmatcherurl) to add the matcher to the policy.

   ```javascript
   resourcePolicy = acp_ess_2.addAllOfMatcherUrl(
     resourcePolicy,
     resourcePublicMatcher
   );
   ```
7. [acp\_ess\_2.setAllowModes](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#setallowmodes) to specify the access modes for the policy:

   ```javascript
   resourcePolicy = acp_ess_2.setAllowModes(
     resourcePolicy,
     { read: true, append: false, write: false },
   );
   ```
8. [acp\_ess\_2.addPolicyUrl](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#addpolicyurl) to apply the new policy to the resource:

   ```javascript
   resourceWithAcr = acp_ess_2.addPolicyUrl(
      resourceWithAcr,
      asUrl(resourcePolicy)
    );
   ```
9. [acp\_ess\_2.setResourcePolicy](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#setresourcepolicy) to store the new policy definition to the ACR:

   ```javascript
   resourceWithAcr = acp_ess_2.setResourcePolicy(
      resourceWithAcr,
      resourcePolicy,
   );
   ```
10. [acp\_ess\_2.saveAcrFor](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#saveacrfor) to save the modified ACR.

    ```javascript
    const updatedResourceWithAcr = await acp_ess_2.saveAcrFor(
      resourceWithAcr,
      { fetch: fetch }          // fetch from the authenticated session
    );
    ```

#### View Policies and Matchers for a Resource

The following example uses the ACP-specific APIs to view the ACP policies for a resource.

```javascript
import { handleIncomingRedirect, login, fetch, getDefaultSession } from '@inrupt/solid-client-authn-browser';
import { acp_ess_2, solidDatasetAsTurtle } from "@inrupt/solid-client";

// ... Various logic, including login logic, omitted for brevity.

async function viewResourceACR(resourceURL) {

  try {
    // 1. Fetch the SolidDataset with its Access Control Resource (ACR).
    const resourceWithAcr = await acp_ess_2.getSolidDatasetWithAcr(
      resourceURL,
      { fetch: fetch }            // fetch from the authenticated session
    );

    // 2a. Get the Access Control Resource (ACR)
    const myACR = await getSolidDataset(
      acp_ess_2.getLinkedAcrUrl(resourceWithAcr),
      { fetch: fetch }
    );
    
    // 2b. Output (formatted as Turtle) its policies and matchers details.
    console.log(solidDatasetAsTurtle(myACR));

    // 3a. Get all policies from the ACR to process policies.
    const myResourcePolicies = acp_ess_2.getResourcePolicyAll(resourceWithAcr);

    // Loop through each policy for processing.
    myResourcePolicies.forEach(policy => {
      //... 
    });

    // 3b. Get a specific policy from the ACR.
    const specificPolicy = acp_ess_2.getResourcePolicy(
      resourceWithAcr,
      "specify-the-name-of-policy-to-get"
    );

    // 4a. Get all matchers from the ACR to process matchers.
    const myResourceMatchers = acp_ess_2.getResourceMatcherAll(resourceWithAcr)

    // Loop through each matcher for processing.
    myResourceMatchers.forEach(matcher => {
      // ... 
    });

    // 4b. Get a specific matcher from the ACR.
    const specificMatcher = acp_ess_2.getResourceMatcher(
      resourceWithAcr,
      "specify-the-name-of-matcher-to-get"
    );


  } catch (error) {
      console.error(error.message);
  }
}
```

**Details**

In particular, the example uses:

1. [acp\_ess\_2.getSolidDatasetWithAcr](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#getsoliddatasetwithacr) to retrieve the SolidDataset (the SolidDataset can be a Container) with its ACR.

   ```javascript
   const resourceWithAcr = await acp_ess_2.getSolidDatasetWithAcr(
     resourceURL,
     { fetch: fetch }            // fetch from the authenticated session
   );
   ```

   To specify policies for files with other structures (such as .pdf or .jpeg files), use [acp\_ess\_2.getFileWithAcr](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#getfilewithacr) instead.
2. [getSolidDataset](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#getsoliddataset) with [getLinkedAcrUrl](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#getlinkedacrurl) to retrieve the ACR.

   ```javascript
   const myACR = await getSolidDataset(
     acp_ess_2.getLinkedAcrUrl(resourceWithAcr),
     { fetch: fetch }
   );
   ```

   Once you retrieve the ACR as a SolidDataset, you can use [solidDatasetAsTurtle](https://inrupt.github.io/solid-client-js/modules/formats.html#soliddatasetasturtle) to format ACR as [Turtle](https://www.w3.org/TR/turtle/).

   ```javascript
   console.log(solidDatasetAsTurtle(myACR));
   ```
3. [acp\_ess\_2.getResourcePolicyAll](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#getresourcepolicyall) to get the policies from the resource’s ACR.

   ```javascript
   const myResourcePolicies = acp_ess_2.getResourcePolicyAll(resourceWithAcr);

   // Loop through each policy for processing.
   myResourcePolicies.forEach(policy => {
     //... 
   });
   ```

   To view a specific policy, you can use [acp\_ess\_2.getResourcePolicy](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#getresourcepolicy):

   ```javascript
   const specificPolicy = acp_ess_2.getResourcePolicy(
     resourceWithAcr,
     "specify-the-name-of-policy-to-get"
   );
   ```
4. [acp\_ess\_2.getResourceMatcherAll](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#getresourcematcherall) to get all matchers from the resource’s ACR.

   ```javascript
   const myResourceMatchers = acp_ess_2.getResourceMatcherAll(resourceWithAcr)

   // Loop through each matcher for processing.
   myResourceMatchers.forEach(matcher => {
     // ... 
   });
   ```

   To view a specific matcher, you can use [acp\_ess\_2.getResourceMatcher](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#getresourcematcher):

   ```javascript
   const specificMatcher = acp_ess_2.getResourceMatcher(
     resourceWithAcr,
     "specify-the-name-of-matcher-to-get"
   );
   ```

#### Delete Existing Policy for a Resource

The following example deletes an existing Policy for a resource.

Tip

To view existing Policies for a resource, see [View Policies and Matchers for a Resource](#view-policies-and-matchers-for-a-resource).

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

// ... Various logic, including login logic, omitted for brevity.

async function deletePolicyForResource(resourceURL, policyName) {

  try {

    // 1. Fetch the SolidDataset with its Access Control Resource (ACR).
    let resourceWithAcr = await acp_ess_2.getSolidDatasetWithAcr(
      resourceURL,           // Resource whose policy you want to delete
      { fetch: fetch }       // fetch from the authenticated session
    );

    // 2. Remove the Policy definition from the ACR
    resourceWithAcr = acp_ess_2.removeResourcePolicy(resourceWithAcr, policyName);

    // 3. Save the ACR for the Resource.
    const updatedResourceWithAcr = await acp_ess_2.saveAcrFor(
      resourceWithAcr,
      { fetch: fetch }          // fetch from the authenticated session
    );

  } catch (error) {
    console.error(error.message);
  }
}
```

**Details**

In particular, the example uses:

1. [acp\_ess\_2.getSolidDatasetWithAcr](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#getsoliddatasetwithacr) to retrieve the SolidDataset (the SolidDataset can be a Container) with its ACR.

   ```javascript
   let resourceWithAcr = await acp_ess_2.getSolidDatasetWithAcr(
     resourceURL,           // Resource whose policy you want to delete
     { fetch: fetch }       // fetch from the authenticated session
   );
   ```

   To specify policies for files with other structures (such as .pdf or .jpeg files), use [acp\_ess\_2.getFileWithAcr](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#getfilewithacr) instead.
2. [acp\_ess\_2.removeResourcePolicy](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#removeresourcepolicy) to delete the Policy definition from the ACR:

   ```javascript
   resourceWithAcr = acp_ess_2.removeResourcePolicy(resourceWithAcr, policyName);
   ```

   [acp\_ess\_2.removeResourcePolicy](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#removeresourcepolicy) can also accept the Policy URL or the Policy itself instead of the Policy name.
3. [acp\_ess\_2.saveAcrFor](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#saveacrfor) to save the modified ACR.

   ```javascript
   const updatedResourceWithAcr = await acp_ess_2.saveAcrFor(
     resourceWithAcr,
     { fetch: fetch }          // fetch from the authenticated session
   );
   ```

#### Modify Existing Matcher for a Resource

The following example continues from an earlier example. Specifically, the example modifies the `match-app-friends` created in [Create Policy to Match Agents and Clients](#create-policy-to-match-agents-and-clients) to remove one of the Agents from the match list.

Tip

To view existing Matchers for a resource, see [View Policies and Matchers for a Resource](#view-policies-and-matchers-for-a-resource).

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

// ... Various logic, including login logic, omitted for brevity.

async function removeAgentFromMatcher(resourceURL) {

  try {

    // 1. Fetch the SolidDataset with its Access Control Resource (ACR).
    let resourceWithAcr = await acp_ess_2.getSolidDatasetWithAcr(
        resourceURL,           // Resource whose Matcher you want to modify
        { fetch: fetch }       // fetch from the authenticated session
    );

    // 2. Get the Matcher to modify.
    let matcherToModify = acp_ess_2.getResourceMatcher(
        resourceWithAcr,
        "match-app-friends" // Name of the Matcher created in an earlier example.
    );

    // 3. Modify the Matcher; e.g., remove an Agent from the Matcher.

    const agentToRemove="https://id.example.com/chattycarl";
    matcherToModify = acp_ess_2.removeAgent(matcherToModify, agentToRemove);

    // 4. Store the modified Matcher definition to the resource's ACR.
    resourceWithAcr = acp_ess_2.setResourceMatcher(
        resourceWithAcr,
        matcherToModify
    );

    // 5. Save the modified ACR for the resource.
    const updatedResourceWithAcr = await acp_ess_2.saveAcrFor(
        resourceWithAcr,
        { fetch: fetch }          // fetch from the authenticated session
    );

  } catch (error) {
    console.error(error.message);
  }
}
```

**Details**

In particular, the example uses:

1. [acp\_ess\_2.getSolidDatasetWithAcr](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#getsoliddatasetwithacr) to retrieve the SolidDataset (the SolidDataset can be a Container) with its ACR.

   ```javascript
   let resourceWithAcr = await acp_ess_2.getSolidDatasetWithAcr(
       resourceURL,           // Resource whose Matcher you want to modify
       { fetch: fetch }       // fetch from the authenticated session
   );
   ```

   To specify policies for files with other structures (such as .pdf or .jpeg files), use [acp\_ess\_2.getFileWithAcr](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#getfilewithacr) instead.
2. [acp\_ess\_2.getResourceMatcher](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#getresourcematcher) to get the Matcher from the resource’s ACR.

   ```javascript
   let matcherToModify = acp_ess_2.getResourceMatcher(
       resourceWithAcr,
       "match-app-friends" // Name of the Matcher created in an earlier example.
   );
   ```

   The `match-app-friends` was created in an earlier example, [Create Policy to Match Agents and Clients](https://github.com/inrupt/docs-gitbook/tree/main/guides/broken-reference/README.md).

   Tip

   To view existing Matchers for a resource, see [View Policies and Matchers for a Resource](https://github.com/inrupt/docs-gitbook/tree/main/guides/broken-reference/README.md).
3. [acp\_ess\_2.removeAgent](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#removeagent) to remove an Agent’s WebID from the list of the Matcher’s WebIDs to match.

   ```javascript
   policyToModify = acp_ess_2.setAllowModes(policyToModify,
     { write: false }
   );
   ```
4. [acp\_ess\_2.setResourceMatcher](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#setresourcematcher) to update the Matcher definition in the ACR:

   ```javascript
   resourceWithAcr = acp_ess_2.setResourcePolicy(
     resourceWithAcr,
     policyToModify
   );
   ```
5. [acp\_ess\_2.saveAcrFor](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#saveacrfor) to save the modified ACR.

   ```javascript
   const updatedResourceWithAcr = await acp_ess_2.saveAcrFor(
     resourceWithAcr,
     { fetch: fetch }          // fetch from the authenticated session
   );
   ```

#### Modify Existing Policy for a Resource

The following example continues from an earlier example. Specifically, the example modifies the `app-friends-policy` created in [Create Policy to Match Agents and Clients](/security/authorization/acp#create-policy-to-match-agents-and-clients).

Tip

To view existing Policies for a resource, see [View Policies and Matchers for a Resource](https://docs.inrupt.com/security/authorization/acp#view-policies-and-matchers-for-a-resource).

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

// ... Various logic, including login logic, omitted for brevity.

async function modifyAppFriendsPolicy(resourceURL) {

  try {

    // 1. Fetch the SolidDataset with its Access Control Resource (ACR).
    let resourceWithAcr = await acp_ess_2.getSolidDatasetWithAcr(
      resourceURL,           // Resource whose Policy you want to modify
      { fetch: fetch }       // fetch from the authenticated session
    );

    // 2. Get the Policy to modify. 
    let policyToModify = acp_ess_2.getResourcePolicy(
      resourceWithAcr,
      "app-friends-policy" // Name of the Policy created in an earlier example.
    );

    // 3. Change the Write access mode to false (from true). Other access modes remain unchanged.
    policyToModify = acp_ess_2.setAllowModes(policyToModify,
      { write: false }
    );

    // 4. Store the modified Policy definition to the resource's ACR. 
    resourceWithAcr = acp_ess_2.setResourcePolicy(
      resourceWithAcr,
      policyToModify
    );

    // 5. Save the modified ACR for the resource.
    const updatedResourceWithAcr = await acp_ess_2.saveAcrFor(
      resourceWithAcr,
      { fetch: fetch }          // fetch from the authenticated session
    );

  } catch (error) {
    console.error(error.message);
  }
}
```

**Details**

In particular, the example uses:

1. [acp\_ess\_2.getSolidDatasetWithAcr](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#getsoliddatasetwithacr) to retrieve the SolidDataset (the SolidDataset can be a Container) with its ACR.

   ```javascript
   let resourceWithAcr = await acp_ess_2.getSolidDatasetWithAcr(
     resourceURL,           // Resource whose Policy you want to modify
     { fetch: fetch }       // fetch from the authenticated session
   );
   ```

   To specify policies for files with other structures (such as .pdf or .jpeg files), use [acp\_ess\_2.getFileWithAcr](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#getfilewithacr) instead.
2. [acp\_ess\_2.getResourcePolicy](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#getresourcepolicy) to get the Policy from the resource’s ACR. The `app-friends-policy` was created in an earlier example, [Create Policy to Match Agents and Clients](#create-policy-to-match-agents-and-clients).

   ```javascript
   let policyToModify = acp_ess_2.getResourcePolicy(
     resourceWithAcr,
     "app-friends-policy" // Name of the Policy created in an earlier example.
   );
   ```

   Tip

   To view existing Policies for a resource, see [View Policies and Matchers for a Resource](#view-policies-and-matchers-for-a-resource).
3. [acp\_ess\_2.setAllowModes](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#setallowmodes) to update the Write access mode for the Policy. The other Access Modes for this Policy remain unchanged.

   ```javascript
   policyToModify = acp_ess_2.setAllowModes(policyToModify,
     { write: false }
   );
   ```

   For additional Policy functions, see the [API documentation](https://inrupt.github.io/solid-client-js/).
4. [acp\_ess\_2.setResourcePolicy](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#setresourcepolicy) to update the Policy definition in the ACR:

   ```javascript
   resourceWithAcr = acp_ess_2.setResourcePolicy(
     resourceWithAcr,
     policyToModify
   );
   ```
5. [acp\_ess\_2.saveAcrFor](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#saveacrfor) to save the modified ACR.

   ```javascript
   const updatedResourceWithAcr = await acp_ess_2.saveAcrFor(
     resourceWithAcr,
     { fetch: fetch }          // fetch from the authenticated session
   );
   ```


# Universal API

Access control/authorization determines which actions an [Agent](/reference/glossary#agent) can perform on a [Resource](/reference/glossary#resource). For instance, an agent may have [Read Access](/reference/glossary#read-access) to a Resource, but not [Write Access](/reference/glossary#write-access). Different Solid Servers can support different access control mechanisms; e.g., [Access Control Policies (ACP)](/reference/glossary#access-control-policies) or [Web Access Control (WAC)](https://solid.github.io/web-access-control-spec/).

{% hint style="danger" %}
Inrupt does not provide support for ESS servers running Web Access Control in Production.
{% endhint %}

To help simplify the handling of different access control mechanisms, the **`solid-client`** library provides universal access control APIs that can be used with either ACP or WAC. That is, for Resources that are controlled by either mechanisms, you can use the universal access control APIs to manage access instead of the mechanism-specific APIs.

{% hint style="info" %}
**Mechanism-specific APIs**

When possible, use the universal access control APIs. However, the universal access control APIs are only available for features/functionalities of ACP and WAC that can be generalized. To handle ACP-specific or WAC-specific situations that cannot be generalized, the **`solid-client`** library provides ACP-specific APIs and WAC-specific APIs.
{% endhint %}

### Access Object

Using the **`solid-client`** library’s access control APIs, you can retrieve or modify the access for a Resource. The **`solid-client`** library handles access as an object of the form:

```javascript
{
  read: <boolean>,
  append: <boolean>,
  write: <boolean>,
}
```

Where a **`<boolean>`** value of:

* **`true`** indicates the access mode has been granted.
* **`false`** indicates the access mode has not been granted.

The following access modes are available:

| Access Mode  | Description                                                                       |
| ------------ | --------------------------------------------------------------------------------- |
| **`read`**   | The ability to view the contents of a Resource.                                   |
| **`append`** | The ability to add new data to a Resource.                                        |
| **`write`**  | The ability to add new data to a Resource, and to change or remove existing data. |

### Retrieve Access Data for a Resource

The **`solid-client`** library provides the following universal access control APIs to retrieve the access for a Resource:

* [getAgentAccess](https://inrupt.github.io/solid-client-js/modules/universalAccess.html#getagentaccess)
* [getAgentAccessAll](https://inrupt.github.io/solid-client-js/modules/universalAccess.html#getagentaccessall)
* [getPublicAccess](https://inrupt.github.io/solid-client-js/modules/universalAccess.html#getpublicaccess)

These functions attempt to fetch the specified Resource, parse its access data that has been explicitly set for the specified Agent or the Public, and return a Promise that either resolves to an access object, or **`null`** if it could not be read.

{% hint style="info" %}
**Note**

* Starting in **`solid-client`** version 1.19, to use the universal access function, import the **`universalAccess`** module from **`@inrupt/solid-client`**.
* To retrieve a Resource’s access data using these functions, the user must have appropriate access to read the access control for that Resource.
  {% endhint %}

**If an access object is returned,**

```javascript
{
  read: <boolean>,
  append: <boolean>,
  write: <boolean>,
}
```

* The returned access is the access granted **directly** to that Agent or **directly** to the Public. That is, the returned access does not include access that may be indirectly set. For example, an Agent has not been directly granted the **`write`** access to a Resource, but **`write`** access has been given to the Public. Then, even though the Agent, by being a member of the general Public, has **`write`** access to the Resource and can modify the Resource, [getAgentAccess](https://inrupt.github.io/solid-client-js/modules/universalAccess.html#getagentaccess) returns **`write`** access as **`false`**.
* The returned access applies only to the specified Resource and not to the Resource’s children. For example, if the [getAgentAccess](https://inrupt.github.io/solid-client-js/modules/universalAccess.html#getagentaccess) function indicates that an agent has **`read`** access to the Resource **`https://example.com/container/`**, that does not mean the agent also has **`read`** access to **`https://example.com/container/child`**.

If **`null`** is returned,

* Access data is inaccessible by the user. Reasons for the inaccessibility are varied and can include:
  * Inadequate access to retrieve the access data for that Resource.
  * The access is defined in a way that is incompatible with the access model used by these APIs.
* It is recommended that you explicitly check for **`null`** to handle the failure in your application.

#### Get Public Access

The function [getPublicAccess](https://inrupt.github.io/solid-client-js/modules/universalAccess.html#getpublicaccess) returns access granted specifically to the general public; that is, to everyone and not to specific Agents. To use [getPublicAccess](https://inrupt.github.io/solid-client-js/modules/universalAccess.html#getpublicaccess), pass it the following parameters:

* The URL of the Resource.
* The options object that contains the **`fetch`** function from an authenticated session. See Authentication.

**Example**

```javascript
// ... import statement for authentication, which includes the fetch function, is omitted for brevity.

import { universalAccess } from "@inrupt/solid-client";

// ... Login logic omitted for brevity.

// Fetch the access explicitly/directly set for the public.
// The returned access can be an object { read: <boolean>, append: <boolean>, ... }
// or null if the access data is inaccessible to the user.
universalAccess.getPublicAccess(
  "https://example.com/resource",   // Resource
  { fetch: fetch }                  // fetch function from authenticated session
).then((returnedAccess) => {
  if (returnedAccess === null) {
    console.log("Could not load access details for this Resource.");
  } else {
    console.log("Returned Public Access:: ", JSON.stringify(returnedAccess));
  }
});
```

**Returned Access**

* The function returns access granted specifically to the general public and not to specific Agents.

  For example, consider a situation where:

  * you grant **`read`** access for a Resource to the Public (see [setPublicAccess](https://inrupt.github.io/solid-client-js/modules/universalAccess.html#setpublicaccess)), and
  * you also grant **`write`** access to specific Agents for the Resource.

  The function returns only **`read`** as **`true`**, even though specific Agents may have additional access to the Resource.
* The returned access applies only to the specified Resource and not to the Resource’s children.

#### Get Agent’s Access for a Resource

The function [getAgentAccess](https://inrupt.github.io/solid-client-js/modules/universalAccess.html#getagentaccess) returns the access that has been explicitly granted to the specified Agent for the Resource. To use [getAgentAccess](https://inrupt.github.io/solid-client-js/modules/universalAccess.html#getagentaccess), pass it the following parameters:

* The URL of the Resource.
* The WebID of the Agent whose access you want to return.
* The options object that contains the **`fetch`** function from an authenticated session. See Authentication.

**Example**

```javascript
// ... import statement for authentication, which includes the fetch function, is omitted for brevity.

import { universalAccess } from "@inrupt/solid-client";

// ... Login logic omitted for brevity.

// Fetch the access explicitly/directly set for an agent.
// (i.e., omits access inherited through public membership).
// The returned access can be an object { read: <boolean>, append: <boolean>, ... } 
// or null if the access data is inaccessible to the user.
universalAccess.getAgentAccess(
  "https://example.com/resource",       // resource  
  "https://id.example.com/someWebId",   // agent
  { fetch: fetch }                      // fetch function from authenticated session
).then((agentAccess) => {
  logAccessInfo("https://id.example.com/someWebId", agentAccess, "https://example.com/resource");
});

function logAccessInfo(agent, agentAccess, resource) {
  console.log(`For resource::: ${resource}`);
  if (agentAccess === null) {
    console.log(`Could not load ${agent}'s access details.`);
  } else {
    console.log(`${agent}'s Access:: ${JSON.stringify(agentAccess)}`);
  }
}
```

**Returned Access**

* The function does not return access that has been indirectly granted to the Agent, such as access granted to the Public.

  For example, consider a scenario where:

  * you grant **`read`** access for a Resource to the Public (see [setPublicAccess](https://inrupt.github.io/solid-client-js/modules/universalAccess.html#setpublicaccess)), and
  * you set **`read`** access to **`false`** explicitly for a specific Agent (see [setAgentAccess](https://inrupt.github.io/solid-client-js/modules/universalAccess.html#setagentaccess)) for that Resource.

  For this Agent, the function returns **`read`** as **`false`**. However, in the absence of any other access rules that may affect the Agent’s **`read`** access, the Agent can **`read`** the Resource since the Public access grants the **`read`** to the Agent.
* The returned access applies only to the specified Resource and not to the Resource’s children.

#### Get All Agents’ Access for a Resource

The function [getAgentAccessAll](https://inrupt.github.io/solid-client-js/modules/universalAccess.html#getagentaccessall) returns the explicitly set access to the Resource for each Agent whose access to the Resource has been explicitly set.

To use [getAgentAccessAll](https://inrupt.github.io/solid-client-js/modules/universalAccess.html#getagentaccessall), pass it the following parameters:

* The URL of the Resource.
* The options object that contains the **`fetch`** function from an authenticated session. See Authentication.

**Example**

```javascript
// ... import statement for authentication, which includes the fetch function, is omitted for brevity.

import { universalAccess } from "@inrupt/solid-client";

// ... Login logic omitted for brevity.

// Fetch the access for all agents whose access has been explicitly/directly set
// (i.e., omits access inherited through public membership).
// The returned access can be an object { read: <boolean>, append: <boolean>, ... } 
// or null if the access data is inaccessible to the user.
universalAccess.getAgentAccessAll(
  "https://example.com/resource", // resource
  { fetch: fetch }                // fetch function from authenticated session
).then((accessByAgent) => {
  // => accessByAgent is an object with Agent WebIDs as keys,
  //    and their associated access object {read: <boolean>, ... } as values.
  for (const [agent, agentAccess] of Object.entries(accessByAgent)) {
    logAccessInfo(agent, agentAccess, resource);
  }
});

function logAccessInfo(agent, agentAccess, resource) {
  console.log(`For resource::: ${resource}`);
  if (agentAccess === null) {
    console.log(`Could not load ${agent}'s access details.`);
  } else {
    console.log(`${agent}'s Access:: ${JSON.stringify(agentAccess)}`);
  }
}
```

**Returned Access**

* The function does not return access that has been indirectly granted to an Agent, such as access granted to the Public.

  For example, consider a scenario where:

  * you grant **`read`** access for a Resource to the Public (see [setPublicAccess](https://inrupt.github.io/solid-client-js/modules/universalAccess.html#setpublicaccess)), and
  * you set **`read`** access to **`false`** explicitly for a specific Agent (see [setAgentAccess](https://inrupt.github.io/solid-client-js/modules/universalAccess.html#setagentaccess)) for that Resource.

  For this Agent, the function returns **`read`** as **`false`**. However, in the absence of any other access rules that may affect the Agent’s **`read`** access, the Agent can **`read`** the Resource since the Public access grants the **`read`** to the Agent.
* The returned access applies only to the specified Resource and not to the Resource’s children.

### Changing Access Data for a Resource

The **`solid-client`** library provides the following universal access control APIs to modify the access for a Resource:

* [setAgentAccess](https://inrupt.github.io/solid-client-js/modules/universalAccess.html#setagentaccess)
* [setPublicAccess](https://inrupt.github.io/solid-client-js/modules/universalAccess.html#setpublicaccess)

{% hint style="info" %}
**Note**

* Starting in **`solid-client`** version 1.19, to use the universal access function, import the **`universalAccess`** module from **`@inrupt/solid-client`**.
* To retrieve a Resource’s access data using these functions, the user must have appropriate access to modify the access for that Resource.
  {% endhint %}

These functions modify the access that is directly associated with an Agent or the general Public. An Agent can have additional access granted indirectly, such as through access granted to the Public.

Pass into the function the access object with the specific modes you want to set. Set these modes to

* **`true`** to grant that mode.
* **`false`** to revoke access for that mode. That is, using these functions to set a mode to **`false`** removes access granted directly to the given agent/public, but does not prevent that access from being granted via other indirect means.

Modes that are unspecified in the access object remain unchanged.

These functions attempt to fetch the specified Resource, parse its access data, apply the specified access changes (*unspecified access modes remain unchanged*), save the access data back to the Pod, and return a Promise that either resolves to the updated access data, or **`null`** if it could not be read or changed.

If an access object is returned,

* The returned access applies only to the specified Resource and not to the Resource’s children. For example, if the updated access indicates that an agent has **`read`** access to the Resource **`https://example.com/container/`**, that does not mean the agent also has **`read`** access to **`https://example.com/container/child`**.

If **`null`** is returned,

* Access data is inaccessible and/or unmodifiable by the user. Reasons for these are varied and can include:
  * Inadequate access to retrieve or modify the access data for that Resource.
  * The access is defined in a way that is incompatible with the access model used by these APIs.
* It is recommended that you explicitly check for **`null`** to handle the failure in your app.

#### Change Public Access

The function [setPublicAccess](https://inrupt.github.io/solid-client-js/modules/universalAccess.html#setpublicaccess) sets access specifically for the general public; that is for everyone and not for specific Agents.

* Setting an access mode to **`true`** grants that mode.
* Setting an access mode to **`false`** revokes access for that mode for the Public. Agents may still be granted the access mode directly. For example, if you set **`read`** access to **`false`** for a Resource using [setPublicAccess](https://inrupt.github.io/solid-client-js/modules/universalAccess.html#setpublicaccess), and you also grant **`read`** access to specific Agents for the Resource, [getPublicAccess](https://inrupt.github.io/solid-client-js/modules/universalAccess.html#getpublicaccess) returns **`read`** as **`false`**, even though specific Agents may have **`read`** access to the Resource and can read from the Resource.
* Modes that are unspecified in the access object remain unchanged.

```javascript
// ... import statement for authentication, which includes the fetch function, is omitted for brevity.

import { universalAccess } from "@inrupt/solid-client";

// ... Login logic omitted for brevity.

// Set access for the general Public (i.e., everyone):
// - Grant Read access
// - Remove any previously granted Write access.
// - Leave the rest (append, controlRead and controlWrite) unchanged.
// The returned access can be an object { read: <boolean>, append: <boolean>, ... }
// or null if the access data is inaccessible to the user.
universalAccess.setPublicAccess(
  "https://example.com/resource",  // Resource
  { read: true, write: false },    // Access object
  { fetch: fetch }                 // fetch function from authenticated session
).then((newAccess) => {
  if (newAccess === null) {
    console.log("Could not load access details for this Resource.");
  } else {
    console.log("Returned Public Access:: ", JSON.stringify(newAccess));

  }
});
```

{% hint style="info" %}
**Note**

The returned access applies only to the specified Resource and not to the Resource’s children.
{% endhint %}

#### Change Agent Access

The function [setAgentAccess](https://inrupt.github.io/solid-client-js/modules/universalAccess.html#setagentaccess) sets access for a specific Agent. The function does not affect access that may have been granted to the Agent indirectly, such as access granted to the Public.

* Setting an access mode to **`true`** grants that mode.
* Setting an access mode to **`false`** revokes access for that mode. The Agent may still be granted the access mode indirectly through general Public access.
* Modes that are unspecified in the access object remain unchanged.

```javascript
// ... import statement for authentication, which includes the fetch function, is omitted for brevity.

import { universalAccess } from "@inrupt/solid-client";

// ... Login logic omitted for brevity.

// Set access for an Agent:
// - Grant Read access
// - Remove any previously granted Write access.
// - Leave the rest (append, controlRead and controlWrite) unchanged.
// The returned access can be an object { read: <boolean>, append: <boolean>, ... }
// or null if the access data is inaccessible to the user.
universalAccess.setAgentAccess(
  "https://example.com/resource",         // Resource
  "https://id.example.com/someWebId",     // Agent
  { read: true, write: false, },          // Access object
  { fetch: fetch }                         // fetch function from authenticated session
).then((newAccess) => {
  logAccessInfo("https://id.example.com/someWebId", newAccess, "https://example.com/resource")
});

function logAccessInfo(agent, agentAccess, resource) {
  console.log(`For resource::: ${resource}`);
  if (agentAccess === null) {
    console.log(`Could not load ${agent}'s access details.`);
  } else {
    console.log(`${agent}'s Access:: ${JSON.stringify(agentAccess)}`);
  }
}
```

{% hint style="info" %}
**Note**

The returned access applies only to the specified Resource and not to the Resource’s children.
{% endhint %}

### Mechanism-Specific Access Control APIs

In addition to the universal access control APIs, the **`solid-client`** library also provides APIs specific to ACP and APIs specific to WAC.

When possible, use the universal access control APIs. However, the universal access control APIs are only available for features/functionalities of WAC and ACP that can be generalized. To handle a WAC-specific or ACP-specific situations that cannot be generalized, use the mechanism-specific APIs, such as:

* To handle error conditions that are specific to the access control mechanism. For example, if a WAC-controlled Resource does not have a reachable [Fallback ACL](/reference/glossary#fallback-acl), you may want to initialize a Resource-specific ACL anyway.
* To use mechanism-specific functionality. For example, in ACP, you can deny (not just revoke/unset) access, specify a creator-matching rule, specify a client-application matching rule. These functionalities are specific to ACP and are not available in WAC.
* To specify access that does not have a universal access API equivalent. For example, the universal access APIs only affect or refer to the access of the Resource itself and not its children, if the Resource is a [Container](/reference/glossary#container).

{% hint style="info" %}
**Note**

Using the mechanism-specific APIs, it is possible for you to define an access model that is incompatible with the universal access APIs and, therefore, can only be managed with the mechanism-specific APIs.
{% endhint %}


# Authentication in Solid

Authentication is the process of verifying the identity of an [agent](/reference/glossary#agent). To access private data on Solid [Pods](/reference/glossary#pods), you must authenticate as a user/agent who has been granted appropriate access to that data.

Authentication in Solid can be performed:

* [via OIDC directly in the browser](/guides/authentication-in-solid/authentication-from-browser)
* [via OIDC via a backend](/guides/authentication-in-solid/authentication-server-side)
* [Via OAuth Client Credentials](/guides/authentication-in-solid/authentication-single-user-application)

## Session object

Both `@inrupt/solid-client-authn-browser` and `@inrupt/solid-client-authn-node` libraries expose a `Session` class which represents a stateful user session.

### Session information

Information about the session can be obtained using the `info` property on a `Session` instance, exposing the following fields:

<table data-header-hidden><thead><tr><th width="164.578125"></th><th></th></tr></thead><tbody><tr><td><code>isLoggedIn</code></td><td>Boolean flag indicating whether the session is currently able to make authenticated requests.</td></tr><tr><td><code>webId</code></td><td>The WebID of the user if logged in, undefined otherwise.</td></tr><tr><td><code>clientAppId</code></td><td>The application identifier, or a “Public app” identifier if the app does not provide its own. This is undefined until the session is logged in and the app identifier has been verified.</td></tr><tr><td><code>sessionId</code></td><td>A unique identifier for the session. This is generated automatically when creating a new session.</td></tr><tr><td><code>expirationDate</code></td><td>UNIX timestamp (number of milliseconds since Jan 1st 1970) representing the time until which this session is valid.</td></tr></tbody></table>

### Session Lifecycle

The `Session` class provides the following methods to drive its authentication lifecycle:

<table data-header-hidden><thead><tr><th width="222.75390625"></th><th></th></tr></thead><tbody><tr><td><code>login</code></td><td>Initiates the login process, potentially redirecting the user to their identity provider.</td></tr><tr><td><code>handleIncomingRedirect</code></td><td>Completes the login process by parsing information sent by the identity provider after successful authentication and a redirection to the application.</td></tr><tr><td><code>logout</code></td><td>Terminates the user session. By default, only local credentials are cleaned up on logout, but the function can be called with a flag set to log the user out of their OpenID Provider as well. In the latter case, the user session will be terminated across all of their Solid applications, not only the one performing the logout.</td></tr></tbody></table>

The [server-side](/guides/authentication-in-solid/authentication-server-side), [in-browser](/guides/authentication-in-solid/authentication-from-browser) and [script](/guides/authentication-in-solid/authentication-single-user-application) authentication pages provide details about the specifics of each environment.

### Session data retrieval

The `Session` class exposes a `fetch` method. When the user session is logged in, the `fetch` method adds authentication information to the HTTP requests. The `fetch` method signature mimics the [standard fetch API](https://fetch.spec.whatwg.org/), making it compatible with any code expecting a fetch function.

### Session Events

The `Session` object exposes an `events` attribute which can be used to listen to various session-related events. `events` exposes an isomorphic API similar to the [NodeJS EventEmitter class](https://nodejs.org/docs/latest-v22.x/api/events.html#class-eventemitter), with methods such as `on` to register a callback to an event or `off` to remove the callback.

A `Session` instance will emit the following events:

<table data-header-hidden><thead><tr><th width="172.96484375"></th><th></th></tr></thead><tbody><tr><td><code>login</code></td><td>Emitted when a session successfully logs in.</td></tr><tr><td><code>logout</code></td><td>Emitted when a session successfully logs out.</td></tr><tr><td><code>sessionExpired</code></td><td>Emitted when a session’s token expires and was not refreshed.</td></tr><tr><td><code>sessionExtended</code></td><td>Emitted when a session’s token is refreshed, extending its lifetime.</td></tr><tr><td><code>error</code></td><td>Fired when an error occurs during session operations.</td></tr></tbody></table>

Typescript types are used to document the arguments passed to the callbacks for each event.

The [server-side](/guides/authentication-in-solid/authentication-server-side) authentication pages document events specific to the NodeJS environment.


# Authentication from Browser

Inrupt provides the `@inrupt/solid-client-authn-browser` library to authenticate in a browser.

```
npm install @inrupt/solid-client-authn-browser
```

For applications implementing [Authorization Code Flow](https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowSteps):

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.

<figure><img src="/files/aczLsPZtA05yOmdNM8oj" alt=""><figcaption></figcaption></figure>

{% hint style="warning" %}
The login is only complete after the user is redirected back to the application; i.e., your application must be reloaded between the start of the login and its completion.
{% endhint %}

### Login Process

1. The application calls the library’s [login()](https://inrupt.github.io/solid-client-authn-js/browser/functions.html#login) function to start the process. To the function, the application passes in the following login options:

   <table data-header-hidden><thead><tr><th width="150.7578125"></th><th></th></tr></thead><tbody><tr><td><a href="https://inrupt.github.io/solid-client-authn-js/browser/interfaces/ILoginInputOptions.html#oidcissuer">oidcIssuer</a></td><td>Set to the user’s Solid Identity Provider (where the <em>login</em> function will send the user).</td></tr><tr><td><a href="https://inrupt.github.io/solid-client-authn-js/browser/interfaces/ILoginInputOptions.html#redirecturl">redirectUrl</a></td><td><p>Set to the location where the Solid Identity Provider will send the user back once logged in and where the application can complete the login process (i.e., call <a href="https://inrupt.github.io/solid-client-authn-js/browser/functions.html#handleincomingredirect">handleIncomingRedirect()</a>).</p><div data-gb-custom-block data-tag="hint" data-style="warning" class="hint hint-warning"><p>Specify a static <code>redirectUrl</code> value that does not change with application routes or hash or query parameters.</p><p>For instance, instead of specifying <code>window.location.href</code> which might change for a Single Page Application (SPA) with multiple routes, you can use the URL constructor, such as <code>new URL("/path/to/redirectHandlingPage", window.location.href).toString()</code>.</p></div></td></tr><tr><td><a href="https://inrupt.github.io/solid-client-authn-js/browser/interfaces/ILoginInputOptions.html#clientname">clientName</a></td><td>(Optional) Set to the display name for the client. During the login process, the user has to approve the client’s access to the requested data (such as the user’s WebID). The <code>clientName</code> is the name displayed during the approval step. If <code>clientName</code> is not provided, a random identifier is generated and used for the name.</td></tr><tr><td><code>customScopes</code></td><td>(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.</td></tr></tbody></table>

   \
   For other options available to the function, see [ILoginInputOptions](https://inrupt.github.io/solid-client-authn-js/browser/interfaces/ILoginInputOptions.html).\\

   This process redirects the user from your application to the Solid Identity Provider.
2. 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, namely to the `redirectUrl` specified in the login.
3. From the `redirectUrl` page, the application calls the library’s [handleIncomingRedirect()](https://inrupt.github.io/solid-client-authn-js/browser/functions.html#handleincomingredirect) to complete the login process. The function collects the information provided by the Solid Identity Provider.

   The session is logged in only after it handles the incoming redirect from the Solid Identity Provider.

{% hint style="info" %}
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](/guides/authentication-in-solid/authentication-from-browser/session-restore-upon-browser-refresh).
{% endhint %}

Once logged in, the library’s [fetch()](https://inrupt.github.io/solid-client-authn-js/browser/functions.html#fetch) function can retrieve data using the available login information. You can pass this [fetch()](https://inrupt.github.io/solid-client-authn-js/browser/functions.html#fetch) function as an option to the `solid-client` functions (e.g., [getSolidDataset](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#getsoliddataset), [saveSolidDatasetAt](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#savesoliddatasetat)) to include the user’s credentials with a request.

### Example

The example uses a single-user/single-Session application `https://myapp.example.com` that includes the following routes:

* `https://myapp.example.com/callback`
* `https://myapp.example.com/todolist`

The example assumes the use of Inrupt PodSpaces (i.e., the Identity Provider is `https://login.inrupt.com`).

#### Start Login

The application has a login panel at the top, which calls the `solid-client-authn-browser` library’s [login()](https://inrupt.github.io/solid-client-authn-js/browser/functions.html#login) function to start the login process. That is, the login panel calls the below code to start the login process:

```javascript
import {  login, getDefaultSession } from '@inrupt/solid-client-authn-browser'

// ...

async function startLogin() {
  // Start the Login Process if not already logged in.
  if (!getDefaultSession().info.isLoggedIn) {
    await login({
      oidcIssuer: "https://login.inrupt.com",
      redirectUrl: new URL("/callback", window.location.href).toString(),
      clientName: "My application"
    });
  }
}
```

The app calls [login()](https://inrupt.github.io/solid-client-authn-js/browser/functions.html#login) with the following parameters:

<table data-header-hidden><thead><tr><th width="156.24609375"></th><th></th></tr></thead><tbody><tr><td><a href="https://inrupt.github.io/solid-client-authn-js/browser/interfaces/ILoginInputOptions.html#oidcissuer">oidcIssuer</a></td><td>Set to <code>"https://login.inrupt.com"</code>. (The example assumes the use of <a href="/pages/w20IKttecwXUMZyR6fuU">Inrupt PodSpaces</a>.)</td></tr><tr><td><a href="https://inrupt.github.io/solid-client-authn-js/browser/interfaces/ILoginInputOptions.html#redirecturl">redirectUrl</a></td><td>Set to <code>new URL("/callback", window.location.href).toString()</code>, which resolves to <code>https://myapp.example.com/callback</code></td></tr><tr><td><a href="https://inrupt.github.io/solid-client-authn-js/browser/interfaces/ILoginInputOptions.html#clientname">clientName</a></td><td>Set to <code>"My application"</code>.</td></tr></tbody></table>

The [login()](https://inrupt.github.io/solid-client-authn-js/browser/functions.html#login) sends the user to the specified `oidcIssuer`, in this example `https://login.inrupt.com`. The user logs in and is redirected back to the `redirectUrl` to complete the login process.

#### Complete Login

Once the user logs in, the user is redirected back to `redirectUrl`. For the application, the `redirectUrl` value is `new URL("/callback", window.location.href).toString()`, which resolves to `https://myapp.example.com/callback`.

The page at `redirectUrl` calls the `solid-client-authn-browser` library’s [handleIncomingRedirect()](https://inrupt.github.io/solid-client-authn-js/browser/functions.html#handleincomingredirect) method to complete the login; specifically, it calls the below code which calls the [handleIncomingRedirect()](https://inrupt.github.io/solid-client-authn-js/browser/functions.html#handleincomingredirect) method:

```javascript
import { handleIncomingRedirect } from '@inrupt/solid-client-authn-browser'

// ...

async function completeLogin() {
   await handleIncomingRedirect();
}
```

#### Perform Authenticated Operations

Once authentication is complete, pass the [fetch()](https://inrupt.github.io/solid-client-authn-js/browser/classes/Session.html#fetch) function (from the authenticated user’s Session) as an option to various `solid-client` functions to read and write data to a Pod where the logged-in user has the appropriate access.

For example, after the user has authenticated, the app can call the following code to make authenticated [getSolidDataset](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#getsoliddataset) and [saveSolidDatasetAt](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#savesoliddatasetat) calls:

```javascript
import { fetch } from '@inrupt/solid-client-authn-browser'
import { getSolidDataset, saveSolidDatasetAt } from "@inrupt/solid-client";

async function readTodoList() {

  // 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://storage.inrupt.com/somepod/todolist",
    { fetch: fetch }
  );
}

async function updateToDoList(myChangedDataset) {

  // The user must be someone with Write access to the specified URL.
  const savedSolidDataset = await saveSolidDatasetAt(
    "https://storage.inrupt.com/somepod/todolist",
    myChangedDataset,
    { fetch: fetch }
  );
}

// ...
```


# Session Restore upon Browser Refresh

For security reasons, `@inrupt/solid-client-authn-browser` does not store access tokens in the [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) or any other place that persists across page refresh.

This means that upon page refresh/reload, the access token is lost, and your web application will be logged out. This page provides various procedures to log your application back in without user interaction.

### Enable Session Restore

As part of the [authentication flow in a browser environment](/guides/authentication-in-solid/authentication-from-browser), your application calls `@inrupt/solid-client-authn-browser`’s [handleIncomingRedirect()](https://inrupt.github.io/solid-client-authn-js/browser/functions.html#handleincomingredirect) function. The [handleIncomingRedirect()](https://inrupt.github.io/solid-client-authn-js/browser/functions.html#handleincomingredirect) function can accept the following properties in its `options` parameter:

<table><thead><tr><th width="235.69921875">Option</th><th>Description</th></tr></thead><tbody><tr><td><code>url</code></td><td><p>The URL the user is being redirected from the Solid Identity Provider.</p><p>Defaults to <code>window.location.href</code> if unspecified.</p></td></tr><tr><td><code>restorePreviousSession</code></td><td><p>A boolean that indicates whether the application should log the user back in after a page reload without user intervention. If the user is not already logged in at the time of the page reload, <code>restorePreviousSession</code> has no impact, and the user manually logs in to the Solid Identity Provider as part of the application’s login flow.</p><p>Default: <code>false</code></p></td></tr></tbody></table>

To automatically log the user back in after a page refresh, in your application, call [handleIncomingRedirect()](https://inrupt.github.io/solid-client-authn-js/browser/functions.html#handleincomingredirect) with `restorePreviousSession` set `true`. The initial authentication and later page refresh has the following flow:

1. For the initial authentication, the application calls [login()](https://inrupt.github.io/solid-client-authn-js/browser/functions.html#login) to start the process. The function redirects the user away to the user’s Solid Identity Provider where the user can login.
2. The user logs in. Upon successful login, the Solid Identity Provider sends the user back to your application.
3. The application call [handleIncomingRedirect({ restorePreviousSession : true })](https://inrupt.github.io/solid-client-authn-js/browser/functions.html#handleincomingredirect) to complete the login process.

   The application can now perform authenticated operations on the user’s Pod.
4. At some point, the user refreshes the page, causing the application to reload and log out the user. That is, the application can no longer make authenticated requests.
5. However, if on reload, the application starts by calling [handleIncomingRedirect({ restorePreviousSession : true })](https://inrupt.github.io/solid-client-authn-js/browser/functions.html#handleincomingredirect), the user goes through a process called *silent authentication*. In a *silent authentication* process, the browser redirects to the Solid Identity Provider as before but with a configuration that immediately returns the user back to the application without any user interaction.

   That is, whether the user is still logged in to the Solid Identity Provider or not, the user is redirected back to the application, specifically to the `redirectUrl` parameter set during the [login()](https://inrupt.github.io/solid-client-authn-js/browser/functions.html#login) call.
6. When the user returns back to the application, the application calls [handleIncomingRedirect({ restorePreviousSession : true })](https://inrupt.github.io/solid-client-authn-js/browser/functions.html#handleincomingredirect). If the user is still logged in when redirected back, this completes the login process, and the user is logged back in.

   The user can refresh the page again and repeat the same process as long as the user is still logged in to the Solid Identity Provider.

{% hint style="warning" %}
When session restore is enabled, calling [handleIncomingRedirect()](https://inrupt.github.io/solid-client-authn-js/browser/functions.html#handleincomingredirect) may cause a redirection away from the current page.
{% endhint %}

### Use Session Restore Event Handler

For the initial authentication, your application calls the [login()](https://inrupt.github.io/solid-client-authn-js/browser/functions.html#login) function with various parameters, including the `redirectUrl` parameter. This parameter is set to the location where the Solid Identity Provider should return your user once logged in.

If session restore is enabled and the user reloads any page of your application (such as by refreshing the page or following a link), this `redirectURL` is also where the Solid Identity Provider returns the user during the *silent authentication*. In some cases, this `redirectURL` may differ from the actual page the user reloaded, such as if your application uses a framework with client-side routing like [NextJS](https://nextjs.org/). In addition to the redirection, this also results in loss of application state.

You can use the session’s [events](https://inrupt.github.io/solid-client-authn-js/browser/classes/Session.html#events) function to route the user back to the refreshed page instead of to the `redirectURL`. That is, with session restore enabled, when the user returns back to the application after the *silent authentication*, a `sessionRestore` event is fired. You can use the session’s [events.on](https://inrupt.github.io/solid-client-authn-js/browser/interfaces/ISessionEventListener.html#on) function with the following parameters to register your own callback to invoke:

* `"sessionRestore"` (the event to associate with the callback)
* a callback to be invoked with the URL of the refreshed page .

Your callback can implement any application-specific logic needed to restore the user back to the refreshed page.

{% hint style="warning" %}
While this process routes the user back to the refreshed page, it does not address the loss of application state.
{% endhint %}

The following NextJS example uses [onSessionRestore()](https://inrupt.github.io/solid-client-authn-js/browser/functions.html#onsessionrestore) to redirect back to the current page after a refresh:

```javascript
import { handleIncomingRedirect, EVENTS } from "@inrupt/solid-client-authn-browser";
import { useEffect } from "react";
import { useRouter } from "next/router";

export default function MyApp() {
  const router = useRouter();

  // 1. Register the callback to restore the user's page after refresh and
  //    redirection from the Solid Identity Provider.
  session.events.on(EVENTS.SESSION_RESTORED, (url) => {
    router.push(url);
  });

  useEffect(() => {
    // 2. When loading the component, call `handleIncomingRedirect` to authenticate
    //    the user if appropriate, or to restore a previous session.
    handleIncomingRedirect({
      restorePreviousSession: true,
    }).then((info) => {
      console.log(`Logged in with WebID [${info.webId}]`);
    });
  }, []);

  // ... rest of the component, where `login()` should be called to initiate the
  // login process.
}
```


# 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](https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowSteps):

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.

<figure><img src="/files/aczLsPZtA05yOmdNM8oj" alt=""><figcaption></figcaption></figure>

### 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:**

1. Create a new [Session](https://inrupt.github.io/solid-client-authn-js/node/classes/Session.html#constructor) 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 the `Session` identifier via a cookie, as the identifier is required in subsequent steps.\
   \
   You should also ensure that you capture the `authorizationRequest` 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.
2. Call the [Session.login()](https://inrupt.github.io/solid-client-authn-js/node/classes/Session.html#login) function to start the login process. Pass in the following login options:\
   \\

   <table data-header-hidden><thead><tr><th width="163.3671875"></th><th></th></tr></thead><tbody><tr><td><a href="https://inrupt.github.io/solid-client-authn-js/node/interfaces/ILoginInputOptions.html#oidcissuer">oidcIssuer</a></td><td>Set to the user’s Solid Identity Provider (where <code>handleRedirect</code> will send the user).</td></tr><tr><td><a href="https://inrupt.github.io/solid-client-authn-js/node/interfaces/ILoginInputOptions.html#redirecturl">redirectUrl</a></td><td>Set to the location that the Solid Identity Provider will send the user back once logged in.</td></tr><tr><td><a href="https://inrupt.github.io/solid-client-authn-js/node/interfaces/ILoginInputOptions.html#handleredirect">handleRedirect</a></td><td>Set to a callback function that sends users to their Solid Identity Provider.</td></tr><tr><td><a href="https://inrupt.github.io/solid-client-authn-js/node/interfaces/ILoginInputOptions.html#clientname">clientName</a></td><td>(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 <code>clientName</code> is the name displayed during the approval step. If <code>clientName</code> is not provided, a random identifier is generated and used for the name.</td></tr><tr><td><code>customScopes</code></td><td>(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.</td></tr></tbody></table>

   \
   For other options available to the function, see [ILoginInputOptions](https://inrupt.github.io/solid-client-authn-js/node/interfaces/ILoginInputOptions.html).\
   \
   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:

{% tabs %}
{% tab title="Using an external storage" %}
{% hint style="danger" %}
This requires using a [Solid-OIDC Client ID](https://solid.github.io/solid-oidc/#clientids-document). Make sure your Client Identifier is a URL pointing to a Client Identifier Document.
{% endhint %}

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.
{% endtab %}

{% tab title="Using legacy in-memory storage" %}
{% hint style="danger" %}
The legacy in-memory storage relies on the process memory being consistent. This means it is not a suitable approach in a cluster deployment where the same session could have requests handled by different nodes.
{% endhint %}

To retrieve a user’s session, `@inrupt/solid-client-authn-node` provides the [getSessionFromStorage()](https://inrupt.github.io/solid-client-authn-js/node/functions.html#getsessionfromstorage) function. It takes a Session ID as its argument, and returns a `Session` instance.
{% endtab %}
{% endtabs %}

***

#### **Completing the authentication flow:**

3. Retrieve the session using one of the methods above.
4. 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).
5. To complete the login process, call [Session.handleIncomingRedirect()](https://inrupt.github.io/solid-client-authn-js/node/classes/Session.html#handleincomingredirect), passing in the URL of the page handling the redirect. [Session.handleIncomingRedirect()](https://inrupt.github.io/solid-client-authn-js/node/classes/Session.html#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.
6. After [Session.handleIncomingRedirect()](https://inrupt.github.io/solid-client-authn-js/node/classes/Session.html#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:

{% tabs %}
{% tab title="Using an external storage" %}
{% hint style="danger" %}
This requires using a [Solid-OIDC Client ID](https://solid.github.io/solid-oidc/#clientids-document). Make sure your Client Identifier is a URL pointing to a Client Identifier Document.
{% endhint %}

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.
{% endtab %}

{% tab title="Using legacy in-memory storage" %}
{% hint style="danger" %}
The legacy in-memory storage relies on the process memory being consistent. This means it is not a suitable approach in a cluster deployment where the same session could have requests handled by different nodes.
{% endhint %}

To retrieve a user’s session, `@inrupt/solid-client-authn-node` provides the [getSessionFromStorage()](https://inrupt.github.io/solid-client-authn-js/node/functions.html#getsessionfromstorage) function. It takes a Session ID as its argument, and returns a `Session` instance. By default, `getSessionFromStorage` will refresh the session. You can disable this behavior to manually control the refreshing of the session by setting `refreshSession: false` in the second argument of `getSessionFromStorage`.
{% endtab %}
{% endtabs %}

***

#### **Making an authenticated requests:**

7. Once logged in, the `Session` object provides a [fetch()](https://inrupt.github.io/solid-client-authn-js/node/classes/Session.html#fetch) function that retrieves data using available login information.\
   \
   You can pass this [fetch()](https://inrupt.github.io/solid-client-authn-js/node/classes/Session.html#fetch) function as an option to the `solid-client` functions (e.g., [getSolidDataset](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#getsoliddataset), [saveSolidDatasetAt](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#savesoliddatasetat)) to include the user’s credentials with a request.

#### **Logging a session out:**

{% tabs %}
{% tab title="Using an external storage" %}
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`.
{% endtab %}

{% tab title="Using legacy in-memory storage" %}
{% hint style="danger" %}
The legacy in-memory storage relies on the process memory being consistent. This means it is not a suitable approach in a cluster deployment where the same session could have requests handled by different nodes.
{% endhint %}

8. Call the session’s [logout()](https://inrupt.github.io/solid-client-authn-js/node/classes/Session.html#logout) method. Logging a session out removes it from the in-memory storage as well as disabling its access to private resources.
   {% endtab %}
   {% endtabs %}

#### **Getting a list of all the sessions currently in storage:**

{% tabs %}
{% tab title="Using an external 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.
{% endtab %}

{% tab title="Using legacy in-memory storage" %}
{% hint style="danger" %}
The legacy in-memory storage relies on the process memory being consistent. This means it is not a suitable approach in a cluster deployment where the same session could have requests handled by different nodes.
{% endhint %}

Call [getSessionIdFromStorageAll()](https://inrupt.github.io/solid-client-authn-js/node/functions.html#getsessionidfromstorageall). This function return the session identifiers in the legacy in-memory storage. These can then be used calling [getSessionFromStorage()](https://inrupt.github.io/solid-client-authn-js/node/functions.html#getsessionfromstorage).
{% endtab %}
{% endtabs %}

### Managing the Session tokens

{% hint style="danger" %}
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.
{% endhint %}

#### Exchanging tokens with a `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:

<table><thead><tr><th width="184.7109375">Token Type</th><th>Description</th></tr></thead><tbody><tr><td><strong>Short-lived tokens</strong></td><td><ul><li><strong>Access Token</strong>: Used to authenticate API requests to protected resources. Typically valid for a short period (minutes to hours).</li><li><strong>ID Token</strong>: Contains user identity information, used for authentication purposes. Has a similar short lifespan to the Access Token.</li></ul></td></tr><tr><td><strong>Long-lived token</strong></td><td><ul><li><strong>Refresh Token</strong>: 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).</li></ul></td></tr></tbody></table>

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](https://expressjs.com/) server example uses the `@inrupt/solid-client-authn-node` library to log in to a Solid server.

{% hint style="info" %}
In the example, the `cookie-session` Express middleware is used to associate the session ID to the user’s browser through a cookie.
{% endhint %}

```javascript
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=&lt;resource URL&gt;`.</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].`
  );
});
```


# Authentication Single-User Application

Inrupt provides the `@inrupt/solid-client-authn-node` library to authenticate in Node.js.

```
npm install @inrupt/solid-client-authn-node
```

## Node.js: Single-User Application

Your application can use the `@inrupt/solid-client-authn-node` library to handle the user authentication flow.

Although your application can start the authentication flow that involves browser-based user interactions with a Solid Identity Provider, as an alternative, you can separately obtain refresh tokens and client credentials for use in your application.

That is, if your Solid Identity Provider supports refresh tokens and/or client credentials as grant types, then:

1. As a prerequisite, you can first obtain:

   * refresh token and client credentials (from dynamic or static registration of the application), or
   * client credentials from static registration of the application.

   This is done separately from the application.
2. Then, in your application, you can use either:
   * refresh tokens and client credentials, or
   * client credentials only if the application is statically registered.

{% hint style="info" %}
**Refresh Tokens/Client Credentials Support**

Solid Identity Providers are not required to support either refresh tokens or client credentials.

Inrupt’s ESS and PodSpaces support refresh tokens and client credentials.
{% endhint %}

### Authenticate with Statically Registered Client Credentials

**Prerequisite**

If supported by your Solid Identity Provider, statically register your application. The registration results in a Client ID and Client Secret pair.

For example, if using the Solid Identity Provider for Inrupt’s Pod Spaces, you can statically register your application via its Inrupt Application Registration page.

**Update Application Code**

For a statically registered application, you can use the `@inrupt/solid-client-authn-node` library with the client credentials for the the user authentication flow:

1. Create a new [Session](https://inrupt.github.io/solid-client-authn-js/node/classes/Session.html#constructor) for the user. By default, the Session is *periodically* refreshed in the background using the refresh token; you can override this periodic behavior by specifying `keepAlive: false` as a Session option to the Session constructor.
2. Call the [Session.login()](https://inrupt.github.io/solid-client-authn-js/node/classes/Session.html#login) function, passing in the [ILoginInputOptions](https://inrupt.github.io/solid-client-authn-js/node/interfaces/ILoginInputOptions.html).

   Although you can pass in the options (`oidcIssuer`, `redirectUrl`, `handleRedirect`) to start the authentication flow, you can instead pass in the following options with the values obtained from the prerequisite section to login without the manual, browser-based user interactions:

   * [clientId](https://inrupt.github.io/solid-client-authn-js/node/interfaces/ILoginInputOptions.html#clientid)
   * [clientSecret](https://inrupt.github.io/solid-client-authn-js/node/interfaces/ILoginInputOptions.html#clientsecret)
   * [oidcIssuer](https://inrupt.github.io/solid-client-authn-js/node/interfaces/ILoginInputOptions.html#oidcissuer), the Solid Identity Provider where your `Client ID` and `Client Secret` have been registered.
   * `customScopes`, an 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.

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

{% hint style="danger" %}
Safeguard your `clientId` and `clientSecret` values. Do not share these with any third parties as anyone with your `clientId` and `clientSecret` values can impersonate you and act fully on your behalf.
{% endhint %}

**Example**

The following single-user application calls [Session.login()](https://inrupt.github.io/solid-client-authn-js/node/classes/Session.html#login) with:

* `clientId`, `clientSecret`, and
* `oidcIssuer`.

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

// 1. Get the authenticated credentials: myClientId, myClientSecret
// and myIdentityProvider, the Solid Identity Provider associated with the credentials.
// ...
// ...
// Important: Safeguard these credentials.

const session = new Session();
session.login({
  // 2. Use the authenticated credentials to log in the session.
  clientId: myClientId,
  clientSecret: myClientSecret,
  oidcIssuer: myIdentityProvider
}).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);
  }
});
```

**Static Registration of a Client Application**

If your Solid Identity Provider provides a mechanism to statically register applications, your applications can use the associated client credentials to login.

**Availability**

Inrupt’s PodSpaces provides an [Application Registration page](https://login.inrupt.com/registration.html) where you can statically register your applications to generate credentials for them. It is available for users with accounts on `https://login.inrupt.com`; i.e., users who have registered a Pod with PodSpaces. To use, visit the [Application Registration](https://login.inrupt.com/registration.html) to register your client. More information about this service can be found in the [Application Registration documentation](https://github.com/inrupt/docs-gitbook/tree/main/guides/authentication-in-solid/broken-reference/README.md) for the ESS Solid OIDC Broker Service.

For availability of static client registration for your Solid Identity Provider, see your Solid Identity Provider’s documentation.


# Integrating an AI Agent with ESS using MCP

{% hint style="success" %}
Added in version 3.0.0
{% endhint %}

This guide walks through building a client application that integrates with ESS's Model Context Protocol (MCP) Service. The MCP Service enables AI agents and applications to securely access, manage, and interact with personal data stored in ESS.

## Overview

The ESS MCP integration uses the [MCP Resource Service](https://docs.inrupt.com/ess/latest/services/service-mcp/mcp-resource), which provides MCP tools for managing and accessing resources. Clients authenticate using ESS Access Tokens obtained through the [Platform Management service](https://docs.inrupt.com/ess/latest/services/service-platform-management/token-exchange).

A typical MCP client workflow involves:

1. Authenticating with your Identity Provider and exchanging for an ESS Access Token
2. Using MCP tools to request access to resources
3. Retrieving resource content using Access Grants

## Quickstart

Before setting up a proper MCP client, it is possible to try the ESS MCP service using [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector).

### Step 1: Get an ESS Access Token

1. Authenticate with your external Identity Provider to obtain an ID token.
2. Exchange the ID token for an ESS Access Token via the [Platform Management service](https://docs.inrupt.com/ess/latest/services/service-platform-management/token-exchange):

```http
POST /access/token HTTP/1.1
Host: platform.YOUR-ESS-DOMAIN
Content-Type: application/x-www-form-urlencoded
 
grant_type=urn:ietf:params:oauth:grant-type:token-exchange&subject_token=<idp-id-token>&subject_token_type=urn:ietf:params:oauth:token-type:id_token
```

Using cURL, the command would look like

```bash
curl -X POST https://platform.YOUR-ESS-DOMAIN/access/token -H "Content-Type: application/x-www-form-urlencoded" -d "grant_type=urn:ietf:params:oauth:grant-type:token-exchange&subject_token=<idp-id-token>&subject_token_type=urn:ietf:params:oauth:token-type:id_token"
```

A successful response will include an ESS Access Token.

### Step 2: Set up the MCP Inspector

1. Save the following configuration in an `mcp.json` file:

```json
{
  "mcpServers": {
    "ess": {
        "type": "streamable-http",
        "url": "https://mcp.YOUR-ESS-DOMAIN/api"
    }
  }
}
```

2. Start the MCP inspector using this configuration:

```bash
npx @modelcontextprotocol/inspector --config mcp.json --server ess
```

3. In the MCP inspector browser window which should have opened, in the "Authentication" section, add a custom `Authorization` header with the value `Bearer your-access-token`, using the ESS Access Token obtained at the previous step.
4. Click connect. You should now be successfully connected.

### Step 3: Interact with the MCP tools

Now that you have successfully connected, you can interact with the different tools, such as `requestAccess` or `checkAccessRequestStatus`. The access control restrictions listed in the [MCP Resource Service](https://docs.inrupt.com/ess/latest/services/service-mcp/mcp-resource) documentation apply.

## Setup a proper MCP client

The MCP inspector is a nice debug/starting point, but the point of the MCP Service is to support an AI Agent using an MCP client to enrich its context when interacting with a user. The following guide describes how to setup such MCP client with the AI Agent of your choice.

### Prerequisites

Before you begin, ensure you have:

* An ESS deployment with MCP services enabled (version 3.0.0 or later)
* An external Identity Provider configured as a trusted issuer in the Platform Management service
* Client credentials for authenticating with your Identity Provider

### Step 1: Configure Identity Provider

Ensure your external Identity Provider is configured as a trusted issuer in ESS's Platform Management service. Contact your ESS administrator to add your IdP's issuer URL to the trusted issuers list.

### Step 2: Obtain an ESS Access Token

Authenticate with your Identity Provider and exchange the resulting ID token for an ESS Access Token:

```js
import { Client } from '@modelcontextprotocol/sdk/client';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';

// Step 1: Authenticate with your IdP (implementation depends on your IdP)
const idToken = await authenticateWithIdP();

// Step 2: Exchange for ESS Access Token
const tokenResponse = await fetch(`${process.env.TOKEN_EXCHANGE_URL}/token`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',
    subject_token: idToken,
    subject_token_type: 'urn:ietf:params:oauth:token-type:id_token',
  }),
});

const { access_token } = await tokenResponse.json();

// Step 3: Connect to MCP Resource Service
const transport = new StreamableHTTPClientTransport(
  process.env.MCP_SERVER, {
    requestInit: {
      headers: {
        'Authorization': `Bearer ${access_token}`,
      },
    },
  }
);

const client = new Client(
  {
    name: 'my-ai-agent',
    version: '1.0.0',
  },
  {
    capabilities: {},
  }
);

await client.connect(transport);
```

### Step 3: Client Uses MCP Tools to Get Access to the User Resources

#### 3.1 Client Checks for an Access Grant

The client may use the `hasMatchingAccessGrant` tool to confirm a valid Access Grant exists. If such Access Grant exists, the Agent may directly go read the resource (See [Step 4](#4-client-retrieves-user-resource-content)).

**Note:** The agent can only verify Access Grants where the grantor is the current authenticated user.

#### 3.2 Client Requests Access to Resource

The client may use the `requestAccess` tool to create an Access Request. The Access Request is automatically attributed to the authenticated user. The client receives an Access Request URL with status `pending`.

#### 3.3 Client Monitors Request Status

The client may use the `checkAccessRequestStatus` tool with the Access Request URL to monitor its status. Continue checking until the status changes from `pending` to one of:

* `granted` - Request approved by Resource Owner
* `denied` - Request rejected
* `cancelled` - Request cancelled

**Note:** The agent can only check the status of Access Requests issued to the current authenticated user.

#### 3.4 User Approves Client Request

The end user who received the Access Request will now need to approve it using a separate interface to manage access. Reviewing and approving Access Requests cannot be done via the MCP service.

#### 4. Client Retrieves User Resource Content

The client may use the `getResource` tool to retrieve the actual resource data using the Access Grant.

**Note:** The client can only retrieve resources using Access Grants issued by the current authenticated user.

## Security Considerations

### Token Management

* ESS Access Tokens have a default TTL of 5 minutes
* Use the `expires_in` value from the token exchange response to proactively re-authenticate before the token expires
* Handle `401` responses by performing a new token exchange and retrying the request
* Store tokens securely and never expose them in client-side code or logs

### Access Grant Validation

The Resource Service performs comprehensive validation for `getResource` operations:

* The Access Grant must exist and not be revoked
* The Access Grant must not have expired
* The Access Grant must authorize access to the requested resource
* The Access Grant must have been issued to the agent
* The Access Grant must include the required access modes

## Error Handling

Handle common error scenarios gracefully.

### Authentication Errors

* **Invalid Token**: ID token from the external IdP is invalid or expired
* **Untrusted IdP**: The IdP issuer is not in the trusted issuers list

### Access Request Errors

* **Denied**: Resource owner rejected the access request
* **Cancelled**: Access Request was cancelled before approval
* **Expired**: Access Grant has expired

### Resource Access Errors

* **Authorization Error**: Token expired or invalid
* **Grant Not Found**: Access Grant ID is invalid or has been revoked
* **Resource Not Found**: Resource URI is invalid or inaccessible

## Best Practices

1. **Check Existing Grants First**: Before creating new Access Requests, check if valid grants already exist to avoid duplicate requests
2. **Provide Clear Purpose Statements**: When requesting access, include descriptive purpose text to help Resource Owners make informed decisions
3. **Handle Asynchronous Approval**: Access Requests require human approval, so implement polling with appropriate backoff strategies
4. **Handle Token Expiry**: Use `expires_in` from the token exchange response to re-authenticate before expiry, and retry on `401` responses
5. **Use Canonical URIs**: When referencing resources in Access Requests, use canonical URIs (`/sc/` form) for durable references
6. **Validate All Inputs**: Validate resource URIs, access modes, and other parameters before making tool calls
7. **Log All Operations**: Maintain audit logs of all Access Requests and resource retrievals for compliance and debugging
8. **Respect User Context**: Always ensure your agent operates within the boundaries of the authenticated user's access rights

## Additional Resources

* [MCP Resource Service Documentation](https://docs.inrupt.com/ess/latest/services/service-mcp/mcp-resource)
* [MCP Service Overview](https://docs.inrupt.com/ess/latest/services/service-mcp/)
* [Platform Management service](https://docs.inrupt.com/ess/latest/services/service-platform-management/token-exchange)
* [Model Context Protocol Specification](https://modelcontextprotocol.io/)
* [OAuth 2.0 Token Exchange (RFC 8693)](https://datatracker.ietf.org/doc/html/rfc8693)


# Javascript SDK

The JavaScript Client Libraries provide a suite of JavaScript Application Programming Interfaces (APIs) to build [Solid](https://solidproject.org/TR/protocol) applications.

### Inrupt’s JavaScript Client Libraries

Inrupt’s JavaScript Client Libraries provide APIs, such as for read/write/authenticate/access management operations, that conform to the [Solid specifications](https://solidproject.org/TR/protocol).

<table><thead><tr><th width="287.5">Library</th><th>Description</th></tr></thead><tbody><tr><td><a href="https://inrupt.github.io/solid-client-js/"><strong><code>solid-client</code></strong></a></td><td><p>A client library for accessing data stored in Solid Pods. It is designed to make it easy for developers to create applications on top of Inrupt's <a href="/pages/7PKZaxnnUDGdJ8gPDOqL#enterprise-solid-server-ess">Enterprise Solid Server</a>.</p><p><strong><code>solid-client</code></strong> provides an abstraction layer on top of both <a href="/pages/7PKZaxnnUDGdJ8gPDOqL#non-rdf-resource">Solid</a> and <a href="/pages/Ejga65cCzJE3HowMncgm">Resource Description Framework (RDF)</a> principles and is compatible with the <a href="https://rdf.js.org/">RDF/JS specification</a>.</p><p>You can use <strong><code>solid-client</code></strong> in <a href="https://nodejs.org">Node.js</a> using <a href="http://www.commonjs.org">CommonJS</a> modules and in the browser with a bundler like <a href="https://webpack.js.org">Webpack</a>, <a href="https://rollupjs.org">Rollup</a>, or <a href="https://parceljs.org">Parcel</a>.</p></td></tr><tr><td><strong><code>solid-client-authn</code></strong></td><td><p>A set of libraries for authenticating to Solid identity servers:</p><ul><li><a href="https://inrupt.github.io/solid-client-authn-js/browser/"><strong><code>solid-client-authn-browser</code></strong></a> for use in a browser.</li><li><a href="https://inrupt.github.io/solid-client-authn-js/node/"><strong><code>solid-client-authn-node</code></strong></a> for use in Node.js.</li></ul></td></tr><tr><td><a href="https://inrupt.github.io/solid-client-access-grants-js/"><strong><code>solid-client-access-grants</code></strong></a></td><td>A client library for managing Access Requests and Grants.</td></tr><tr><td><a href="/pages/OhEYjpSYWWP9O9iheZ6d"><strong><code>solid-client-notifications</code></strong></a></td><td>A client library for subscribing to change notifications.</td></tr><tr><td><strong><code>vocab-solid</code></strong></td><td>A library that provides convenience objects for many Solid-related identifiers. Previously released as <strong><code>vocab-solid-common</code></strong>.</td></tr><tr><td><strong><code>vocab-inrupt-core</code></strong></td><td>A library that provides convenience objects for Inrupt-related identifiers. Previously released as <strong><code>vocab-inrupt-common</code></strong>.</td></tr></tbody></table>

### Node.js Support

Inrupt’s Javascript Client libraries support [Active/Maintenance LTS releases for Node.js](https://nodejs.org/en/about/releases/).

### Browser Support

The JavaScript Client Libraries support the latest 2 stable releases of the following browsers:

<table data-header-hidden><thead><tr><th width="143.5"></th><th></th></tr></thead><tbody><tr><td>Desktop</td><td><p>Google Chrome</p><p>Mozilla Firefox</p><p>Microsoft Edge</p><p>Apple Safari</p></td></tr><tr><td>Mobile</td><td><p>iOS/Safari</p><p>Android/Chrome</p><p>Android/Samsung Internet</p></td></tr></tbody></table>

You can use the libraries in the browser with a bundler like [Webpack](https://webpack.js.org/), [Rollup](https://rollupjs.org/), or [Parcel](https://parceljs.org/).

### Bugs and Feature Requests

For public feedback, bug reports, and feature requests, please file an issue via GitHub.

* [**`solid-client`**](https://github.com/inrupt/solid-client-js/issues/)
* [**`solid-client-authn`**](https://github.com/inrupt/solid-client-authn-js/issues/)
* [**`solid-client-notifications`**](https://github.com/inrupt/solid-client-notifications-js/issues/)
* [**`solid-client-access-grants`**](https://github.com/inrupt/solid-client-access-grants-js/issues/)
* [**`solid-client-errors`**](https://github.com/inrupt/solid-client-errors-js/issues/)


# Installation

You can use [npm](https://www.npmjs.com/) to install the libraries.

For example, to install the **`solid-client`**, **`solid-client-authn-browser`**, and **`vocab-solid`** libraries:

```javascript
npm install @inrupt/solid-client @inrupt/solid-client-authn-browser @inrupt/vocab-solid
```

See also:

* [@inrupt/solid-client](https://www.npmjs.com/package/@inrupt/solid-client)
* [@inrupt/solid-client-authn-browser](https://www.npmjs.com/package/@inrupt/solid-client-authn-browser)
* [@inrupt/solid-client-authn-node](https://www.npmjs.com/package/@inrupt/solid-client-authn-node)
* [@inrupt/solid-client-notifications](https://www.npmjs.com/package/@inrupt/solid-client-notifications)
* [@inrupt/solid-client-access-grants](https://www.npmjs.com/package/@inrupt/solid-client-access-grants)
* [@inrupt/vocab-solid](https://www.npmjs.com/package/@inrupt/vocab-solid)
* [@inrupt/vocab-inrupt-core](https://www.npmjs.com/package/@inrupt/vocab-inrupt-core)


# Tutorial

This tutorial creates an introductory application that uses Inrupt’s JavaScript client libraries to:

* Login a user.
* Reads the Pod URL(s) associated with the user’s [WebID](/reference/glossary#webid). A WebID is a URL that identifies a user and dereferences to the user’s WebID profile document.
* Write a reading list to the user’s Pod.

The tutorial uses [npm](https://www.npmjs.com/get-npm) and [webpack](https://webpack.js.org) to run the application locally on **`http://localhost:8080/`** .

<figure><img src="/files/F4ynHdVtgA7sCwUOWFye" alt=""><figcaption></figcaption></figure>

## Prerequisites

### Install npm

If you do not already have npm installed, [install npm](https://www.npmjs.com/get-npm). npm is installed as part of the Node.js installation.

Inrupt’s Javascript Client libraries support [Active/Maintenance LTS releases for Node.js](https://github.com/nodejs/release).

{% hint style="info" %}
**Note**

* This tutorial uses Inrupt’s [PodSpaces](https://github.com/inrupt/docs-gitbook/tree/main/.gitbook/includes/broken-reference/README.md) and provides instructions on creating an account, WebID and a Pod through PodSpaces.
* PodSpaces is currently available as Developer Preview. Do not use for production or storing sensitive/personal data.
  {% endhint %}

To get a WebID and a Pod on [PodSpaces](https://github.com/inrupt/docs-gitbook/tree/main/.gitbook/includes/broken-reference/README.md) :

1. Go to [PodSpaces](https://start.inrupt.com/).
2. To create an account, you must agree to the Inrupt’s Terms of Service. To agree, select the checkbox.
3. If you agree to Inrupt’s Terms of Service, click on the <mark style="background-color:blue;">**Sign Up**</mark> button.
4. If you have not registered an account with the Inrupt Identity Provider, click on the <mark style="background-color:blue;">**Sign up**</mark> link to create an account:
   1. Fill in your username, email, and password.
   2. Click <mark style="background-color:blue;">**Sign Up**</mark>. You are sent a verification email.
   3. Check your email for the verification email. Follow the instructions in the email to verify. Check your spam if you do not see the email in your inbox.
   4. Once verified, return to click <mark style="background-color:blue;">**Continue**</mark> to go to the Sign in page:
      1. Enter your username and password.
      2. Click <mark style="background-color:blue;">**Sign in**</mark> to your account. The screen displays the access required to continue.
   5. To allow and continue, click <mark style="background-color:blue;">**Allow**</mark>.\
      The application displays your WebID and Pod Storage details:
      1. WebID: **`https://id.inrupt.com/{username}`** .\
         Pod Storage: **`https://storage.inrupt.com/{Root Container}`**

## Build the Application

### 1. Initialize the Application

1. Create the directory structure for your Webpack project:

   ```sh
   mkdir -p  my-demo-app my-demo-app/src my-demo-app/dist
   ```
2. Go to the newly created **`my-demo-app`** directory.

   ```sh
   cd my-demo-app
   ```
3. Initialize the application.

* To accept the default values for the application without prompts:

  ```sh
  npm init -y
  ```
* Or, to be prompted to enter values for the application:

  ```sh
  npm init
  ```

  1. You can either hit return to accept the default values (including empty values) or supply your own values.
  2. When prompted **`Is this OK? (yes)`** , enter to accept **`yes`** .

### 2. Install the Client Libraries

1. Use npm to install the **`solid-client`** , **`@inrupt/solid-client-authn-browser`** , **`vocab-common-rdf`** , and **`vocab-solid`** libraries:

   ```sh
   npm install @inrupt/solid-client @inrupt/solid-client-authn-browser @inrupt/vocab-common-rdf @inrupt/vocab-solid
   ```

### 3. Install Webpack

1. Use npm to install [Webpack](https://webpack.js.org) packages:

   ```sh
   npm install webpack webpack-cli webpack-dev-server css-loader style-loader --save-dev
   ```
2. In **`my-demo-app`** directory, create a **`webpack.config.js`** file with the following content:

   ```javascript
   const path = require("path");
   module.exports = {
     mode: "development",
     entry: "./src/index.js",
     output: {
       path: path.resolve(__dirname, "dist"),
       filename: "index.js",
     },
     module: {
       rules: [
         {
           test: /\.css$/,
           use: [{ loader: "style-loader" }, { loader: "css-loader" }],
         },
       ],
     },
     devServer: {
       static: "./dist",
     },
   };
   ```
3. In **`my-demo-app`** directory, edit the **`package.json`** file to add **`build`** and **`start`** script fields to **`scripts`** :

<pre class="language-json"><code class="lang-json">"scripts": {
  "test": "echo \"Error: no test specified\" &#x26;&#x26; exit 1",
<strong>  "build": "webpack",
</strong><strong>  "start": "webpack serve --open"
</strong>},
</code></pre>

{% hint style="info" %}
Be sure to add the comma after the preceding field value before adding the `build` and `start` fields.
{% endhint %}

### 4. Create the Application

In the **`my-demo-app`** directory, create the files for the application. For an explanation of the JavaScript code used in this tutorial, see [Explanation](/sdk/javascript-sdk/tutorial/explanation)

**A. Create the CSS File**

In the **`my-demo-app/dist`** folder, create a **`my-demo.css`** file with the following content:

```css
h2,h3 {
    margin: 1rem 1.2rem 1rem 1.4rem;
}
body * {
   margin-left: .5rem;
   margin-right: 1rem;
}
header {
   border-bottom: #5795b9 solid;
   padding-left: .5rem;
}
.panel {
   border: 1px solid #005b81;
   border-radius: 4px;
   box-shadow: rgb(184, 196, 194) 0px 4px 10px -4px;
   box-sizing: border-box;
   padding: 1rem 1.5rem;
   margin: 1rem 1.2rem 1rem 1.2rem;
}
#login {
   background: white;
}
#read, #results {
   background: #e6f4f9;
}
#labelStatus[role="alert"] {
   padding-left: 1rem;
   color: purple;
}
.display {
    margin-left: 1rem;
    color: gray;
}
.disabled {
   color: gray;
   background-color: gray;
}
dl {
  display: grid;
  grid-template-columns:  max-content auto;
}
dt {
  grid-column-start: 1;
}
dd {
  grid-column-start: 2;
}
```

**B. Create the HTML File**

In the **`my-demo-app/dist`** , create an **`index.html`** file with the following content:

{% hint style="info" %}
If you are not using your PodSpaces account, you can modify the **`select-idp`** options.
{% endhint %}

<pre class="language-html"><code class="lang-html">&#x3C;!DOCTYPE html>
&#x3C;html>
&#x3C;head>
  &#x3C;meta charset="utf-8">
  &#x3C;title>Getting Started: Inrupt JavaScript Client Libraries&#x3C;/title>
  &#x3C;script defer src="./index.js">&#x3C;/script>
  &#x3C;link rel="stylesheet" href="my-demo.css" />
&#x3C;/head>
&#x3C;body>
  &#x3C;header>
    &#x3C;h2>Getting Started&#x3C;/h2>
    &#x3C;h3>with Inrupt JavaScript Client Libraries&#x3C;/h3>
  &#x3C;/header>
  &#x3C;section id="login" class="panel">
    &#x3C;div class="row">
      &#x3C;label id="labelIdP" for="select-idp">1. Select your Identity Provider: &#x3C;/label>
      &#x3C;select id="select-idp" name="select-idp">
        &#x3C;option value="">--Please select an Identity Provider (IdP)--&#x3C;/option>
        &#x3C;!-- Update the select-idp option if not using PodSpaces -->
<strong>        &#x3C;option value="https://login.inrupt.com">https://login.inrupt.com (PodSpaces)&#x3C;/option>
</strong>
      &#x3C;/select>
      &#x3C;button name="btnLogin" id="btnLogin">Login&#x3C;/button>
    &#x3C;/div>
  &#x3C;/section>
  &#x3C;div id="read" class="panel">
    &#x3C;div class="row">
      &#x3C;label id="readlabel" for="myWebID">2. Logged in with your WebID: &#x3C;/label>
      &#x3C;input type="text" id="myWebID" name="myWebID" size="50" disabled>
      &#x3C;button name="btnRead" id="btnRead">Get Pod URL(s)&#x3C;/button>
    &#x3C;/div>
  &#x3C;/div>
  &#x3C;div id="write" class="panel">
    &#x3C;div class="row">
      &#x3C;label id="writelabel">3.Create a private reading list in my Pod.&#x3C;/label>
    &#x3C;/div>
    &#x3C;br>
    &#x3C;div class="row">
      &#x3C;div>
        &#x3C;label id="podlabel" for="select-pod">a. Write to your Pod: &#x3C;/label>
        &#x3C;select id="select-pod" name="select-pod" widths: 120>
          &#x3C;option value="">--Please select your Pod--&#x3C;/option>
        &#x3C;/select>getting-started/readingList/myList
      &#x3C;/div>
    &#x3C;/div>
    &#x3C;br>
    &#x3C;div class="row">
      &#x3C;div>
        &#x3C;label id="listLabel" for="titles">b. Enter items to read: &#x3C;/label>
        &#x3C;textarea id="titles" name="titles" rows="5" cols="42">
Leaves of Grass
RDF 1.1 Primer&#x3C;/textarea>
        &#x3C;button name="btnCreate" id="btnCreate">Create&#x3C;/button>
      &#x3C;/div>
      &#x3C;br>
    &#x3C;/div>
  &#x3C;/div>
  &#x3C;div id="results" class="panel">
    &#x3C;div class="row">
      &#x3C;label>Create Reading List Status&#x3C;/label>
      &#x3C;span id="labelCreateStatus">&#x3C;/span>
    &#x3C;/div>
    &#x3C;div class="row">
      &#x3C;div>
        &#x3C;label id="labelRetrieved" for="savedtitles">Retrieved to validate:&#x3C;/label>
        &#x3C;textarea id="savedtitles" name="savedtitles" rows="5" cols="42" disabled>&#x3C;/textarea>
      &#x3C;/div>
    &#x3C;/div>
  &#x3C;/div>
&#x3C;/body>
&#x3C;/html>
</code></pre>

**C. Create the JS File**

In the **`my-demo-app/src`** , create an **`index.js`** file with the following content:

```javascript
// Import from "@inrupt/solid-client-authn-browser"
import {
  login,
  handleIncomingRedirect,
  getDefaultSession,
  fetch
} from "@inrupt/solid-client-authn-browser";
// Import from "@inrupt/solid-client"
import {
  addUrl,
  addStringNoLocale,
  createSolidDataset,
  createThing,
  getPodUrlAll,
  getSolidDataset,
  getThingAll,
  getStringNoLocale,
  removeThing,
  saveSolidDatasetAt,
  setThing
} from "@inrupt/solid-client";
import { SCHEMA_INRUPT, RDF, AS } from "@inrupt/vocab-common-rdf";
const selectorIdP = document.querySelector("#select-idp");
const selectorPod = document.querySelector("#select-pod");
const buttonLogin = document.querySelector("#btnLogin");
const buttonRead = document.querySelector("#btnRead");
const buttonCreate = document.querySelector("#btnCreate");
const labelCreateStatus = document.querySelector("#labelCreateStatus");
buttonRead.setAttribute("disabled", "disabled");
buttonLogin.setAttribute("disabled", "disabled");
buttonCreate.setAttribute("disabled", "disabled");
// 1a. Start Login Process. Call login() function.
function loginToSelectedIdP() {
  const SELECTED_IDP = document.getElementById("select-idp").value;
  return login({
    oidcIssuer: SELECTED_IDP,
    redirectUrl: new URL("/", window.location.href).toString(),
    clientName: "Getting started app"
  });
}
// 1b. Login Redirect. Call handleIncomingRedirect() function.
// When redirected after login, finish the process by retrieving session information.
async function handleRedirectAfterLogin() {
  await handleIncomingRedirect(); // no-op if not part of login redirect
  const session = getDefaultSession();
  if (session.info.isLoggedIn) {
    // Update the page with the status.
    document.getElementById("myWebID").value = session.info.webId;
    // Enable Read button to read Pod URL
    buttonRead.removeAttribute("disabled");
  }
}
// The example has the login redirect back to the root page.
// The page calls this method, which, in turn, calls handleIncomingRedirect.
handleRedirectAfterLogin();
// 2. Get Pod(s) associated with the WebID
async function getMyPods() {
  const webID = document.getElementById("myWebID").value;
  const mypods = await getPodUrlAll(webID, { fetch: fetch });
  // Update the page with the retrieved values.
  mypods.forEach((mypod) => {
    let podOption = document.createElement("option");
    podOption.textContent = mypod;
    podOption.value = mypod;
    selectorPod.appendChild(podOption);
  });
}
// 3. Create the Reading List
async function createList() {
  labelCreateStatus.textContent = "";
  const SELECTED_POD = document.getElementById("select-pod").value;
  // For simplicity and brevity, this tutorial hardcodes the  SolidDataset URL.
  // In practice, you should add in your profile a link to this resource
  // such that applications can follow to find your list.
  const readingListUrl = `${SELECTED_POD}getting-started/readingList/myList`;
  let titles = document.getElementById("titles").value.split("\n");
  // Fetch or create a new reading list.
  let myReadingList;
  try {
    // Attempt to retrieve the reading list in case it already exists.
    myReadingList = await getSolidDataset(readingListUrl, { fetch: fetch });
    // Clear the list to override the whole list
    let items = getThingAll(myReadingList);
    items.forEach((item) => {
      myReadingList = removeThing(myReadingList, item);
    });
  } catch (error) {
    if (typeof error.statusCode === "number" && error.statusCode === 404) {
      // if not found, create a new SolidDataset (i.e., the reading list)
      myReadingList = createSolidDataset();
    } else {
      console.error(error.message);
    }
  }
  // Add titles to the Dataset
  let i = 0;
  titles.forEach((title) => {
    if (title.trim() !== "") {
      let item = createThing({ name: "title" + i });
      item = addUrl(item, RDF.type, AS.Article);
      item = addStringNoLocale(item, SCHEMA_INRUPT.name, title);
      myReadingList = setThing(myReadingList, item);
      i++;
    }
  });
  try {
    // Save the SolidDataset
    let savedReadingList = await saveSolidDatasetAt(
      readingListUrl,
      myReadingList,
      { fetch: fetch }
    );
    labelCreateStatus.textContent = "Saved";
    // Refetch the Reading List
    savedReadingList = await getSolidDataset(readingListUrl, { fetch: fetch });
    let items = getThingAll(savedReadingList);
    let listcontent = "";
    for (let i = 0; i < items.length; i++) {
      let item = getStringNoLocale(items[i], SCHEMA_INRUPT.name);
      if (item !== null) {
        listcontent += item + "\n";
      }
    }
    document.getElementById("savedtitles").value = listcontent;
  } catch (error) {
    console.log(error);
    labelCreateStatus.textContent = "Error" + error;
    labelCreateStatus.setAttribute("role", "alert");
  }
}
buttonLogin.onclick = function () {
  loginToSelectedIdP();
};
buttonRead.onclick = function () {
  getMyPods();
};
buttonCreate.onclick = function () {
  createList();
};
selectorIdP.addEventListener("change", idpSelectionHandler);
function idpSelectionHandler() {
  if (selectorIdP.value === "") {
    buttonLogin.setAttribute("disabled", "disabled");
  } else {
    buttonLogin.removeAttribute("disabled");
  }
}
selectorPod.addEventListener("change", podSelectionHandler);
function podSelectionHandler() {
  if (selectorPod.value === "") {
    buttonCreate.setAttribute("disabled", "disabled");
  } else {
    buttonCreate.removeAttribute("disabled");
  }
}
```

For details about the JavaScript code, see [Explanation](/sdk/javascript-sdk/tutorial/explanation).

### 5. Run the Application

1. In the **`my-demo-app`** directory, run:

   ```sh
   npm run build && npm run start
   ```

   The output resembles the following:

   ```none
   <i> [webpack-dev-server] Project is running at:
   <i> [webpack-dev-server] Loopback: http://localhost:8080/
   ...
   webpack 5.58.2 compiled successfully in 1808 ms
   ```
2. Open **`localhost:8080`** in a browser.
3. Login.
   1. Select the Identity Provider and click <mark style="background-color:blue;">Login</mark>.
   2. If you have logged out of your Pod, you are prompted to sign in. Enter your username and password and sign in.\
      You will be prompted to allow this application the specified access. To continue, click <mark style="background-color:blue;">Continue</mark>.
   3. You are redirected back to your page. Your WebID is now displayed in the application.
4. Click <mark style="background-color:blue;">Get Pod URL</mark> .\
   The application populates the Pods selection box (in panel 3) with the Pod URL from your profile document.
5. Write your reading list to your Pod.
   1. Select your Pod URL.
   2. Edit your reading list.
   3. Click Create to to save the list to your Pod.

The reading list is saved to your Pod at **`https://storage.inrupt.com/<root container>/getting-started/readingList/myList`** .

Upon successful save, the application displays the saved reading list in the next panel.

For details about the example code, see [Explanation](/sdk/javascript-sdk/tutorial/explanation)

### 6. Exit the Application

To exit the application, stop the **`npm run start`** process; e.g., **`Ctrl-C`** .

### Additional Information

* [solid-client API](https://inrupt.github.io/solid-client-js/)
* [solid-client-authn-browser API](https://inrupt.github.io/solid-client-authn-js/browser/)


# Explanation

The following provides a brief explanation of the Inrupt’s JavaScript client libraries usage in the [Tutorial](/sdk/javascript-sdk/tutorial) application code.

## Login Code

The example uses Inrupt’s `@inrupt/solid-client-authn-browser` library to log in. `@inrupt/solid-client-authn-browser` is for **client-side code only** .

For applications implementing [Authorization Code Flow](https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowSteps) :

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.

<figure><img src="/files/aczLsPZtA05yOmdNM8oj" alt=""><figcaption></figcaption></figure>

### Import

The application uses various objects from `@inrupt/solid-client-authn-browser` to log in.

```javascript
import {
  login,
  handleIncomingRedirect,
  getDefaultSession,
  fetch
} from "@inrupt/solid-client-authn-browser";
```

### Start Login

The application starts the login process by calling [login()](https://inrupt.github.io/solid-client-authn-js/browser/functions.html#login) with the following options:

<table><thead><tr><th width="155.33203125"></th><th></th></tr></thead><tbody><tr><td><strong><code>oidcIssuer</code></strong></td><td><p>The URL of the user’s Solid Identity Provider. The function<br>sends the user to this URL to log in.<br>In this example, it is set to the value selected by the user.</p><div data-gb-custom-block data-tag="hint" data-style="info" class="hint hint-info"><p>If you are not using <a href="/pages/w20IKttecwXUMZyR6fuU">PodSpaces</a>, modify the <code>select-idp</code> options in the example’s <code>index.html</code> .</p></div></td></tr><tr><td><strong><code>redirectUrl</code></strong></td><td><p>The URL where the user, after logging in, will be redirected in order to finish the login process.</p><div data-gb-custom-block data-tag="hint" data-style="info" class="hint hint-info"><p>The <strong><code>redirectUrl</code></strong> value should not change with application routes or hash or query parameters.</p></div><p>In this example, it is set to <code>new URL("/", window.location.href).toString()</code> (i.e., the root URL of this<br>application <code>https://localhost:8080/</code>).</p><p>Alternatively, the example could have explicitly specified the <code>index.html</code> as the <code>redirectURL</code>; i.e., <code>new URL("/index.html", window.location.href).toString()</code>.</p></td></tr><tr><td><strong><code>clientName</code></strong></td><td>A user-friendly application name to be passed to the Solid<br>Identity Provider. The value is displayed in the Identity<br>Provider’s Access Request approval window.</td></tr></tbody></table>

```javascript
function loginToSelectedIdP() {
  const SELECTED_IDP = document.getElementById("select-idp").value;
  return login({
    oidcIssuer: SELECTED_IDP,
    redirectUrl: new URL("/", window.location.href).toString(),
    clientName: "Getting started app"
  });
}
```

The [login()](https://inrupt.github.io/solid-client-authn-js/browser/functions.html#login) function sends the user to the Solid Identity Provider specified in `oidcIssuer` . Once the user logs in with the Identity Provider, the user is redirected back to the specified `redirectUrl` to finish the login process .

### Finish Login

Once logged in at the Solid Identity Provider, the user is redirected back to the `redirectUrl` specified at the start of the login process (i.e., in the `login()` function call). The page at this `redirectUrl` calls [handleIncomingRedirect()](https://inrupt.github.io/solid-client-authn-js/browser/functions.html#getdefaultsession) to complete the login process.

In the example, the page calls `handleRedirectAfterLogin()` method, which, in turn, calls the [handleIncomingRedirect()](https://inrupt.github.io/solid-client-authn-js/browser/functions.html#getdefaultsession) :

<pre class="language-javascript"><code class="lang-javascript">
// When redirected after login, finish the process by retrieving session information.
async function handleRedirectAfterLogin() {
<strong>  await handleIncomingRedirect(); // no-op if not part of login redirect
</strong>
  const session = getDefaultSession();
  if (session.info.isLoggedIn) {
    // Update the page with the status.
    document.getElementById("myWebID").value = session.info.webId;
    // Enable Read button to read Pod URL
    buttonRead.removeAttribute("disabled");
  }
}
// The example has the login redirect back to the root page.
// The page calls this method, which, in turn, calls handleIncomingRedirect.
handleRedirectAfterLogin();
</code></pre>

The [handleIncomingRedirect()](https://inrupt.github.io/solid-client-authn-js/browser/functions.html#getdefaultsession) function collects the information provided by the Identity Provider. [handleIncomingRedirect()](https://inrupt.github.io/solid-client-authn-js/browser/functions.html#getdefaultsession) is a no-op if called outside the login processs.

For more information on using the library to authenticate, see [Authentication](/sdk/javascript-sdk/authentication).

## Get Pods Code

The example uses Inrupt’s `solid-client` library to return the Pods associated with a WebID.

### Import

The application uses various objects from `@inrupt/solid-client` . Additional import objects are displayed for the other read and write operations used in the application.

```javascript
import {
  addUrl,
  addStringNoLocale,
  createSolidDataset,
  createThing,
  getPodUrlAll,
  getSolidDataset,
  getThingAll,
  getStringNoLocale,
  removeThing,
  saveSolidDatasetAt,
  setThing
} from "@inrupt/solid-client";
import { SCHEMA_INRUPT, RDF, AS } from "@inrupt/vocab-common-rdf";
```

### Get Pods

The application uses [getPodUrlAll](https://inrupt.github.io/solid-client-js/modules/profile_webid.html#getpodurlall) to retrieve the Pod URLs (i.e., the value stored under `http://www.w3.org/ns/pim/space#storage` ) in the user’s profile.

```javascript
const webID = document.getElementById("myWebID").value;
const mypods = await getPodUrlAll(webID, { fetch: fetch });
```

* For more information on properties of `Things` , and see [Structured Data](/reference/rdf/structured-data-rdf-resources).
* For more information on read operations, see [CRUD (RDF Data)](/sdk/javascript-sdk/read-and-write-rdf-data).

## Write Reading List

### Import

The application uses various objects from `solid-client` and `vocab-common-rdf` libraries to write data to your Pod. Additional import objects are shown for read profile operations.

```javascript
import {
  addUrl,
  addStringNoLocale,
  createSolidDataset,
  createThing,
  getPodUrlAll,
  getSolidDataset,
  getThingAll,
  getStringNoLocale,
  removeThing,
  saveSolidDatasetAt,
  setThing
} from "@inrupt/solid-client";
import { SCHEMA_INRUPT, RDF, AS } from "@inrupt/vocab-common-rdf";
```

### Create Reading List SolidDataset

The application uses [getSolidDataset](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#getsoliddataset) to retrieve an existing reading list from the URL.

* If found, the application uses [removeThing](https://inrupt.github.io/solid-client-js/modules/thing_thing.html#removething) to clear the reading list by removing all titles from the list.
* If not found (i.e., errors with [404](/sdk/javascript-sdk/error-codes#id-404-not-found)), the application uses [createSolidDataset](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#createsoliddataset) to create a new SolidDataset (i.e., the reading list).

```javascript
  let myReadingList;
  try {
    // Attempt to retrieve the reading list in case it already exists.
    myReadingList = await getSolidDataset(readingListUrl, { fetch: fetch });
    // Clear the list to override the whole list
    let items = getThingAll(myReadingList);
    items.forEach((item) => {
      myReadingList = removeThing(myReadingList, item);
    });
  } catch (error) {
    if (typeof error.statusCode === "number" && error.statusCode === 404) {
      // if not found, create a new SolidDataset (i.e., the reading list)
      myReadingList = createSolidDataset();
    } else {
      console.error(error.message);
    }
  }
```

{% hint style="info" %}
As an alternative to fetching an existing reading list and removing all titles from the list, you can instead attempt to [deleteSolidDataset](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#deletesoliddataset) first, and then use [createSolidDataset](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#createsoliddataset) .
{% endhint %}

### Add Items (Things) to Reading List

For each title entered by the user:

* The application uses [createThing](https://inrupt.github.io/solid-client-js/modules/thing_thing.html#creatething) to create a new reading item Thing.
  * The application specifies the Thing’s name (optional) during its instantiation. Typically, a Thing’s URL is its SolidDataset URL (which ends with a `/`) appended with `#` and the Thing’s name; in this case:
    * `${podURL}getting-started/readingList/myList#title1`,
    * `${podURL}getting-started/readingList/myList#title2`, etc.
* To the item, the application uses the following functions to add specific data:
  * [addUrl](https://inrupt.github.io/solid-client-js/modules/thing_add.html#addurl) to add the `http://www.w3.org/1999/02/22-rdf-syntax-ns#type` property with the URL value `https://www.w3.org/ns/activitystreams#Article`\
    The example uses the `RDF.type` and `AS.Article` convenience objects to specify the aforementioned property and value.
  * [addStringNoLocale](https://inrupt.github.io/solid-client-js/modules/thing_add.html#addstringnolocale) to add the `https://schema.org/name` property with the string value set to one of the entered titles.\
    The example uses the `SCHEMA_INRUPT.name` convenience object for the aforementioned property.
* Then, the application uses [setThing](https://inrupt.github.io/solid-client-js/modules/thing_thing.html#setthing) to add the item to the SolidDataset (i.e., the reading list).

```javascript
  let i = 0;
  titles.forEach((title) => {
    if (title.trim() !== "") {
      let item = createThing({ name: "title" + i });
      item = addUrl(item, RDF.type, AS.Article);
      item = addStringNoLocale(item, SCHEMA_INRUPT.name, title);
      myReadingList = setThing(myReadingList, item);
      i++;
    }
  });
```

The `solid-client` library’s functions (such as the various add/set functions) do not modify the objects that are passed in as arguments. Instead, the library’s functions return a new object with the requested changes.

### Save Reading List (SolidDataset)

{% hint style="info" %}
For the sake of simplicity and brevity, this getting started guide hardcodes the SolidDataset URL. In practice, you should add a link to this resource in your profile that applications can follow.
{% endhint %}

Use [saveSolidDatasetAt](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#savesoliddatasetat) to save the reading list to `<PodURL>getting-started/readingList/myList` . [saveSolidDatasetAt](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#savesoliddatasetat) creates any intermediate folders/containers as needed.

```javascript
let savedReadingList = await saveSolidDatasetAt(
  readingListUrl,
  myReadingList,
  { fetch: fetch }
);
```

{% hint style="info" %}
The `solid-client` library also provides the [saveSolidDatasetInContainer](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#savesoliddatasetincontainer) . However, unlike [saveSolidDatasetAt](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#savesoliddatasetat) which creates any intermediate folders/containers as needed, [saveSolidDatasetInContainer](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#savesoliddatasetincontainer) requires that the specified destination container already exists.
{% endhint %}

Upon successful save, [saveSolidDatasetAt](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#savesoliddatasetat) returns a SolidDataset whose state reflects the data that was sent to be saved.

See also [Create vs. Update Operations](/sdk/javascript-sdk/read-and-write-rdf-data#save-considerations).

### Verify the Save Operation

The save operation returns the SolidDataset (the reading list) whose state reflect the data that was sent to be saved. The `savedReadingList` may not accurately reflect the saved state of the data if concurrent operations have modified additional fields.

To ensure you have the latest data, the tutorial uses [getSolidDataset](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#getsoliddataset) again after saving the data.

```javascript
savedReadingList = await getSolidDataset(readingListUrl, { fetch: fetch });
let items = getThingAll(savedReadingList);
let listcontent = "";
for (let i = 0; i < items.length; i++) {
  let item = getStringNoLocale(items[i], SCHEMA_INRUPT.name);
  if (item !== null) {
    listcontent += item + "\n";
  }
}
document.getElementById("savedtitles").value = listcontent;
```

The application uses `SCHEMA_INRUPT.name` convenience object from the `vocab-common-rdf` library to specify the property to retrieve.


# Authentication

Authentication is the process of verifying the identity of an [agent](/reference/glossary#agent). To access private data, you must authenticate as an agent who has been granted appropriate access to that data.

### Authentication Flows

Solid authentication is based on the [Solid-OIDC](https://solid.github.io/solid-oidc/) specification. [Solid-OIDC](https://solid.github.io/solid-oidc/) builds upon the [OpenID Connect](https://openid.net/connect/) standards, which itself builds on the [OAuth 2.0](https://datatracker.ietf.org/doc/html/rfc6749) authorization framework.

For applications implementing [Authorization Code Flow](https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowSteps):

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.

<figure><img src="/files/aczLsPZtA05yOmdNM8oj" alt=""><figcaption></figcaption></figure>

For applications implementing [Client Credentials](https://www.rfc-editor.org/rfc/rfc6749#section-4.4) flow:

1. The application (such as a single-user script) logs in, on behalf of the user who registered the client, by sending its client credentials to its Solid Identity Provider (i.e., where the user registered the client).
2. The Solid Identity Provider returns the tokens to the app.

### Inrupt Client Libraries

Inrupt provides the following libraries for authentication:

* **`solid-client-authn-browser`** to authenticate in a browser.
* **`solid-client-authn-node`** to authenticate in Node.js.

<details>

<summary>Note about Client IDs</summary>

In [Solid-OIDC](https://solid.github.io/solid-oidc/) (i.e., in [OAuth 2.0](https://datatracker.ietf.org/doc/html/rfc6749) and [OpenID Connect](https://openid.net/connect/)), an application identifies itself using a [client identifier (Client ID)](https://solid.github.io/solid-oidc/#clientids).

A Client ID can be:

* a URL that dereferences to a [Client ID Document](https://solid.github.io/solid-oidc/#clientids-document).
* a value that has been registered using either [OIDC dynamic or static registration](https://solid.github.io/solid-oidc/#clientids-oidc).

Inrupt’s client libraries provide `login` APIs that supports:

* Specifying a Client ID (of type URL) that dereferences to a [Client ID Document](https://solid.github.io/solid-oidc/#clientids-document).
* [Dynamic registration of the client](https://www.rfc-editor.org/rfc/rfc7591#section-3.1)
* Logging in with client credentials (Client ID and Secret) from static registration.

</details>

[Authentication](/guides/authentication-in-solid) in Solid can be performed:

* ​[via OIDC directly in the browser](/guides/authentication-in-solid/authentication-from-browser)​
* ​[via OIDC via a backend](/guides/authentication-in-solid/authentication-server-side)
* ​[Via OAuth Client Credentials ](/guides/authentication-in-solid/authentication-single-user-application)​

See these direct guides for more information.


# CRUD (Files)

Pods can store regular files (e.g., PDFs, photos, etc.) in addition to storing structured data as [Things](/reference/glossary#thing) and [SolidDatasets](/reference/glossary#soliddataset) (see [CRUD (RDF Data)](/sdk/javascript-sdk/read-and-write-rdf-data) for storing structured data).

The [**`solid-client`**](https://inrupt.github.io/solid-client-js/) library provides various [file handling functions](https://inrupt.github.io/solid-client-js/modules/resource_file.html). Like other data stored in a Pod, each [File](https://inrupt.github.io/solid-client-js/modules/interfaces.html#file) is a [Resource](/reference/glossary#resource) with a distinct URL, which may or may not include the file extension.

### Required Access

The same access control mechanism applies to these files that applies to any other Resource in the Pod. As such, to perform file operations on restricted Resources (i.e., not open to the general public), the user must first authenticate as someone with the appropriate access. Then, to make authenticated requests, pass to the various read/write functions the authenticated Session’s **`fetch`** function. For more information on authentication, see [Authentication](/sdk/javascript-sdk/authentication).

<table><thead><tr><th width="264.42578125">Action</th><th>Required Access</th></tr></thead><tbody><tr><td>Read a file</td><td><strong><code>Read</code></strong> access to the file.</td></tr><tr><td>To write a new file to a Container</td><td><p>Either <strong><code>Append</code></strong> or <strong><code>Write</code></strong> access to the Container, depending on the library function used:</p><p>To use <a href="https://inrupt.github.io/solid-client-js/modules/resource_file.html#savefileincontainer"><strong><code>saveFileInContainer()</code></strong></a>, the user must have <strong><code>Append</code></strong> and/or <strong><code>Write</code></strong> access to the Container.</p><p>To use <a href="https://inrupt.github.io/solid-client-js/modules/resource_file.html#overwritefile"><strong><code>overwriteFile()</code></strong></a>, the user must have both <strong><code>Write</code></strong> access to the Container and <strong><code>Write</code></strong> access to the target file.<br><br>To create access policies for yet to be created files, create a default member policy with <strong><code>Write</code></strong> access for the Container.</p></td></tr><tr><td>To replace an existing file in a Container</td><td><p>Either <strong><code>Append</code></strong> or <strong><code>Write</code></strong> access to the Container, depending on the library function used:</p><p>To use <a href="https://inrupt.github.io/solid-client-js/modules/resource_file.html#savefileincontainer"><strong><code>saveFileInContainer()</code></strong></a>, the user must have <strong><code>Append</code></strong> and/or <strong><code>Write</code></strong> access to the Container.</p><p>To use <a href="https://inrupt.github.io/solid-client-js/modules/resource_file.html#overwritefile"><strong><code>overwriteFile()</code></strong></a>, the user must have both <strong><code>Write</code></strong> access to the Container and <strong><code>Write</code></strong> access to the target File.</p></td></tr><tr><td>To delete an existing file in a Container</td><td>Both <strong><code>Write</code></strong> access to the File and <strong><code>Write</code></strong> access to the Container.</td></tr></tbody></table>

For files saved in a Pod, their URL acts as the unique identifier. Their URLs are relative to the Pod's URL. For example:

* **`https://storage.inrupt.com/{someIdentifier}/pictures/picture.jpg`**
* **`https://storage.inrupt.com/{someIdentifier}/data/inventory1.pdf`**

where **`https://storage.inrupt.com/{someIdentifier}/`** is the Pod's URL.

Inrupt’s **`solid-client`** library provides [**`getPodUrlAll`**](https://inrupt.github.io/solid-client-js/functions/profile_webid.getPodUrlAll.html) to get the Pod’s URL or, if the user has multiple Pods, the list of Pod URLs.

```javascript
import { getPodUrlAll } from "@inrupt/solid-client";

// Returns a list of URLs

const mystorages = await getPodUrlAll(webID, { fetch: fetch });
```

### Read a File

To read a file, you can use [**`getFile()`**](https://inrupt.github.io/solid-client-js/modules/resource_file.html#getfile) to fetch the file content at the specified URL. The **`getFile()`** returns a File. Once fetched, you can decode appropriately.

{% hint style="info" %}
To use [**`getFile()`**](https://inrupt.github.io/solid-client-js/modules/resource_file.html#getfile), the user must have **`Read`** access for the file.
{% endhint %}

The following example uses **`getFile()`** to read the specified files. The example assumes the user has the appropriate access.

{% tabs %}
{% tab title="Browser" %}

<pre class="language-javascript"><code class="lang-javascript">// ... import statement for authentication, which includes the fetch function, is omitted for brevity.
<strong>import { getFile, isRawData, getContentType, getSourceUrl, } from "@inrupt/solid-client";
</strong>
// ... Various logic, including login logic, omitted for brevity.

// Read file from Pod 
async function readFileFromPod(fileURL) {
  try {
    // File (https://inrupt.github.io/solid-client-js/modules/interfaces.html#file) is a Blob (see https://developer.mozilla.org/docs/Web/API/Blob)
<strong>    const file = await getFile(
</strong><strong>      fileURL,               // File in Pod to Read
</strong><strong>      { fetch: fetch }       // fetch from authenticated session
</strong><strong>    );
</strong>
    console.log( **`Fetched a ${getContentType(file)} file from ${getSourceUrl(file)}.`);
    console.log(`The file is ${isRawData(file) ? "not " : ""}a dataset.`);

  } catch (err) {
    console.log(err);
  }
}
</code></pre>

{% endtab %}

{% tab title="Node.JS" %}

<pre class="language-javascript"><code class="lang-javascript">// ... import statement for authentication, which includes the fetch function, is omitted for brevity.

import { writeFile } from 'fs/promises';
<strong>import { getFile, isRawData, getContentType, getSourceUrl, } from "@inrupt/solid-client";
</strong>
const MY_POD_URL = "https://example.com/mypod/";

const session = new Session();

// ... Various logic, including login logic, omitted for brevity.

if (session.info.isLoggedIn) {
  readFileFromPod(`${MY_POD_URL}mypics/pigeon.jpg`, session.fetch, './downloaded-pigeon.jpg');
}

// ...

// Read file from Pod and save to local file
async function readFileFromPod(fileURL, fetch, saveAsFilename) {
  try {
<strong>    const file = await getFile(
</strong><strong>      fileURL,               // File in Pod to Read
</strong><strong>      { fetch: fetch }       // fetch from authenticated session
</strong><strong>    );
</strong>
    console.log(`Fetched a ${getContentType(file)} file from ${getSourceUrl(file)}.`);
    console.log(`The file is ${isRawData(file) ? "not " : ""}a dataset.`);

    const arrayBuffer = await file.arrayBuffer();
    writeFile(saveAsFilename, new Uint8Array(arrayBuffer));

  } catch (err) {
    console.log(err);
  }
}
</code></pre>

{% endtab %}
{% endtabs %}

The above example uses:

* [`getFile()`](https://inrupt.github.io/solid-client-js/modules/resource_file.html#getfile) to fetch the File.
* [`getContentType()`](https://inrupt.github.io/solid-client-js/modules/resource_resource.html#getcontenttype) to return the content type. For example, **`text/plain;charset=UTF-8`** or **`image/svg+xml`** or **`text/html;charset=UTF-8`**. You can use `getContentType()` on any Resource, not just Files.
* [`isRawData()`](https://inrupt.github.io/solid-client-js/modules/resource_resource.html#israwdata) to determine if the Resource is raw data (i.e., not a SolidDataset). You can use `isRawData()` on any Resource, not just Files.

{% hint style="info" %}
You can use [`getFile()`](https://inrupt.github.io/solid-client-js/modules/resource_file.html#getfile) to retrieve a file that contains structured data. In this case, [`getContentType()`](https://inrupt.github.io/solid-client-js/modules/resource_resource.html#getcontenttype) on the returned File might return **`text/turtle; charset=UTF-8`** if the user’s Pod server defaults to returning structured data in that format. The [`isRawData()`](https://inrupt.github.io/solid-client-js/modules/resource_resource.html#israwdata) on the File returns **`false`**.
{% endhint %}

### Write a File

When writing a file to a Pod, you can:

* Use [`overwriteFile()`](https://inrupt.github.io/solid-client-js/modules/resource_file.html#overwritefile) to specify the exact destination file URL.
* Use [`saveFileInContainer()`](https://inrupt.github.io/solid-client-js/modules/resource_file.html#savefileincontainer) to specify only the URL for the parent [Container](/reference/glossary#container).

#### Write a File to a Specific URL

To specify the file’s destination URL during the save, use `overwriteFile()`. To use `overwriteFile()`, pass it the following parameters:

<table data-header-hidden><thead><tr><th width="154"></th><th></th><th data-hidden></th></tr></thead><tbody><tr><td>File URL</td><td>The destination URL for the File. If a file already exists at that URL, the function <mark style="color:red;"><strong>overwrites</strong></mark> the existing file.</td><td></td></tr><tr><td>Options object</td><td><p>An object that includes the following options:</p><p><strong><code>{ contentType: &#x3C;MIME type>, fetch: &#x3C;fetch func> }</code></strong><br></p><p><strong>fetch</strong></p><p><strong><code>fetch</code></strong> function from an authenticated session if accessing restricted Resource (i.e., the general public cannot write file at the specified location). See <a data-mention href="/pages/PoAxAaEt4Wur1R5pmPPi">/pages/PoAxAaEt4Wur1R5pmPPi</a>.</p><p>Optional if the general public can write files at the specified location.</p><p><strong>contentType</strong></p><p>Optional. MIME type.</p></td><td></td></tr></tbody></table>

{% hint style="info" %}

* To use `overwriteFile()`, the user must have both **`Write`** access to the Container and **`Write`** access to the target file. Since the new file does not yet exist in the Container, the Container must have included **`Write`** access as its default member access.
* When using `overwriteFile()` to save the file to the destination URL, the Solid server creates any intermediate Container as needed.
  {% endhint %}

{% tabs %}
{% tab title="Browser" %}
The following example uses `overwriteFile()` to save the selected local files to the specified URL. The example assumes the user has the appropriate access.

<pre class="language-javascript"><code class="lang-javascript">// ... import statement for authentication, which includes the fetch function, is omitted for brevity.

<strong>import { overwriteFile, getSourceUrl } from "@inrupt/solid-client";
</strong>
// ... Various logic, including login logic, omitted for brevity.

const MY_POD_URL = "https://example.com/mypod/";

// Upload selected files to Pod
function handleFiles() {
  const fileList = document.getElementById('fileinput').files;

  fileList.forEach(file => {
    writeFileToPod(file, `${MY_POD_URL}uploadedFiles/${file.name}`, fetch);
  });
}

// Upload File to the targetFileURL.
// If the targetFileURL exists, overwrite the file.
// If the targetFileURL does not exist, create the file at the location.
async function writeFileToPod(file, targetFileURL, fetch ) {
  try {
<strong>    const savedFile = await overwriteFile(  
</strong><strong>      targetFileURL,                              // URL for the file.
</strong><strong>      file,                                       // File
</strong><strong>      { contentType: file.type, fetch: fetch }    // mimetype if known, fetch from the authenticated session
</strong><strong>    );
</strong>    console.log(`File saved at ${getSourceUrl(savedFile)}`);

  } catch (error) {
    console.error(error);
  }
}
</code></pre>

In the example, if the **`uploadedFiles`** Container does not exist when saving the file, the Solid server creates it to save the file to the specified URL.
{% endtab %}

{% tab title="Node.js" %}
The following example reads local files and uses `overwriteFile()` to save the files to the specified URL. The example assumes the user has the appropriate access.

<pre class="language-javascript"><code class="lang-javascript">// ... import statement for authentication, which includes the fetch function, is omitted for brevity.

import { readFile } from 'fs/promises';
<strong>import { overwriteFile, getSourceUrl } from "@inrupt/solid-client";
</strong>
const MY_POD_URL = "https://example.com/mypod/";

const session = new Session();

// ... Various logic, including login logic, omitted for brevity.
  
if (session.info.isLoggedIn) {
  uploadFile('./pigeon.jpg', "image/jpeg", `${MY_POD_URL}mypics/pigeon.jpg`, session.fetch);
  uploadFile('./report.pdf', "application/pdf",`${MY_POD_URL}mypdfs/report.pdf`, session.fetch);
}

// ...

// Read local file and save to targetURL
async function uploadFile(filepath, mimetype, targetURL, fetch) {
  try {
    const data = await readFile(filepath);
    writeFileToPod(data, mimetype, targetURL, fetch);
  } catch (err) {
    console.log(err);
  }
}

// Upload data as a file to the targetFileURL.
// If the targetFileURL exists, overwrite the file.
// If the targetFileURL does not exist, create the file at the location.
async function writeFileToPod(filedata, mimetype, targetFileURL, fetch) {
  try {
<strong>    const savedFile = await overwriteFile(  
</strong><strong>      targetFileURL,                   // URL for the file.
</strong><strong>      filedata,                        // Buffer containing file data
</strong><strong>      { contentType: mimetype, fetch: fetch } // mimetype if known, fetch from the authenticated session
</strong><strong>    );
</strong>    console.log(`File saved at ${getSourceUrl(savedFile)}`);
  } catch (error) {
    console.error(error);
  }
</code></pre>

In the example, if the `mypod` and `mypdfs` Containers do not exist when saving the file, the Solid server creates them to save the file to the specified URL.
{% endtab %}
{% endtabs %}

#### Write a File into an Existing Container

To specify only the URL of the parent Container during the save, i.e., to let the Solid server determine the name of your file in the Container, use [`saveFileInContainer()`](https://inrupt.github.io/solid-client-js/modules/resource_file.html#savefileincontainer). To use `saveFileInContainer()`, pass it the following parameters:

<table data-header-hidden><thead><tr><th width="154"></th><th></th><th data-hidden></th></tr></thead><tbody><tr><td>Container URL</td><td>The URL of the Container where you wish to place the file. The Container must already exist.</td><td></td></tr><tr><td>Options object</td><td><p>An object that includes the following options:</p><p><strong><code>{ slug: &#x3C;name>, contentType: &#x3C;MIME type>, fetch: &#x3C;fetch func> }</code></strong><br></p><p><strong>slug</strong></p><p>Optional. The suggested file name. There is no guarantee that the Solid server will use the <code>slug</code> as the saved file name.</p><p>If the Solid server decides to use the <code>slug</code> as the file name but the <code>slug</code> matches an already existing file in the specified Container, the Solid server creates a <strong>new</strong> name for your file. That is, the function does <strong>not</strong> overwrite existing files.</p><p><strong>fetch</strong></p><p><strong><code>fetch</code></strong> function from an authenticated session if accessing restricted Resource (i.e., the general public cannot write file at the specified location). See <a data-mention href="/pages/PoAxAaEt4Wur1R5pmPPi">/pages/PoAxAaEt4Wur1R5pmPPi</a>.</p><p>Optional if the general public can write files at the specified location.</p><p><strong>contentType</strong></p><p>Optional. MIME type.</p></td><td></td></tr></tbody></table>

{% hint style="info" %}

* To use `saveFileInContainer()`, the user must have **`Append`** and/or **`Write`** access to the Container. See [Authentication](/sdk/javascript-sdk/authentication).
* With `saveFileInContainer()`, you do not control the name, and thus the URL, of your file. The Solid server may or may not use the suggested **`slug`** as the file name.
* If the specified Container does not exist, the save operation fails.
  {% endhint %}

The following example reads local files and uses `saveFileInContainer()` to save the files into the specified Container. The example assumes the user has the appropriate access.

{% tabs %}
{% tab title="Browser" %}

<pre class="language-javascript"><code class="lang-javascript">// ... import statement for authentication, which includes the fetch function, is omitted for brevity.

<strong>import { saveFileInContainer, getSourceUrl } from "@inrupt/solid-client";
</strong>
// ... Various logic, including login logic, omitted for brevity.

const MY_POD_URL = "https://example.com/mypod/";

// Upload selected files into Container
function handleFiles() {
  const fileList = document.getElementById('fileinput').files;

  fileList.forEach(file => {
    placeFileInContainer(file, `${MY_POD_URL}uploadedFiles/`);
  });
}

// Upload file into the targetContainer.
async function placeFileInContainer(file, targetContainerURL) {
  try {
<strong>    const savedFile = await saveFileInContainer(
</strong><strong>      targetContainerURL,           // Container URL
</strong><strong>      file,                         // File 
</strong><strong>      { slug: file.name, contentType: file.type, fetch: fetch }
</strong>    );
    console.log(`File saved at ${getSourceUrl(savedFile)}`);
  } catch (error) {
    console.error(error);
  }
}
</code></pre>

{% endtab %}

{% tab title="Node.js" %}

<pre class="language-javascript"><code class="lang-javascript">// ... import statement for authentication, which includes the fetch function, is omitted for brevity.

import { readFile } from 'fs/promises';
<strong>import { saveFileInContainer, getSourceUrl } from "@inrupt/solid-client";
</strong>
const MY_POD_URL = "https://example.com/mypod/";

const session = new Session();

// ... Various logic, including login logic, omitted for brevity.

if (session.info.isLoggedIn) {
  readAndSaveFile('./pigeon.jpg', "image/jpeg", `${MY_POD_URL}mypics/`, "mypigeon.jpg", session.fetch);
  readAndSaveFile('./report.pdf', "application/pdf", `${MY_POD_URL}mypdfs/`, "myreport.pdf", session.fetch);
}

// ...

// Read local file and upload into a Container
async function readAndSaveFile(filepath, mimetype, containerURL, slug, fetch) {
  
  try {
    const data = await readFile(filepath);
    placeFileInContainer(data, mimetype, containerURL, slug, fetch);
  } catch (err) {
    console.log(err);
  }
}

// Upload data as a file into the targetContainer.
async function placeFileInContainer(filedata, mimetype, targetContainerURL, slug, fetch) {
  try {
<strong>    const savedFile = await saveFileInContainer(
</strong><strong>      targetContainerURL,           // Container URL
</strong><strong>      filedata,                     // Buffer containing file data
</strong><strong>      { slug: slug, contentType: mimetype, fetch: fetch }
</strong><strong>    );
</strong>    console.log(`File saved at ${getSourceUrl(savedFile)}`);
  } catch (error) {
    console.error(error);
  }
}
</code></pre>

{% endtab %}
{% endtabs %}

After saving the file, the example uses [`getSourceUrl`](https://inrupt.github.io/solid-client-js/modules/resource_resource.html#getsourceurl) on the returned file to determine the saved filename.

### Delete a File

To delete a file, you can use [`deleteFile()`](https://inrupt.github.io/solid-client-js/modules/resource_file.html#deletefile) to remove the file at the specified URL. To use `deleteFile()`, pass it the following parameters:

<table data-header-hidden><thead><tr><th width="227.5"></th><th></th></tr></thead><tbody><tr><td>File URL</td><td>The URL of the file to delete.</td></tr><tr><td>Options object</td><td><p>An object that includes the following option:<br><strong><code>{ fetch: &#x3C;fetch func> }</code></strong><br><br><strong>fetch</strong><br><code>fetch</code> function from an authenticated session if deleting a restricted Resource (i.e., the general public cannot delete the specified File). See <a data-mention href="/pages/PoAxAaEt4Wur1R5pmPPi">/pages/PoAxAaEt4Wur1R5pmPPi</a>.</p><p>Optional if the general public can delete the Resource.<br></p></td></tr></tbody></table>

{% hint style="info" %}
The user must have **`Write`** access to the File.
{% endhint %}

The following example uses `deleteFile()` to delete the specified file. The example assumes the user has the appropriate access.

{% tabs %}
{% tab title="Browser" %}

<pre class="language-javascript"><code class="lang-javascript">// ... import statement for authentication, which includes the fetch function, is omitted for brevity.

<strong>import { deleteFile } from "@inrupt/solid-client";
</strong>
// ... Various logic, including login logic, omitted for brevity.

try {
  // Delete the specified file from the Pod.
<strong>  await deleteFile(
</strong><strong>    "https://example.com/some/boring/file",  // File to delete
</strong><strong>    { fetch: fetch }                         // fetch function from authenticated session
</strong>  );
  console.log("Deleted::  https://example.com/some/boring/file");
} catch (err) {
  console.error(err);
}
</code></pre>

{% endtab %}

{% tab title="Node.js" %}

```javascript
// ... import statement for authentication, which includes the fetch function, is omitted for brevity.

import { deleteFile } from "@inrupt/solid-client";

const session = new Session();

// ... Various logic, including login logic, omitted for brevity.

if (session.info.isLoggedIn) {
  deleteFileFromPod("https://example.com/mypod/mypics/pigeon.jpg", session.fetch);
}

// ...


async function deleteFileFromPod(fileURL, fetch) {
  try {
    await deleteFile(
      fileURL,          // File to delete from Pod
      { fetch: fetch }   // fetch from the authenticated session
    );
    console.log(`File deleted at ${fileURL}`);
  } catch (error) {
    console.error(error);
  }
}
```

{% endtab %}
{% endtabs %}


# CRUD (RDF Data)

Reading and writing structured data stored in Wallet Storages (Pods).

{% hint style="info" %}
For information on structured data, or [Resource Description Framework (RDF)](/reference/glossary#rdf-resource) data, see [Structured Data](/reference/rdf/structured-data-rdf-resources).
{% endhint %}

To read and write [RDF data](/reference/glossary#rdf-resource) to [Pods](/reference/glossary#pods), Inrupt provides the [**`solid-client`**](https://inrupt.github.io/solid-client-js/) library.

### Required Access

By default, **`solid-client`** functions make unauthenticated requests. To perform read/write operations on restricted [Resources](/reference/glossary#resource) (i.e., not open to the general public), the user must be authenticated with appropriate access to that Resource. Then, to make authenticated requests, pass to the various read/write functions the authenticated session’s **`fetch`** function.

{% tabs %}
{% tab title="Create" %}
The creation operation creates the Resource and updates the content of the parent [Container](/reference/glossary#container) with the new Resource’s metadata.

{% hint style="info" %}
**Access**

To create a Resource, either **`Append`** or **`Write`** access is required.
{% endhint %}

<table><thead><tr><th width="160.4140625">Target Resource</th><th>Required Access</th></tr></thead><tbody><tr><td><a href="/pages/7PKZaxnnUDGdJ8gPDOqL#soliddataset">SolidDataset</a></td><td><p>Either <strong><code>Append</code></strong> or <strong><code>Write</code></strong> access to the parent Container, depending on the library’s function:</p><ul><li><a href="https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#savesoliddatasetin%20container"><code>saveSolidDatasetInContainer()</code></a>: Either <strong><code>Append</code></strong> or <strong><code>Write</code></strong> access to the parent Container.</li><li><a href="https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#savesoliddatasetat"><code>saveSolidDatasetAt()</code></a>: <strong><code>Write</code></strong> access to the parent Container.</li></ul></td></tr><tr><td><a href="/pages/7PKZaxnnUDGdJ8gPDOqL#container">Container</a></td><td>Either <strong><code>Append</code></strong> or <strong><code>Write</code></strong> access on the <strong>parent</strong> Container (under which the new Container is to be created) allows <a href="/pages/7PKZaxnnUDGdJ8gPDOqL#agent">Agents</a> to create a new Container.</td></tr></tbody></table>
{% endtab %}

{% tab title="Read" %}
For read operations, the user requires **`Read`** access.

<table><thead><tr><th width="160.984375">Target Resource</th><th>Description</th></tr></thead><tbody><tr><td><a href="https://docs.inrupt.com/reference/glossary#term-SolidDataset">SolidDataset</a></td><td><strong><code>Read</code></strong> access on the target SolidDataset allows agents to read/retrieve the SolidDataset (regardless of the access on the parent Container).</td></tr><tr><td><a href="https://docs.inrupt.com/reference/glossary/#term-Container">Container</a></td><td><p><strong><code>Read</code></strong> access on the target Container (analogous to a folder in a file system) allow agents to read/retrieve the Container as a resource (not the resource(s) under the Container). That is, Read access only affects read operation on the Container itself.</p><p>Reading a Container (which stores metadata about the resources contained within the Container) allows a client to discover what resources are contained inside the Container and their resource type (i.e., analogous to an <strong><code>ls</code></strong> on a folder in a file system).</p></td></tr></tbody></table>
{% endtab %}

{% tab title="Update" %}
For update operations, the user requires **`Append`** or **`Write`** access, depending on the specific update operation.

<table><thead><tr><th width="161.66796875">Target Resource</th><th>Description</th></tr></thead><tbody><tr><td><a href="https://docs.inrupt.com/reference/glossary#term-SolidDataset">SolidDataset</a></td><td><ul><li>Either <strong><code>Append</code></strong> or <strong><code>Write</code></strong> access on the target SolidDataset allows agents to add to the resource: new <a href="https://docs.inrupt.com/developer-tools/javascript/client-libraries/reference/glossary/#term-Thing">Things</a>; new properties and value(s) for existing Things.</li><li><strong><code>Write</code></strong> access on an RDF resource allows agents to delete Things from the resource.</li><li><strong><code>Write</code></strong> access on an RDF resource allows agents to update property/value for existing Things in the resource.</li></ul></td></tr><tr><td><a href="https://docs.inrupt.com/reference/glossary/#term-Container">Container</a></td><td><p>To add resources to the Container, see the <strong><code>Create</code></strong> tab.</p><p>To delete resources from the Container, see the <strong><code>Delete</code></strong> tab.</p></td></tr></tbody></table>
{% endtab %}

{% tab title="Delete" %}
For delete operations, the user requires **`Write`** access.

<table><thead><tr><th width="160.10546875">Target</th><th>Description</th></tr></thead><tbody><tr><td><a href="https://docs.inrupt.com/reference/glossary#term-SolidDataset">SolidDataset</a></td><td>To delete a SolidDataset, <strong><code>Write</code></strong> access on both the parent Container and the target SolidDataset allows agents to delete the target SolidDataset.</td></tr><tr><td><a href="https://docs.inrupt.com/reference/glossary/#term-Container">Container</a></td><td><p><strong><code>Write</code></strong> access on both the parent Container and the target Container allows agents to delete the target Container.</p><p>To delete a Container, the target Container must be empty.</p></td></tr></tbody></table>
{% endtab %}
{% endtabs %}

### Prerequisite for Restricted Data

To make authenticated requests, you can use one of Inrupt’s authentication libraries to login and pass the [fetch()](https://inrupt.github.io/solid-client-authn-js/browser/functions.html#fetch) function as an option to **`solid-client`** functions. 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.

The following example uses **`solid-client-authn-browser`** to authenticate a user and use the authenticated Session’s [`fetch()`](https://inrupt.github.io/solid-client-authn-js/browser/functions.html#fetch) function to make authenticated requests:

<pre class="language-javascript"><code class="lang-javascript">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,
  //    - Which completes the login flow if redirected back to this page as part of login; or
  //    - Which is a No-op if not part of login.
  await handleIncomingRedirect();

  // 2. Start the Login Process if not already logged in.
  if (!getDefaultSession().info.isLoggedIn) {
    await login({
      oidcIssuer: "https://login.inrupt.com",
      redirectUrl: new URL("/", window.location.href).toString(),
      clientName: "My application"
    });
  }

  // ...
  // const exampleSolidDatasetURL = ...;
  
  // 3. Make authenticated requests by passing `fetch` to the solid-client functions.
  // For example, the user must be someone with Read access to the specified URL.
  const myDataset = await getSolidDataset(
    exampleSolidDatasetURL, 
<strong>    { fetch: fetch }  // fetch function from authenticated session
</strong>  );

  // ...
  
  // For example, the user must be someone with Write access to the specified URL.
  const savedSolidDataset = await saveSolidDatasetAt(
    exampleSolidDatasetURL,
    myChangedDataset,
<strong>    { fetch: fetch }  // fetch function from authenticated session
</strong>  );
}

loginAndFetch();
</code></pre>

For more information on authentication, see [Authentication](/sdk/javascript-sdk/authentication).

For files saved in a Pod, their URL acts as the unique identifier. Their URLs are relative to the Pod's URL. For example:

* **`https://storage.inrupt.com/{someIdentifier}/pictures/picture.jpg`**
* **`https://storage.inrupt.com/{someIdentifier}/data/inventory1.pdf`**

where **`https://storage.inrupt.com/{someIdentifier}/`** is the Pod's URL.

Inrupt’s **`solid-client`** library provides [**`getPodUrlAll`**](https://inrupt.github.io/solid-client-js/functions/profile_webid.getPodUrlAll.html) to get the Pod’s URL or, if the user has multiple Pods, the list of Pod URLs.

```javascript
import { getPodUrlAll } from "@inrupt/solid-client";

// Returns a list of URLs

const mystorages = await getPodUrlAll(webID, { fetch: fetch });
```

### Assumptions

The following examples assumes:

* The application has used the **`solid-client-authn-browser`** library to handle login and has an authenticated [`fetch`](https://inrupt.github.io/solid-client-authn-js/browser/functions.html#fetch) function.
* The logged-in user has the appropriate permissions to perform the specified SolidDataset operations.

### Read Data

To read data with **`solid-client`**:

1. Use [getSolidDataset](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#getsoliddataset) to fetch the SolidDataset. You must have **`Read`** access to the SolidDataset.
2. Then, use either:
   * [getThing](https://inrupt.github.io/solid-client-js/modules/thing_thing.html#getthing) to get a single data entity from the fetched SolidDataset, or
   * [getThingAll](https://inrupt.github.io/solid-client-js/modules/thing_thing.html#getthingall) to get all data entities from the fetched SolidDataset.
3. Then, from the data entity (i.e., **`Thing`**), you can [get](https://inrupt.github.io/solid-client-js/modules/thing_get.html) specific data. For a list of the **`get`** functions, see [thing/get module](https://inrupt.github.io/solid-client-js/modules/thing_get.html).

#### 0. Import

For the examples, import the following objects from the client libraries:

```javascript
import {
  getSolidDataset,
  getThing,
  getStringNoLocale,
  getUrlAll
} from "@inrupt/solid-client";

```

#### 1. Fetch the **`SolidDataset`**

Use [getSolidDataset](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#getsoliddataset) to fetch the **`SolidDataset`** that contains the data:

* Pass the **`SolidDataset`** URL to [getSolidDataset](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#getsoliddataset). In the following example, the **`SolidDataset`** is the reading list document created as part of Getting Started.
* To make an authenticated request, the example includes a **`fetch`** function associated with a logged in Session.

```javascript
  const myDataset = await getSolidDataset(
    readingListUrl,
    { fetch: fetch }          // fetch from authenticated session
  );
```

#### 2. Get the Data Entity **`Thing`**

From the fetched SolidDataset, you can use either:

* [getThing](https://inrupt.github.io/solid-client-js/modules/thing_thing.html#getthing) with the Thing’s URL to get a single data entity, or
* [getThingAll](https://inrupt.github.io/solid-client-js/modules/thing_thing.html#getthingall) to get all data entities from the dataset.

Typically, but not always, a Thing’s URL is the SolidDataset’s URL appended with a hash fragment **`#<something>`**; that is, **`<SolidDataset URL>#<something>`**,

In the reading list example, the **`titles`** (Things) use the hash fragment **`#title<number>`**,

The following example uses [getThing](https://inrupt.github.io/solid-client-js/modules/thing_thing.html#getthing) to retrieve a book title from the previously fetched SolidDataset (i.e., the reading list document).

```javascript
  const itemForWeek = getThing(
    myDataset,
    `${readingListUrl}#title${weekNum}`
  );
```

#### 3. Read Data Attribute of a Thing

Like the **`Thing`** and **`SolidDataset`**, each property (such as **`name`**, etc.) of a **`Thing`** is also identified by a URL. That is, when storing the **`name`** of a book, you do not store the data under the string identifier **`name`**, Instead, you use a URL identifier, such as **`https://schema.org/name`** from the **`https://schema.org`** Vocabulary (or some other URL).

A property can have zero, one or more values, and the value is typed; e.g., a string, an integer, or a URL if pointing to other Things. To retrieve the data, use the appropriate [get](https://inrupt.github.io/solid-client-js/modules/thing_get.html) function depending on the data type and the number of values for the property.

<table data-header-hidden><thead><tr><th width="177.8359375"></th><th width="183.6796875"></th><th width="230.85546875"></th><th></th></tr></thead><tbody><tr><td><p><a href="https://inrupt.github.io/solid-client-js/modules/thing_get.html#getboolean">getBoolean</a></p><p><a href="https://inrupt.github.io/solid-client-js/modules/thing_get.html#getbooleanall">getBooleanAll</a></p><p><a href="https://inrupt.github.io/solid-client-js/modules/thing_get.html#getdate">getDate</a></p><p><a href="https://inrupt.github.io/solid-client-js/modules/thing_get.html#getdateall">getDateAll</a></p><p><a href="https://inrupt.github.io/solid-client-js/modules/thing_get.html#getdatetime">getDatetime</a></p><p><a href="https://inrupt.github.io/solid-client-js/modules/thing_get.html#getdatetimeall">getDatetimeAll</a></p></td><td><p><a href="https://inrupt.github.io/solid-client-js/modules/thing_get.html#getdecimal">getDecimal</a></p><p><a href="https://inrupt.github.io/solid-client-js/modules/thing_get.html#getdecimalall">getDecimalAll</a></p><p><a href="https://inrupt.github.io/solid-client-js/modules/thing_get.html#getinteger">getInteger</a></p><p><a href="https://inrupt.github.io/solid-client-js/modules/thing_get.html#getintegerall">getIntegerAll</a></p></td><td><p><a href="https://inrupt.github.io/solid-client-js/modules/thing_get.html#getstringbylocaleall">getStringByLocaleAll</a></p><p><a href="https://inrupt.github.io/solid-client-js/modules/thing_get.html#getstringnolocale">getStringNoLocale</a></p><p><a href="https://inrupt.github.io/solid-client-js/modules/thing_get.html#getstringnolocaleall">getStringNoLocaleAll</a></p><p><a href="https://inrupt.github.io/solid-client-js/modules/thing_get.html#getstringwithlocale">getStringWithLocale</a><a href="https://inrupt.github.io/solid-client-js/modules/thing_get.html#getstringwithlocaleall">getStringWithLocaleAll</a></p></td><td><p><a href="https://inrupt.github.io/solid-client-js/modules/thing_get.html#gettime">getTime</a></p><p><a href="https://inrupt.github.io/solid-client-js/modules/thing_get.html#gettimeall">getTimeAll</a></p><p><a href="https://inrupt.github.io/solid-client-js/modules/thing_get.html#geturl">getUrl</a></p><p><a href="https://inrupt.github.io/solid-client-js/modules/thing_get.html#geturlall">getUrlAll</a></p></td></tr></tbody></table>

{% hint style="info" %}
Note

These types are specific to Solid, i.e. they are not JavaScript types. As such, there is a distinction between integers and decimals as well as a separate type for URLs.
{% endhint %}

To the [get](https://inrupt.github.io/solid-client-js/modules/thing_get.html) function, pass the URL that identifies the property to fetch. To encourage interoperability, various Vocabularies exist to identify common data. For example:

* **`https://schema.org/name`** identifies a string that represents a name of a Thing.

```javascript
  // In this example, use `getStringNoLocale` to get a single string data value from the Thing.
  // (i.e., "https://schema.org/name") value as a string.
  const title = getStringNoLocale(itemForWeek, "https://schema.org/name");
```

### Write Data

#### Write a New SolidDataset

**0. Import**

For the examples, import the following objects from the client libraries:

```javascript
import { login, handleIncomingRedirect, getDefaultSession, fetch } from "@inrupt/solid-client-authn-browser";

import {
  addUrl,
  addStringNoLocale,
  buildThing,
  createSolidDataset,
  createThing,
  setThing,
  saveSolidDatasetAt,
} from "@inrupt/solid-client";
```

**1. Create a new `SolidDataset`**

You can use [createSolidDataset](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#createsoliddataset) to create a new SolidDataset. For example:

```javascript
// Create a new SolidDataset for Writing 101
let courseSolidDataset = createSolidDataset();
```

**2. Create a new `Thing`**

{% tabs %}
{% tab title="Fluent API" %}
First, use [createThing](https://inrupt.github.io/solid-client-js/modules/thing_thing.html#creatething) to create a new Thing (i.e., data entity) for the SolidDataset. Pass the function a **`name`** for the Thing. Typically, a Thing’s URL is the SolidDataset’s URL appended with a **`#`** hash fragment. Upon save, the specified **`name`** becomes the **`#`** hash fragment; i.e., the Thing’s URL becomes **`<SolidDatasetURL>#<name>.`**

Then, you can use the [buildThing()](https://inrupt.github.io/solid-client-js/modules/thing_build.html#buildthing) and the [ThingBuilder functions](https://inrupt.github.io/solid-client-js/modules/thing_build.html#buildthing) (Fluent API) to generate the Thing with the data modifications.

For example:

```javascript
// Create a new Thing for "book1"; Thing's URL will include the hash #book1.
// Use Fluent API to add properties to the new Thing, and 
// build a new Thing with the properties.
// Note: solid-client functions do not modify objects passed in as arguments. 
// Instead the functions return new objects with the modifications.
const newBookThing1 = buildThing(createThing({ name: "book1" }))
  .addStringNoLocale("https://schema.org/name", "ABC123 of Example Literature")
  .addUrl(RDF.type, "https://schema.org/Book")
  .build();
```

The example creates a new Thing with name `book1` (which will be part of its URL), and using the Fluent API, adds the following Thing properties and value:

<table data-header-hidden><thead><tr><th width="285.83984375"></th><th></th></tr></thead><tbody><tr><td><code>SCHEMA_INRUPT.name</code></td><td><code>"ABC123 of Example Literature"</code></td></tr><tr><td><code>RDF.type</code></td><td><code>"https://schema.org/Book"</code></td></tr></tbody></table>

{% hint style="info" %}
The **`solid-client`** library’s functions (such as the various add/set/remove functions) do not modify the objects that are passed in as arguments. Instead, the library’s functions return a new object with the requested changes, and do not modify the passed-in objects.
{% endhint %}
{% endtab %}

{% tab title="Non-Fluent Alternative" %}
First, use [createThing](https://inrupt.github.io/solid-client-js/modules/thing_thing.html#creatething) to create a new Thing (i.e., data entity) for the SolidDataset. Pass the function a **name** for the Thing. Typically, a Thing’s URL is the SolidDataset’s URL appended with a # hash fragment. Upon save, the specified **name** becomes the **#** hash fragment; i.e., the Thing’s URL becomes **`<SolidDatasetURL>#<name>`**.

Then, use the series of add/set/remove functions to create a new Thing with the data modifications:

* [thing/add](https://inrupt.github.io/solid-client-js/modules/thing_add.html) functions that return a new Thing modified with new data added,
* [thing/set](https://inrupt.github.io/solid-client-js/modules/thing_set.html) functions that return a new Thing modified with the specified data, and
* [thing/remove](https://inrupt.github.io/solid-client-js/modules/thing_remove.html) functions that return a new Thing modified with specified data removed.

```javascript
// Create a new Thing for "book2"; Thing's URL will include the hash #book2.
// Use various add functions to add properties to the Thing
// Note: solid-client functions do not modify objects passed in as arguments. 
// Instead the functions return new objects with the modifications.
let newBookThing2 = createThing({ name: "book2" });
newBookThing2 = addStringNoLocale(newBookThing2, SCHEMA_INRUPT.name, "ZYX987 of Example Poetry");
newBookThing2 = addUrl(newBookThing2, RDF.type, "https://schema.org/Book");
```

The example creates a new Thing with name `book2` (which will be part of its URL), and using a sequence of [thing/add](https://inrupt.github.io/solid-client-js/modules/thing_add.html) functions to add the following Thing properties:

| `SCHEMA_INRUPT.name` | `"ZYX987 of Example Poetry"` |
| -------------------- | ---------------------------- |
| `RDF.type`           | `"https://schema.org/Book"`  |

{% hint style="info" %}
The solid-client library’s functions (such as the various add/set/remove functions) do not modify the objects that are passed in as arguments. Instead, the library’s functions return a new object with the requested changes, and do not modify the passed-in objects.
{% endhint %}
{% endtab %}
{% endtabs %}

**3. Insert Things into** **`SolidDataset`**

Use [setThing](https://inrupt.github.io/solid-client-js/modules/thing_thing.html#setthing) to create a SolidDataset that contains the Things.

For example:

```javascript
// Update SolidDataset with the book1 and book2 Things.
// Note: solid-client functions do not modify objects passed in as arguments. 
// Instead the functions return new objects with the modifications.
courseSolidDataset = setThing(courseSolidDataset, newBookThing1);
courseSolidDataset = setThing(courseSolidDataset, newBookThing2);
```

The **`solid-client`** library’s functions (such as the various add/set/remove functions) do not modify the objects that are passed in as arguments. Instead, the library’s functions return a new object with the requested changes, and do not modify the passed-in objects.

**4. Save the `SolidDataset`**

Use [saveSolidDataSetAt](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#savesoliddatasetat) to save the SolidDataset to the Pod and return the SolidDataset. For example:

```javascript
  // Save the SolidDataset at the specified URL.
  // The function returns a SolidDataset that reflects your sent data
  const savedSolidDataset = await saveSolidDatasetAt(
    "https://pod.example.com/universityZ/fall2021/courses/Writing101",
    courseSolidDataset,
    { fetch: fetch }             // fetch from authenticated Session
  );
```

When using [saveSolidDataSetAt](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#savesoliddatasetat), the Solid server creates any intermediary Containers as needed.

#### Modify an Existing SolidDataset

**0. Import**

For the examples, import the following objects from the client libraries:

```javascript
import { login, handleIncomingRedirect, getDefaultSession, fetch } from "@inrupt/solid-client-authn-browser";

import {
  buildThing,
  createThing,
  getSolidDataset,
  getThing,  
  setStringNoLocale,
  setThing,
  saveSolidDatasetAt,
} from "@inrupt/solid-client";

```

**1. Retrieve the `SolidDataset`.**

You can use [getSolidDataset](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#getsoliddataset) to fetch an existing SolidDataset. For example:

```javascript
// Get the SolidDataset for Writing 101 at the specified URL
const resourceURL = "https://pod.example.com/universityZ/fall2021/courses/Writing101";
let courseSolidDataset = await getSolidDataset(
  resourceURL,
  { fetch: fetch }
);
```

**2. Get the `Thing` to Modify**

{% tabs %}
{% tab title="Fluent API" %}
First, you can use [getThing](https://inrupt.github.io/solid-client-js/modules/thing_thing.html#getthing) to get an existing Thing from the fetched SolidDataset. Pass the function the Thing’s URL. Typically, a Thing’s URL is **`<SolidDatasetURL>#<name>`**,

Then, you can use the [buildThing()](https://inrupt.github.io/solid-client-js/modules/thing_build.html#buildthing) and the [ThingBuilder functions](https://inrupt.github.io/solid-client-js/modules/thing_build.html#buildthing) (Fluent API) to generate the Thing with the data modifications.

For example:

```javascript
// Get the "book1" Thing from the retrieved SolidDataset; the Thing's URL will be the SolidDatatset URL with hash #book1.
// Use Fluent API to add a new property to the Thing, and 
// build a new Thing with the added property.
// Note: solid-client functions do not modify objects passed in as arguments. 
// Instead the functions return new objects with the modifications.
let book1Thing = getThing(courseSolidDataset, `${resourceURL}#book1`);
book1Thing = buildThing(book1Thing)
  .addInteger("https://schema.org/numberOfPages", 30)
  .build();
```

The example gets the **`book1`** Thing, and using the Fluent API, adds the following Thing property and value:

| `"https://schema.org/numberOfPages"` | `30` |
| ------------------------------------ | ---- |

{% hint style="info" %}
The **`solid-client`** library’s functions (such as the various add/set/remove functions) do not modify the objects that are passed in as arguments. Instead, the library’s functions return a new object with the requested changes, and do not modify the passed-in objects.
{% endhint %}
{% endtab %}

{% tab title="Non-Fluent Alternative" %}
First, you can use [getThing](https://inrupt.github.io/solid-client-js/modules/thing_thing.html#getthing) to get an existing Thing from the fetched SolidDataset. Pass the function the Thing’s URL. Typically, a Thing’s URL is **`<SolidDatasetURL>#<name>`**.

Then, use the series of add/set/remove functions to modify the Thing with the data modifications:

* [thing/add](https://inrupt.github.io/solid-client-js/modules/thing_add.html) functions that return a new Thing modified with new data added,
* [thing/set](https://inrupt.github.io/solid-client-js/modules/thing_set.html) functions that return a new Thing modified with the specified data, and
* [thing/remove](https://inrupt.github.io/solid-client-js/modules/thing_remove.html) functions that return a new Thing modified with specified data removed.

For example:

```javascript
// Get the "book2" Thing from the retrieved SolidDataset; the Thing's URL will be the SolidDatatset URL with hash #book2.
// Use setStringNoLocale to return a new Thing with the name property set to "ZYX987 of Example Poesy"
// Note: solid-client functions do not modify objects passed in as arguments. 
// Instead the functions return new objects with the modifications.
let book2Thing = getThing(courseSolidDataset, `${resourceURL}#book2`);
book2Thing = setStringNoLocale(book2Thing, SCHEMA_INRUPT.name, "ZYX987 of Example Poesy");
```

The example gets the `book2` Thing, and uses a [setStringNoLocale](https://inrupt.github.io/solid-client-js/modules/thing_set.html#setstringnolocale) function to update the Thing’s `SCHEMA_INRUPT.name` value to `"ZYX987 of Example Poesy"`.

{% hint style="info" %}
The **`solid-client`** library’s functions (such as the various add/set/remove functions) do not modify the objects that are passed in as arguments. Instead, the library’s functions return a new object with the requested changes, and do not modify the passed-in objects.
{% endhint %}
{% endtab %}
{% endtabs %}

**3. Create a New `Thing` to Add**

In addition to being able to modify existing Things in the SolidDataset, you can add a new **`Thing`** to the existing SolidDataset.

First, use [createThing](https://inrupt.github.io/solid-client-js/modules/thing_thing.html#creatething) to create a new Thing (i.e., data entity) for the SolidDataset. Pass the function a **`name`** for the Thing. Typically, a Thing’s URL is the SolidDataset’s URL appended with a **`#`** hash fragment. Upon save, the specified **`name`** becomes the **`#`** hash fragment; i.e., the Thing’s URL becomes **`<SolidDatasetURL>#<name>`**,

Then, you can use the [buildThing()](https://inrupt.github.io/solid-client-js/modules/thing_build.html#buildthing) and the [ThingBuilder functions](https://inrupt.github.io/solid-client-js/modules/thing_build.html#buildthing) (Fluent API) to generate the Thing with the data modifications.

The following example creates a new Thing with the name **`location`** (which will be part of its URL), and uses the Fluent API to add various properties and values.

```javascript
// Create a new Thing for "location"; Thing's URL will include the hash #location.
// Use Fluent API to add properties to the new Thing, and 
// build a new Thing with the properties.
// Note: solid-client functions do not modify objects passed in as arguments. 
// Instead the functions return new objects with the modifications.
const locationThing = buildThing(createThing({ name: "location" }))
  .addStringNoLocale("https://schema.org/name", "Sample Lecture Hall")
  .addUrl(RDF.type, "https://schema.org/Place")
  .build();
```

**4. Update the `SolidDataset` with Modified/New Things**

Use [setThing](https://inrupt.github.io/solid-client-js/modules/thing_thing.html#setthing) to create a SolidDataset with Thing modifications.

For example:

```javascript
// Update SolidDataset with the book1 and book2 and location Things.
// Note: solid-client functions do not modify objects passed in as arguments. 
// Instead the functions return new objects with the modifications.
courseSolidDataset = setThing(courseSolidDataset, book1Thing);
courseSolidDataset = setThing(courseSolidDataset, book2Thing);
courseSolidDataset = setThing(courseSolidDataset, locationThing);
```

In the returned SolidDataset, the **`book1`** and **`book2`** Things have been overwritten and the new **`location`** Thing has been added.

The **`solid-client`** library’s functions (such as the various add/set/remove functions) do not modify the objects that are passed in as arguments. Instead, the library’s functions return a new object with the requested changes, and do not modify the passed-in objects.

**5. Save the `SolidDataset`**

Use [saveSolidDataSetAt](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#savesoliddatasetat) to save the SolidDataset to the Pod and return the SolidDataset. For example:

```javascript
// Save the SolidDataset at the specified URL.
// The function returns a SolidDataset that reflects your sent data
const savedSolidDataset = await saveSolidDatasetAt(
  resourceURL,
  courseSolidDataset,
  { fetch: fetch }             // fetch from authenticated Session
);
```

A SolidDataset keeps track of the data changes compared to the data in the Pod. For **`set`** operations on existing properties, the changelog tracks both the old value and new values of the property being modified.

The save operation applies the changes from the changelog to the current SolidDataset. If the old value specified in the changelog does not correspond to the value currently in the Pod, the save operation will **error**.

#### Save Considerations

#### **Create vs. Update Operations**

[saveSolidDatasetAt](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#savesoliddatasetat) can be used to create new Resource or update an existing Resource in a Pod. If the passed-in Resource has an identifying URL, the function performs an update. If the passed-in Resource has no identifying URL, the function attempts to create a new Resource.

When creating a new Resource, the function adds the precondition that the Resource must not already exist. If the Resource already exists, the server will return a [412 Precondition Failed](/sdk/javascript-sdk/error-codes#id-412-precondition-failed) error.

**Changelog Considerations**

A SolidDataset keeps track of the data changes compared to the data in the Pod. The save operation applies the changes from the changelog to the current SolidDataset:

* For **`add`** operations, the changelog keeps track of additions.
* For **`remove`** operations, the changelog keeps track of the deletions.
* For **`set`** operations, the changelog keeps track of the old and new values. As such, if the old value specified in the changelog does not correspond to the value currently in the Pod, the save operation will **error** with a [409 Conflict](/sdk/javascript-sdk/error-codes#id-409-conflict).

In the [Modify an Existing SolidDataset](#modify-an-existing-soliddataset) example, the locally modified **`courseSolidDataset`** has a changelog that reflects:

* addition of a number of pages property for **`book1`**
* modification of the name property field (from old value to new value) for **`book2`**.
* addition of a new Thing **`location`**

**Scenario 1:** If another operation has modified the name property of **`book2`** after you have retrieved **`courseSolidDataset`** but before you save the locally modified **`courseSolidDataset`**, the save operation errors with [409 Conflict](/sdk/javascript-sdk/error-codes#id-409-conflict).

**Scenario 2:** If another operation has separately added/modified/removed a property from **`book1`** after you have retrieved **`courseSolidDataset`** but before you the locally modified **`courseSolidDataset`**, the save operation succeeds, but the returned SolidDataset only reflects your changes. To make sure the SolidDataset accurately reflects all changes to date, call [getSolidDataset](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#getsoliddataset) again after saving the data.

**Returned SolidDataset**

[saveSolidDatasetAt](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#savesoliddatasetat) returns a SolidDataset that reflects only the sent data. That means, if another process made separate non-conflicting modifications to the SolidDataset after you retrieved the SolidDataset but before you successfully saved, the returned SolidDataset only reflects your changes.

To make sure the SolidDataset reflects not just your changes, call [getSolidDataset](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#getsoliddataset) again after saving the data.

### Delete Data

#### Delete an Existing SolidDataset

{% hint style="info" %}
To delete a SolidDataset, you must have the `Write` access to the SolidDataset and its parent Container.
{% endhint %}

#### Delete API

To delete a SolidDataset from a Pod, you can use [deleteSolidDataset](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#deletesoliddataset) to remove the SolidDataset at the specified URL. To use, pass the function the following parameters:

<table><thead><tr><th width="197.72265625">Parameter</th><th>Value</th></tr></thead><tbody><tr><td>SolidDataset URL</td><td>The URL of the SolidDataset to delete.</td></tr><tr><td>Options object</td><td><p>An object that includes the following option:</p><p><strong><code>{ fetch: &#x3C;fetch func> }</code></strong></p><p><br><strong><code>fetch</code></strong> function from an authenticated session if deleting a restricted Resource (i.e., the general public cannot delete the specified SolidDataset).</p><p>Optional if the general public can delete the Resource.</p></td></tr></tbody></table>

The following example uses [deleteSolidDataset](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#deletesoliddataset) to delete the specified SolidDataset.

```javascript
import { login, handleIncomingRedirect, getDefaultSession, fetch } from "@inrupt/solid-client-authn-browser";

import {
  deleteSolidDataset
} from "@inrupt/solid-client";


// ... Various logic, including login logic, omitted for brevity.

try {
  await deleteSolidDataset(
    "https://pod.example.com/universityZ/fall2021/courses/Writing101", 
    { fetch: fetch }           // fetch function from authenticated session
  );
} catch (error) {
  //...
}
```


# Access Requests and Grants

Inrupt’s Enterprise Solid Server (ESS) provides support for Access Request and Grants. With Access Requests and Grants:

* An [agent](/reference/glossary#agent) can request access to [Resources](/reference/glossary#resource) hosted on a [Pod](/reference/glossary#pods). This Access Request includes the specific [access mode](/reference/glossary#access-modes) (e.g., read, write, append) being requested, the Resources to access, the Purpose for which the data will be used, and other optional fields.
* The owner of the requested Resources (i.e., individuals with Control access to the requested Resources) can review the Access Request and either approve the Access Request, resulting in an Access Grant, or deny the Access Request, resulting in an Access Denial.
* If the requesting agent has an Access Grant, the requesting agent can exchange the Access Grant for an access token in order to access the Resources.

{% hint style="warning" %}
**Important**

* An Access Request that specifies a [Container](/reference/glossary#container) also applies to the Container’s descendants unless explicitly specified otherwise in the Request with an **`inherit: false`**.
* An Access Grant that specifies a [Container](/reference/glossary#container) also applies to the Container’s descendants unless explicitly specified otherwise in the approved Access Grant with an **`inherit: false`**.

To set the **`inherit`** field in Access Requests/Access Grants, **`@inrupt/solid-client-access-grants-js`** adds an **`inherit: <boolean>`** option to [issueAccessRequest](https://inrupt.github.io/solid-client-access-grants-js/functions/index.issueAccessRequest.html) and [approveAccessRequest](https://inrupt.github.io/solid-client-access-grants-js/functions/index.approveAccessRequest.html).
{% endhint %}

### Inrupt Client Library

To support ESS’ [Access Request and Grants feature](/security/authorization/access-requests-grants), Inrupt provides the **`@inrupt/solid-client-access-grants`** library. This library contains APIs to manage Access Requests and Grants issued by ESS.

```javascript
npm install @inrupt/solid-client-access-grants
```

### Usage Scenario

In the following usage scenario, a user wants to print some photos that are stored in their Pod. The user visits the ExamplePrinter’s web application, which provides photo printing services. When the ExamplePrinter’s web application asks for the photos to print, the user enters the URLs of the photos. To continue, the ExamplePrinter’s website asks for access to read the photos.

For example, assume the user **`snoringsue`** with the WebID (**`https://id.example.com/snoringsue`**) is on ExamplePrinter’s web application to print the following photos:

* **`"https://storage.example.com/someContainer/myphotos/apples.jpg"`**
* **`"https://storage.example.com/someContainer/myphotos/persimmons.jpg"`**
* **`"https://storage.example.com/someContainer/myphotos/grapes.jpg"`**.

The following diagram gives an overview of the flow (in the example, the Access Request and Grants are serialized as VCs)

<figure><img src="/files/97yKUk1SL2aeWz2mOlTV" alt=""><figcaption></figcaption></figure>

Sequence diagram of the Access Request flow where `snoringsue` is requested access to her photos by ExamplePrinter and approves the Request.

### Next Steps

| [Manage Access Requests](/sdk/javascript-sdk/access-requests-and-grants/manage-access-requests)                                       | To make Access Requests. If access is granted, use the Access Grants to access the resource (such as the ExamplePrinter application in above diagram).                |
| ------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Manage Access Grants](/sdk/javascript-sdk/access-requests-and-grants/manage-access-grants)                                           | To approve or deny Access Requests (such as the Access Management application in above diagram).                                                                      |
| [Use Access Grants to Access Resources](/sdk/javascript-sdk/access-requests-and-grants/use-access-grants-to-access-resources)         | To query for Access Credentials based on a set of filters.                                                                                                            |
| [Inspect Access Requests and Access Grants](/sdk/javascript-sdk/access-requests-and-grants/inspect-access-requests-and-access-grants) | To get values out of Access Requests and Access Grants (such as how the ExamplePrinter application in the above diagram determines which resources it has access to). |


# Manage Access Requests

This page details how an agent can use Inrupt’s [solid-client-access-grants library](https://inrupt.github.io/solid-client-access-grants-js/) to request access to Pod Resources. This Access Request includes the specific access mode requested (e.g., read, write, append), the resources to access, etc.

{% hint style="info" %}
**Access Requests and Grants**

The following Inrupt products are available to support Access Requests and Grants:

* **`solid-client-access-grants`** library for managing Access Requests and Grants
* Inrupt’s Enterprise Solid Server provides support for [Access Requests and Grants](/security/authorization/access-requests-grants). ESS serializes the Access Requests and Grants as Verifiable Credentials.
* Inrupt’s [Authorization Management Component](/security/authorization/access-requests-grants#authorization-management-component-amc) supports Access Request management.
  {% endhint %}

### Requesting Access to Containers

Access Request and Grants for a [Container](/reference/glossary#container) applies both to the Container and its descendants unless explicitly specified otherwise in the request with an **`inherit: false`**.

To specify the **inherit** field in the request, **`@inrupt/solid-client-access-grants-js`** adds an **`inherit`** option to [issueAccessRequest](https://inrupt.github.io/solid-client-access-grants-js/functions/index.issueAccessRequest.html). Set **`inherit: false`** to create a request for the Container only.

### solid-client-access-grants API

Inrupt’s [solid-client-access-grants library](https://inrupt.github.io/solid-client-access-grants-js/) provides various functions for issuing Access Requests and exercising Access Grants; for example:

<table><thead><tr><th width="266.55078125">Key</th><th>Description</th></tr></thead><tbody><tr><td><a href="https://inrupt.github.io/solid-client-access-grants-js/functions/index.issueAccessRequest.html"><code>issueAccessRequest</code></a></td><td><p>Creates an Access Request, serialized as a signed <a href="/pages/7PKZaxnnUDGdJ8gPDOqL#verifiable-credential">Verifiable Credential</a>.</p><p>Server-side code can use the function to create Access Requests.</p></td></tr><tr><td><a href="https://inrupt.github.io/solid-client-access-grants-js/functions/index.redirectToAccessManagementUi.html">redirectToAccessManagementUi</a></td><td><p>Redirects the resource owners to their <a href="/pages/7PKZaxnnUDGdJ8gPDOqL#access-management-application">access management application</a>.</p><p>The requestor code can use the function to redirect the resource owner to the access management application where the owner can grant or deny access. If called server-side, a <strong><code>redirectCallback</code></strong> must be passed in the options for the library to handle redirection depending on the Web framework.</p><p>The access management application returns the resource owners to the URL specified in the function call.</p></td></tr><tr><td><a href="https://inrupt.github.io/solid-client-access-grants-js/functions/index.getSolidDataset.html">getSolidDataset</a></td><td>Uses the Access Grants to retrieve a SolidDataset.</td></tr><tr><td><a href="https://inrupt.github.io/solid-client-access-grants-js/functions/index.saveSolidDatasetAt.html">saveSolidDatasetAt</a></td><td>Uses the Access Grants to save a SolidDataset.</td></tr><tr><td><a href="https://inrupt.github.io/solid-client-access-grants-js/functions/index.getFile.html">getFile</a></td><td>Uses the Access Grant to retrieve a file.</td></tr></tbody></table>

### Requesting Access

The following example implements the code for the access requestor (i.e., ExamplePrinter) in the example introduced in [Access Requests and Grants](/sdk/javascript-sdk/access-requests-and-grants).

To start the Access Request flow:

1. ExamplePrinter’s client-side code calls on its server-side application to create an Access Request.
2. The server-side application can use [issueAccessRequest](https://inrupt.github.io/solid-client-access-grants-js/functions/index.issueAccessRequest.html) to create an Access Request for the photos belonging to the **`resourceOwner`** (in this example, **`"https://id.example.com/snoringsue"`**);

   ```javascript
   async function requestAccessToPhotos(photosToPrint, resourceOwner){

     // ExamplePrinter sets the requested access (if granted) to expire in 5 minutes.
     let accessExpiration = new Date( Date.now() +  5 * 60000 );

     // Call `issueAccessRequest` to create an Access Request
     //
     const requestVC = await issueAccessRequest(
         {
            "access":  { read: true },
            "resources": photosToPrint,   // Array of URLs
            "resourceOwner": resourceOwner,
            "expirationDate": accessExpiration,
            "purpose": [ "https://example.com/purposes#print" ]
         },
         { fetch : session.fetch } // From the requestor's (i.e., ExamplePrinter's) authenticated session
     );
   }
   ```
3. After having received the Access Request, the requestor calls [redirectToAccessManagementUi](https://inrupt.github.io/solid-client-access-grants-js/functions/index.redirectToAccessManagementUi.html) to redirect the user to the user’s [access management application](/reference/glossary#access-management-application). Pass to the function:

   * the **`id`** of the request (in this example, the **`id`** of the Access Request VC) and
   * the URL to which the access management application should return the user after the Access Request has been granted or denied.

   The following example is for a client-side call, and it includes the optional **`fallbackAccessManagementUi`** option. The function first tries to discover the resource owner’s preferred access management application; the fallback URL is used only if none can be discovered. There is no built-in default, so if no application is discovered and no fallback is supplied, no redirect can be performed.

   ```javascript
   // Call `redirectToAccessManagementUi` to redirect to
   // the user's access management app, falling back to the URL supplied in
   // `fallbackAccessManagementUi` if none can be discovered.
   // The user logs into the access management application and decides to grant or deny the request.

   redirectToAccessManagementUi(
      requestVC.id,
      "https://www.example.net/exampleprinter/returnwithgrantVC/",
      {
        fallbackAccessManagementUi: "https://access.example.com/accessRequest",
        fetch : session.fetch // From the requestor's (i.e., ExamplePrinter's) authenticated session
      }
   );
   ```

{% hint style="info" %}
**Tip**

If the call is made on the server-side, also include the **`redirectCallback`** option.
{% endhint %}

4. At this point, the user is redirected away from the requesting app (e.g., ExamplePrinter app) to the user’s Access Management app, where they will either approve or deny the Access Request. The Access Management app will then redirect the user back to the URL provided in the call to [redirectToAccessManagementUi](https://inrupt.github.io/solid-client-access-grants-js/functions/index.redirectToAccessManagementUi.html).
5. Upon redirect, the URL will include add a query parameter with the id of the approved Access Grant. The requesting app can use [getAccessGrantFromRedirectUrl](https://inrupt.github.io/solid-client-access-grants-js/functions/index.getAccessGrantFromRedirectUrl.html) to get the Access Grant.

With an approved Access Grant, the requestor can access the resource. See [Use Access Grants to Access Resources](/sdk/javascript-sdk/access-requests-and-grants/use-access-grants-to-access-resources).

### Adding custom fields to an Access Request

Access Requests are based on a use-case-agnostic data model, and capture information about access to resources. However, it is also useful to be able to tie Access Requests and Access Grants into business-specific processes, which requires the core data model to have extension points.

**`@inrupt/solid-client-access-grants`** supports adding custom fields to an Access Request. **`issueAccessRequest`** has a new **`customFields`** option, accepting a set of **`CustomField`**. A **`CustomField`** is a key/value entry where the key MUST be a URL, and the value a literal (**`string`**, **`boolean`** or **`number`**). The provided values are embedded within the issued Access Request.

If an incorrect **`CustomField`** value is provided, **`AccessGrantError`** is thrown. Invalid **`CustomField`** definitions include not using a **`URL`** as a key, or not using a literal as a value. The TypeScript typing is provided as a guideline.

```javascript
async function requestAccessToPhotos(photosToPrint, resourceOwner){

   // requestVC will have the provided custom fields in addition
   // to the regular fields for Access Requests.
   const requestVC = await issueAccessRequest(
      {
         "access":  { read: true },
         "resources": photosToPrint,
         "resourceOwner": resourceOwner,
         "purpose": [ "https://example.com/purposes#print" ]
      },
      {
         fetch : session.fetch,
         // ExamplePrinter can add custom fields that are relevant
         // to its own application into the Access Request.
         customFields: new Set([{
            key: new URL("https://example.com/printer/orderId"),
            value: "my-order-id"
         }]),
      }
   );
}
```

### Querying for Access Requests

In **`@inrupt/solid-client-access-grants@v3.2.0`**, the application can use the **`query`** function to query for active (i.e., current and not expired) Access Requests made to a user. To use **`query`** for Access Requests, you can pass in a **`AccessRequestFilter`** object that specifies the query filter values (i.e., a combination of the resource, creator, recipient, purpose, and type).

The **`AccessRequestFilter`** object has the following fields:

<table><thead><tr><th width="163.56640625">Key</th><th>Description</th></tr></thead><tbody><tr><td><strong><code>type</code></strong></td><td>The Access Credential type (in this case, <strong><code>SolidAccessRequest</code></strong>).</td></tr><tr><td><strong><code>status</code></strong></td><td><p>Optional. Include a credential status in the query object. The following values are supported for Access Requests:</p><ul><li><strong><code>Pending</code></strong> returns all Access Requests that have neither been granted, denied or canceled.</li><li><strong><code>Granted</code></strong> returns all Access Requests that have been granted by the resource owner.</li><li><strong><code>Denied</code></strong> returns all Access Requests that have been denied by the resource owner.</li><li><strong><code>Canceled</code></strong> returns all Access Requests that have been canceled by the requesting agent.</li></ul></td></tr><tr><td><strong><code>fromAgent</code></strong></td><td>Optional. Include the creator of the Access Request in the query filter. This is the requestor. In the example, the creator value is the ExamplePrinter’s WebID <strong><code>https://id.example.com/examplePrinter</code></strong>.</td></tr><tr><td><strong><code>toAgent</code></strong></td><td>Optional. Include the recipient of the Access Request in the query filter. This is the resource owner.</td></tr><tr><td><strong><code>resource</code></strong></td><td>Optional. Include the resource in the query object. Use this filter to return Access Requests bound to a specific resource.</td></tr><tr><td><strong><code>purpose</code></strong></td><td>Optional. Include a purpose in the query object. Use this filter to return Access Requests bound to a specific purpose.</td></tr><tr><td><strong><code>issuedWithin</code></strong></td><td><p>Optional. Include a time constraint on the issuance date in the query object. All matched credentials will have been issued within the provided duration value. Certain time constraints are available for use with this method, including:</p><ul><li><strong><code>P1D</code></strong> One day (<strong><code>DURATION.ONE_DAY</code></strong>)</li><li><strong><code>P7D</code></strong> Seven days (<strong><code>DURATION.ONE_WEEK</code></strong>)</li><li><strong><code>P1M</code></strong> One month (<strong><code>DURATION.ONE_MONTH</code></strong>)</li><li><strong><code>P3M</code></strong> Three months (<strong><code>DURATION.THREE_MONTH</code></strong>)</li></ul></td></tr><tr><td><strong><code>revokedWithin</code></strong></td><td><p>Optional. Include a time constraint on the revocation date in the query object. All matched credentials will have been revoked or canceled within the provided duration value. Certain time constraints are available for use with this method, including:</p><ul><li><strong><code>P1D</code></strong> One day (<strong><code>DURATION.ONE_DAY</code></strong>)</li><li><strong><code>P7D</code></strong> Seven days (<strong><code>DURATION.ONE_WEEK</code></strong>)</li><li><strong><code>P1M</code></strong> One month (<strong><code>DURATION.ONE_MONTH</code></strong>)</li><li><strong><code>P3M</code></strong> Three months (<strong><code>DURATION.THREE_MONTH</code></strong>)</li></ul></td></tr></tbody></table>

The result is paginated, so the agent may need to make multiple calls to the **`query`** function to get all of the Access Requests matching the provided filter.

The following example queries for active Access Requests made by ExamplePrinter for a given resource.

```javascript
const page1 = await query({
   type: "SolidAccessRequest",
   status: "Pending",
   fromAgent: new URL("https://id.example.com/ExamplePrinter"),
}, {
   fetch: session.fetch,
   queryEndpoint: new URL("https://vc.example.org/query"),
});
if (page1.next !== undefined) {
   const page2 = await query(page1.next, {
      fetch: session.fetch,
      queryEndpoint: new URL("https://vc.example.org/query"),
   });
}
```

**`paginatedQuery`** is a utility provided for iterating through the result pages:

```javascript
const pages = paginatedQuery({
    type: "SolidAccessRequest",
    status: "Pending",
    fromAgent: new URL("https://id.example.com/ExamplePrinter"),
 }, {
    fetch: session.fetch,
    queryEndpoint: new URL("https://vc.example.org/query"),
 });
let i = 0;
for await (const page of pages) {
    console.log(`Page ${i} has ${page.items.length} items.`)
    i += 1;
}
```


# Manage Access Grants

Upon receipt of an Access Request, resource owners can use an access management application to approve or deny the Access Request:

* For an approved request, Inrupt’s Enterprise Solid Server (ESS) creates an Access Grant.
* For a denied request, ESS creates an Access Denial.

{% hint style="info" %}
**Access Requests and Grants**

The following Inrupt products are available to support Access Requests and Grants:

* **`solid-client-access-grants`** library for managing Access Requests and Grants
* Inrupt’s Enterprise Solid Server provides support for [Access Requests and Grants](/security/authorization/access-requests-grants). ESS serializes the Access Requests and Grants as Verifiable Credentials.
* Inrupt’s [Authorization Management Component](/security/authorization/access-requests-grants#authorization-management-component-amc) supports Access Request management.
  {% endhint %}

### Access Grants for Containers

An Access Grant for a [Container](/reference/glossary#container) applies both to the Container and its descendants unless explicitly specified otherwise with an **`inherit: false`**.

**`@inrupt/solid-client-access-grants-js`** adds an **`inherit`** option to [approveAccessRequest](https://inrupt.github.io/solid-client-access-grants-js/functions/index.approveAccessRequest.html). For example:

* You can include **`inherit: false`** as an override option to create an approved Access Grant for the Container only, regardless of the specification in the Access Request.
* You can include **`inherit: true`** as an override option to create an approved Access Grant for both the Container and its descendants, regardless of the specification in the Access Request.

### solid-client-access-grants API

Inrupt’s [solid-client-access-grants library](https://inrupt.github.io/solid-client-access-grants-js/) provides various functions for approving or denying Access Requests; for example:

<table data-header-hidden><thead><tr><th width="224.0390625">Key</th><th>Description</th></tr></thead><tbody><tr><td><a href="https://inrupt.github.io/solid-client-access-grants-js/functions/index.approveAccessRequest.html">approveAccessRequest</a></td><td><p>Approves the request and returns an approved Access Grant, serialized as a signed <a href="/pages/7PKZaxnnUDGdJ8gPDOqL#verifiable-credential">Verifiable Credential</a> (VC). The Access Grants may be used to get access to specified resources.</p><p>A server-side code can use the function to create the Grant.</p><div data-gb-custom-block data-tag="hint" data-style="info" class="hint hint-info"><p>ESS uses <a href="/pages/W73AOXO2MwUXLiukUUab">ACP policy</a> to enables the use of Access Grants for a resource. The <a href="https://inrupt.github.io/solid-client-access-grants-js/functions/index.approveAccessRequest.html">approveAccessRequest</a> function, by default, creates an ACP policy that enables the use of Access Grant.</p><p>Pods are created with default policies that enable the use of Access Grants.</p><p>When using <a href="https://inrupt.github.io/solid-client-access-grants-js/functions/index.approveAccessRequest.html">approveAccessRequest</a>, you can specify <strong><code>updateAcr: false</code></strong> to prevent the function from creating a separate ACP policy that enables the use of Access Grants.</p></div></td></tr><tr><td><a href="https://inrupt.github.io/solid-client-access-grants-js/functions/index.denyAccessRequest.html">denyAccessRequest</a></td><td><p>Denies the request and returns an Access Denial, serialized as a signed <a href="/pages/7PKZaxnnUDGdJ8gPDOqL#verifiable-credential">Verifiable Credential</a>.</p><p>A server-side code can use the function to deny the request.</p></td></tr></tbody></table>

### Granting Access

The following implements the role of the [access management app](/reference/glossary#access-management-application) in the example introduced in Access Requests and Grants. The role of the access management app is to act as a trusted third-party in the Access Request flow.

The access requestor (e.g., ExamplePrinter) sends the Resource Owner (e.g., **`snoringsue`**) to the Resource Owner’s access management app.

{% hint style="info" %}
When sending resource owner to the Access Management app, the requestor adds the following query parameters:

* **`id`** of the Access Request (in the example, the **`id`** of the Access Request VC), and
* **`redirectUrl`**, the URL where the requestor expects the Access Management app to redirect the Resource Owner after the request has been granted or denied.
  {% endhint %}

In order to approve or deny an Access Request:

1. The Resource Owner should log in to the access management app, if not already.
2. The Access Management app displays the Access Request (found in the **`requestVc`** parameter) to the Resource Owner.
   1. If the Access Request id is known, an access management app can also use [getAccessRequest](https://inrupt.github.io/solid-client-access-grants-js/functions/index.getAccessRequest.html) method.
3. The Resource Owner approves or denies the Access Request.

   * If the Resource Owner approves the request, the Access Management app uses [approveAccessRequest](https://inrupt.github.io/solid-client-access-grants-js/functions/index.approveAccessRequest.html) to return the **`id`** of the Access Grant VC. When calling the function, the Access Management application can also include an optional modifications object (such as to specify a subset of the requested resources or permissions or set the **`inherit`** flag).

     ```javascript
     async function getApprovedGrantVC(accessRequestToApprove, resourceOwnerSession){

       // Call `approveAccessRequest` to acquire a Verifiable Credential
       // for the approved Access Grant
       const accessGrant = await approveAccessRequest(
         accessRequestToApprove,
         undefined,  // Optional modifications
         {
           updateAcr: false,
           fetch: resourceOwnerSession.fetch, // From the resource owner's (i.e., snoringsue's) authenticated session
         }
       );
     }
     ```
   * If the Resource Owner denies the request, the Access Management app uses [denyAccessRequest](https://inrupt.github.io/solid-client-access-grants-js/functions/index.denyAccessRequest.html) to return the **`id`** of the Access Denial VC.

   ```javascript
    async function geAccessDenialVC(accessRequestToDeny, resourceOwnerSession){

      // Call `denyAccessRequest`** to create an Access Denial

      const accessDenial = await denyAccessRequest(
        accessRequestToDeny,
        {
          fetch: resourceOwnerSession.fetch, // From the resource owner's (i.e., snoringsue's) authenticated session
        }
      );
    }
   ```
4. Once the user acts on a request, the Access Management app redirects the user to the requesting app using the **`redirectUrl`** parameter received earlier. The **`id`** of the Access Grant/Denial is added to the redirect URL. The requesting app can use [getAccessGrantFromRedirectUrl](https://inrupt.github.io/solid-client-access-grants-js/functions/index.getAccessGrantFromRedirectUrl.html) to get the Access Grant.

   The requesting app can pass the **`id`** of the VC to [getAccessGrant](https://inrupt.github.io/solid-client-access-grants-js/functions/index.getAccessGrant.html) to retrieve the Access Grant.

### Adding custom fields to an Access Grant

Similar to Access Requests (see [Use Access Grants to Access Resources](/sdk/javascript-sdk/access-requests-and-grants/use-access-grants-to-access-resources)), it can be useful to add application-specific information into an Access Grant.

**`@inrupt/solid-client-access-grants`** supports adding custom fields to an Access Grant. **`approveAccessRequest`** has a new **`CustomFields`** entry in its **`requestOverride`** argument, accepting a set of **`CustomField`**. A **`CustomField`** is a key/value entry where the key MUST be a URL, and the value a literal (**`string`**, **`boolean`** or **`number`**). The provided values are embedded within the issued Access Grant. The custom fields from the Access Request being approved are also propagated to the issued Access Grant, if not overridden by one of the provide&#x64;**`CustomField`**&#x6F;verrides. A **`CustomField`** override which value is **`undefined`** will result in the custom field not being present in the Access Grant, even if it was part of the Access Request.

If an incorrect **`CustomField`** value is provided, **`AccessGrantError`** is thrown. Invalid **`CustomField`** definitions include not using a **`URL`** as a key, or not using a literal as a value. The TypeScript typing is provided as a guideline.

```javascript
async function getApprovedGrantVC(accessRequestToApprove, resourceOwnerSession){

  // Call `approveAccessRequest` to acquire a Verifiable Credential
  // for the approved Access Grant
  const accessGrant = await approveAccessRequest(
    accessRequestToApprove,
    {
      // This adds a custom field to the issued Access Grant.
      // Custom fields from the Access Request will also be
      // present in the issued Grant.
      customFields: new Set([{
          key: new URL("https://example.com/printer/confirmationId"),
          value: "my-confirmation-id"
      }]),
    },
    {
      updateAcr: false, 
      fetch: resourceOwnerSession.fetch
    }
  );
}
```

### Querying for Access Grants

In **`@inrupt/solid-client-access-grants@v3.2.0`**, the application can use the **`query`** function to query for active (i.e., current and not expired) Access Grants. To use **`query`** for Access Grants, you can pass in a **`AccessGrantFilter`** object that specifies the query filter values (i.e., a combination of the resource, creator, recipient, purpose, and type).

The **`AccessGrantFilter`** object has the following fields:

<table><thead><tr><th width="151">Key</th><th>Description</th></tr></thead><tbody><tr><td><strong><code>type</code></strong></td><td>The Access Credential type (in this case, <strong><code>SolidAccessGrant</code></strong>).</td></tr><tr><td><strong><code>status</code></strong></td><td><p>Optional. Include a credential status in the query object.</p><p>The following values are supported for Access Grants:</p><ul><li><strong><code>Active</code></strong> returns all active access grants: those that have not expired and have not been revoked.</li><li><strong><code>Expired</code></strong> returns all access grants that have expired.</li><li><strong><code>Revoked</code></strong> returns all access grants that have been revoked by the resource owner.</li></ul></td></tr><tr><td><strong><code>fromAgent</code></strong></td><td>Optional. Include the creator of the Access Grant in the query filter. Resource Owner.</td></tr><tr><td><strong><code>toAgent</code></strong></td><td>Optional. Include the recipient of the Access Grant in the query filter. This is the requestor.</td></tr><tr><td><strong><code>resource</code></strong></td><td>Optional. Include the resource in the query object. Use this filter to return Access Requests bound to a specific resource.</td></tr><tr><td><strong><code>purpose</code></strong></td><td>Optional. Include a purpose in the query object. Use this filter to return Access Requests bound to a specific purpose.</td></tr><tr><td><strong><code>issuedWithin</code></strong></td><td><p>Optional. Include a time constraint on the issuance date in the query object. All matched credentials will have been issued within the provided duration value. Certain time constraints are available for use with this method, including:</p><ul><li><strong><code>P1D</code></strong> One day (<strong><code>DURATION.ONE_DAY</code></strong>)</li><li><strong><code>P7D</code></strong> Seven days (<strong><code>DURATION.ONE_WEEK</code></strong>)</li><li><strong><code>P1M</code></strong> One month (<strong><code>DURATION.ONE_MONTH</code></strong>)</li><li><strong><code>P3M</code></strong> Three months (<strong><code>DURATION.THREE_MONTH</code></strong>)</li></ul></td></tr><tr><td><strong><code>revokedWithin</code></strong></td><td><p>Optional. Include a time constraint on the revocation date in the query object. All matched credentials will have been revoked or canceled within the provided duration value. Certain time constraints are available for use with this method, including:</p><ul><li><strong><code>P1D</code></strong> One day (<strong><code>DURATION.ONE_DAY</code></strong>)</li><li><strong><code>P7D</code></strong> Seven days (<strong><code>DURATION.ONE_WEEK</code></strong>)</li><li><strong><code>P1M</code></strong> One month (<strong><code>DURATION.ONE_MONTH</code></strong>)</li><li><strong><code>P3M</code></strong> Three months (<strong><code>DURATION.THREE_MONTH</code></strong>)</li></ul></td></tr></tbody></table>

The result is paginated, so the agent may need to make multiple calls to the **`query`** function to get all of the Access Requests matching the provided filter.

The following example queries for active access grants, given to ExamplePrinter, that provide access for a specific resource for the purpose of photo printing.

```javascript
const page1 = await query({
  type: "SolidAccessGrant",
  status: "Active",
  resource: new URL("https://storage.example.com/some/resource"),
  purpose: new URL("https://purpose.example.com/PhotoPrinting"),
}, {
  fetch: session.fetch,
  queryEndpoint: new URL("https://vc.example.org/query"),
});
if (page1.next !== undefined) {
  const page2 = await query(page1.next, {
    fetch: session.fetch,
    queryEndpoint: new URL("https://vc.example.org/query"),
  });
}
```

**`paginatedQuery`** is a utility provided for iterating through the result pages:

```javascript
const pages = paginatedQuery({
  type: "SolidAccessGrant",
  status: "Active",
  resource: new URL("https://storage.example.com/some/resource"),
  purpose: new URL("https://purpose.example.com/PhotoPrinting"),
}, {
    fetch: session.fetch,
    queryEndpoint: new URL("https://vc.example.org/query"),
});
let i = 0;
for await (const page of pages) {
  console.log(`Page ${i} has ${page.items.length} items.`)
  i += 1;
}
```


# Use Access Grants to Access Resources

This page details how a server-side application can use Inrupt’s [solid-client-access-grants library](https://inrupt.github.io/solid-client-access-grants-js/) to access Pod Resources with approved Access Grants.

{% hint style="info" %}
**Access Requests and Grants**

The following Inrupt products are available to support Access Requests and Grants:

* **`solid-client-access-grants`** library for managing Access Requests and Grants
* Inrupt’s Enterprise Solid Server provides support for [Access Requests and Grants](/security/authorization/access-requests-grants). ESS serializes the Access Requests and Grants as Verifiable Credentials.
* Inrupt’s [Authorization Management Component](/security/authorization/access-requests-grants#authorization-management-component-amc) supports Access Request management.
  {% endhint %}

### Read/Write APIs

The **`@inrupt/solid-client-access-grants`** library provides various [read and write APIs](https://inrupt.github.io/solid-client-access-grants-js/modules/resource.html) that allows agents with appropriate Access Grants to read/write Pod resources; such as:

| <ul><li><a href="https://inrupt.github.io/solid-client-access-grants-js/functions/index.getSolidDataset.html">getSolidDataset</a></li><li><a href="https://inrupt.github.io/solid-client-access-grants-js/functions/index.saveSolidDatasetAt.html">saveSolidDatasetAt</a></li><li><a href="https://inrupt.github.io/solid-client-access-grants-js/functions/index.saveFileInContainer.html">saveSolidDatasetInContainer</a></li><li><a href="https://inrupt.github.io/solid-client-access-grants-js/functions/index.deleteSolidDataset.html">deleteSolidDataset</a></li></ul> | <ul><li><a href="https://inrupt.github.io/solid-client-access-grants-js/functions/index.getFile.html">getFile</a></li><li><a href="https://inrupt.github.io/solid-client-access-grants-js/functions/index.overwriteFile.html">overwriteFile</a></li><li><a href="https://inrupt.github.io/solid-client-access-grants-js/functions/index.saveFileInContainer.html">saveFileInContainer</a></li><li><a href="https://inrupt.github.io/solid-client-access-grants-js/modules/resource.html#deletefile">deleteFile</a></li></ul> |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

{% hint style="warning" %}
Ensure that you are using the APIs from the **`@inrupt/solid-client-access-grants`** and **not** the **`@inrupt/solid-client`** library.
{% endhint %}

These APIs support the use of **Bearer** tokens (not DPoP tokens).

### Specify Bearer Token Type for Session

Inrupt’s Enterprise Solid Server supports [UMA flow](https://docs.kantarainitiative.org/uma/wg/rec-oauth-uma-grant-2.0.html#protocol-flow-details-sec) to exchange the Access Grants for access tokens. These UMA access tokens can then be used to access the resources.

The **`solid-client-access-grants`**’s [read and write APIs](https://inrupt.github.io/solid-client-access-grants-js/modules/resource.html) handle the UMA exchange and sends the returned UMA access token to access the resource. The library’s read and write APIs support the use of **Bearer** tokens (and not DPoP tokens), and as such, they require the authenticated [Sessions](https://inrupt.github.io/solid-client-authn-js/node/classes/Session.html) to use Bearer tokens (instead of the default DPoP).

To obtain an authenticated Session that uses Bearer tokens, set the [tokenType](https://inrupt.github.io/solid-client-authn-js/node/interfaces/ILoginInputOptions.html#tokentype) for the [Session](https://inrupt.github.io/solid-client-authn-js/node/classes/Session.html) during the [Session.login()](https://inrupt.github.io/solid-client-authn-js/node/classes/Session.html#login).

For example, the following server-side code instantiates a Session and specifies the [tokenType](https://inrupt.github.io/solid-client-authn-js/node/interfaces/ILoginInputOptions.html#tokentype) of **`Bearer`** during login (the default **`tokenType`** is **`DPoP`**):

<pre class="language-javascript"><code class="lang-javascript">import { Session } from "@inrupt/solid-client-authn-node";
//...

const session = new Session();

// ...

if (!session.info.isLoggedIn) {
  await sessionTokenTypeBearer.login({
    clientId: process.env.CLIENT_ID,
    clientSecret: process.env.CLIENT_SECRET,
    oidcIssuer: process.env.IDP,
<strong>    tokenType: "Bearer", // Specify the tokenType option
</strong>  });
}
</code></pre>

The application uses the client credentials received during client registration. For more information on static registration of client applications, see [Authentication Single-User Application](/guides/authentication-in-solid/authentication-single-user-application).

### Retrieve Access Grants

As part of the Access Request/Grant flow, when the Resource Owner grants the Access Request, the **`id`** of the Access Grant (serialized as VC) is sent back to the requesting app as a query parameter.

The requesting app can use [getAccessGrantFromRedirectUrl](https://inrupt.github.io/solid-client-access-grants-js/functions/index.getAccessRequestFromRedirectUrl.html) to get the Access Grant (serialized as VC)

```javascript
import {
   getAccessGrantFromRedirectUrl
} from "@inrupt/solid-client-access-grants";

// ...

const myAccessGrantVC = await getAccessGrantFromRedirectUrl(
   myURL,
   { fetch: session.fetch }     // fetch from the authenticated Session
);
```

### Read and Write `SolidDataset`

If the requestor has an Access Grant that allows the requestor to perform read/write operations on a [SolidDataset](/reference/glossary#soliddataset), the requestor can use the appropriate **`@inrupt/solid-client-access-grants`** [read and write APIs](https://inrupt.github.io/solid-client-access-grants-js/modules/resource.html); for example:

| [getSolidDataset](https://inrupt.github.io/solid-client-access-grants-js/functions/index.getSolidDataset.html)       | To read/fetch a SolidDataset from a Pod. |
| -------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
| [saveSolidDatasetAt](https://inrupt.github.io/solid-client-access-grants-js/functions/index.saveSolidDatasetAt.html) | To write a SolidDataset to a Pod.        |
| [deleteSolidDataset](https://inrupt.github.io/solid-client-access-grants-js/functions/index.deleteSolidDataset.html) | To delete a SolidDataset from a Pod.     |

To use these functions, the authenticated session must use **`Bearer`** token type.

To read or modify the data in a local SolidDataset (e.g., **`getThing`**, **`addUrl`**, **`setThing`** of a fetched SolidDataset or a new SolidDataset), use the **`@inrupt/solid-client`** library’s functions.

For example:

<pre class="language-javascript"><code class="lang-javascript"><strong>import {
</strong><strong>   getSolidDataset,
</strong><strong>   saveSolidDatasetAt
</strong><strong>} from "@inrupt/solid-client-access-grants";
</strong>
import {
  getThing,
  getStringNoLocale,
  addUrl,
  addStringNoLocale,
  buildThing,
  createThing,
  setThing
} from "@inrupt/solid-client";

// ...


// Use `getSolidDataset` from `@inrupt/solid-client-access-grants`
const mySolidDataset = await getSolidDataset(
   resourceURL,
   myAccessGrantVC,  // Access Grant (serialized as VC) that provides the user read access to get the SolidDataset
   { fetch : session.fetch } // fetch from the authenticated Session with tokenType Bearer
)

// Use functions from `@inrupt/solid-client` to modify the SolidDataset
// const myDataThing = getThing( ... );
// ...
// let myUpdatedSolidDataset = ...;
// ...

// Use `saveSolidDatasetAt` from `@inrupt/solid-client-access-grants`
const savedSolidDataset = await saveSolidDatasetAt(
  resourceURL,
  myUpdatedSolidDataset,
  myAccessGrantVC,             // Access Grant (serialized as VC) that grants the user write access to save the SolidDataset
  { fetch: session.fetch }     // authenticated Session with tokenType Bearer
);
</code></pre>

{% hint style="warning" %}
Ensure that you are using the APIs from the **`@inrupt/solid-client-access-grants`** and **not** the **`@inrupt/solid-client`** library.
{% endhint %}

To access the contents of the SolidDataset, use the **`@inrupt/solid-client`** library’s functions. For examples, see:

* [Read Data](/sdk/javascript-sdk/read-and-write-rdf-data#read)
* [Write a New SolidDataset](/sdk/javascript-sdk/read-and-write-rdf-data#write-a-new-soliddataset)
* [Modify an Existing SolidDataset](/sdk/javascript-sdk/read-and-write-rdf-data#modify-an-existing-soliddataset)

### Read and Write Non-RDF Files

If the requestor has an Access Grant that allows the requestor to perform read/write operations on a non-RDF file (e.g., `.pdf`, `.jpeg`, etc.), the requestor can use the appropriate **`@inrupt/solid-client-access-grants`** [read and write APIs](https://inrupt.github.io/solid-client-access-grants-js/modules/resource.html); for example:

<table data-header-hidden><thead><tr><th width="283.7109375">solid-client-access-grants Functions</th><th>Description</th></tr></thead><tbody><tr><td><a href="https://inrupt.github.io/solid-client-access-grants-js/functions/index.getFile.html">getFile</a></td><td>To read/fetch a file from a Pod.</td></tr><tr><td><a href="https://inrupt.github.io/solid-client-access-grants-js/functions/index.overwriteFile.html">overwriteFile</a></td><td><p>To update an <strong>existing</strong> file in a Pod.</p><p>Unlike the corresponding function in <code>@inrupt/solid-client</code>, you cannot use <code>solid-client-access-grants</code> <a href="https://inrupt.github.io/solid-client-access-grants-js/functions/index.overwriteFile.html">overwriteFile</a> to save a new file.</p></td></tr><tr><td><a href="https://inrupt.github.io/solid-client-access-grants-js/functions/index.saveFileInContainer.html">saveFileInContainer</a></td><td>To write a <strong>new</strong> file to a Pod.</td></tr><tr><td><a href="https://inrupt.github.io/solid-client-access-grants-js/modules/resource.html#deletefile">deleteFile</a></td><td>To delete a file from a Pod.</td></tr></tbody></table>

{% hint style="info" %}
The authenticated session must use **`Bearer`** token type.
{% endhint %}

{% hint style="warning" %}
Ensure that you are using the APIs from the **`@inrupt/solid-client-access-grants`** and **not** the **`@inrupt/solid-client`** library.
{% endhint %}

For example:

<pre class="language-javascript"><code class="lang-javascript"><strong>import {
</strong><strong>   getFile, overwriteFile
</strong><strong>} from "@inrupt/solid-client-access-grants";
</strong>
// ...

const file = await getFile(
  fileURL,               // File in Pod to Read
  myAccessGrantVC,       // Access Grant (serialized as VC) that grants the user read access to the File
  { fetch: session.fetch }  // authenticated Session with tokenType Bearer
);

// ...

const updated = await overwriteFile(
  fileURL,               // URL for the file
  fileWithNewContent,    // File
  myAccessGrantVC,       // Access Grant (serialized as VC) that grants the user read access to the File
  { contentType: fileWithNewContent.type,
    fetch: session.fetch }  // authenticated Session with tokenType Bearer
);
</code></pre>


# Inspect Access Requests and Access Grants

{% hint style="info" %}
**Access Requests and Grants**

The following Inrupt products are available to support Access Requests and Grants:

* **`solid-client-access-grants`** library for managing Access Requests and Grants
* Inrupt’s Enterprise Solid Server provides support for [Access Requests and Grants](/security/authorization/access-requests-grants). ESS serializes the Access Requests and Grants as Verifiable Credentials.
* Inrupt’s [Authorization Management Component](/security/authorization/access-requests-grants#authorization-management-component-amc) supports Access Request management.
  {% endhint %}

Since the content of this page is applicable to both Access Requests and Access Grants, the generic term Access Credential is used to refer to both.

Access Credentials are used to get access to data from a Pod, and it is useful to be able to inspect the metadata from the Credential to figure out which Pod data it applies to, and who can exercise the access it is giving.

## Reading information from Access Credentials

The **`@inrupt/solid-client-access-grants`** library provides various getters to extract information from the Access Credentials. The [API docs](https://inrupt.github.io/solid-client-access-grants-js/modules/common.html) lists all the available getters.

Most getters are specific to the Access Credentials data model: for instance, [getResources](https://inrupt.github.io/solid-client-access-grants-js/modules/common.html#getresources) lists all resources for which an Access Credential is applicable. Here is a basic usage exemple.

```javascript
const credential = /* get the credential */;
const resources = getResources(credential);
console.log(
  `Credential ${credential.id} applies to ${resources.length} resources.`
);
```

## Reading custom fields from Access Credentials

**`@inrupt/solid-client-access-grants`** supports adding custom fields to Access Credentials. These custom fields can also be read from the credential using dedicated getters. Two approaches are possible:

| Bulk reading custom fields with **`getCustomFields`**                                                                                       | Reads all the custom fields in the consent section of the provided Access Credential, and returns them as an object, keyed by custom field URL.                              |
| ------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Reading typed individual custom fields with **`getCustomBoolean`**, **`getCustomFloat`**, **`getCustomInteger`** and **`getCustomString`**. | Reads a given custom field (using its URL as a key) from the consent section of the provided Access Credential. These getters are type-safe and will throw on type mismatch. |

```javascript
const accessRequest = await issueAccessRequest({/* ... */}, {
 /* ..., */
 customFields: new Set([
    {
      key: new URL("https://example.org/ns/customString"),
      value: "custom value",
    },
    {
      key: new URL("https://example.org/ns/customInteger"),
      value: 1,
    },
  ]),
});
const customFields = getCustomFields(accessRequest);
// s is "custom value"
const s = customFields["https://example.org/ns/customString"];
// i is 1
const i = customFields["https://example.org/ns/customInteger"];

// s2 is also "custom value", and it is type safe.
const s2: string = getCustomString(
  accessRequest,
  new URL("https://example.org/ns/customString")
);

// i2 is also 1, and it is type safe.
const i2: number = getCustomInteger(
  accessRequest,
  new URL("https://example.org/ns/customInteger")
);
```


# Notifications

ESS can publish WebSocket notifications for create/update/delete operations on a [Resource](/reference/glossary#resource). Using the **`@inrupt/solid-client-notifications`** library, applications can subscribe to WebSocket notifications for a Resource.

### Subscribe to Changes

To subscribe to WebSocket notifications for a particular Resource:

* Use the [WebsocketNotification](https://inrupt.github.io/solid-client-notifications-js/classes/websocketNotification.WebsocketNotification.html#constructor) constructor to create a **`WebsocketNotification`** object. The constructor accepts the following:
  * Resource URL, and
  * An options object with an authenticated [fetch()](https://inrupt.github.io/solid-client-authn-js/browser/functions.html#fetch) function.

    {% hint style="warning" %} To subscribe to a Resource, you must authenticate as a user with **`Read`** to the Resource. For details on user authentication, see [Authentication](/sdk/javascript-sdk/authentication). {% endhint %}
* Use the [on](https://inrupt.github.io/solid-client-notifications-js/classes/websocketNotification.WebsocketNotification.html#on) function to listen for events. The function accepts the following:
  * The event type (e.g., **`"message"`**), and
  * A callback function to process the event.
* Use the [connect](https://inrupt.github.io/solid-client-notifications-js/classes/websocketNotification.WebsocketNotification.html#connect) function to connect to the WebSocket server.
* Use the [disconnect](https://inrupt.github.io/solid-client-notifications-js/classes/websocketNotification.WebsocketNotification.html#disconnect) function to disconnect from the WebSocket server.

When subscribing to a Container, the application receives notifications when the Container is created, deleted, or modified (such as when files are added or removed from the Container)

When subscribing to [SolidDataset](/reference/glossary#soliddataset) files or regular files (e.g., **`.jpg`**, **`.json`**), the application receives notifications when the target files are modified or deleted.

#### Example[#](#example)

The following example subscribes to change notifications for a Container **`https://storage.inrupt.com/<some identifier>/some-container/`** and logs the event messages to your console.

{% hint style="info" %}
**Note**

* When subscribing to a Container, the application receives notifications when the Container is created, deleted, or modified (such as when files are added or removed from the Container).
* For brevity, the authentication logic has been omitted. For details on user authentication, see Authentication.
  {% endhint %}

```javascript
import { getDefaultSession, fetch } from "@inrupt/solid-client-authn-browser";
import {
  WebsocketNotification,
} from "@inrupt/solid-client-notifications";

const containerUrl = "https://storage.inrupt.com/<some identifier>/some-container/";

// ... authentication logic has been omitted

const websocket = new WebsocketNotification(
  containerUrl,
  { fetch: fetch }
);

websocket.on("message", (message) => { console.log(JSON.stringify(message)) });

websocket.connect();
```

#### Sample Event

The following is a sample event emitted by the [Inrupt’s PodSpaces](/podspaces/podspaces).

```javascript
{
   "@context":[
      "https://www.w3.org/ns/activitystreams",
      {
         "state":{
            "@id":"http://www.w3.org/2011/http-headers#etag"
         }
      }
   ],
   "id":"urn:uuid:<uuid>",
   "type":[
      "http://www.w3.org/ns/prov#Activity",
      "Update"
   ],
   "object":{
      "id":"https://storage.inrupt.com/<some identifier>/some-container/",
      "type":[
         "http://www.w3.org/ns/ldp#BasicContainer",
         "http://www.w3.org/ns/ldp#Container",
         "http://www.w3.org/ns/ldp#RDFSource",
         "http://www.w3.org/ns/ldp#Resource"
      ]
   },
   "published":"2021-03-30T01:01:49.550044Z"
}
```

<table data-header-hidden><thead><tr><th width="128.06964111328125">Field</th><th>Description</th></tr></thead><tbody><tr><td><strong><code>@context</code></strong></td><td>An array containing the JSON-LD contexts for the notification event itself.</td></tr><tr><td><strong><code>id</code></strong></td><td>String that contains an identifier for the event.</td></tr><tr><td><strong><code>type</code></strong></td><td><p>An array identifying the event type:</p><pre class="language-javascript"><code class="lang-javascript">[
   "http://www.w3.org/ns/prov#Activity",
   "&#x3C;Action>"
]
</code></pre><p>Where <strong><code>"&#x3C;Action>"</code></strong> can be one of the following values:</p><ul><li><strong><code>"Create"</code></strong></li><li><strong><code>"Delete"</code></strong></li><li><strong><code>"Update"</code></strong></li></ul></td></tr><tr><td><strong><code>object</code></strong></td><td>The resource object:</td></tr><tr><td></td><td></td></tr><tr><td><strong><code>object.id</code></strong></td><td>String indicating the Resource URL.</td></tr><tr><td><strong><code>object.type</code></strong></td><td><p>An array indicating the Resource types.</p><div data-gb-custom-block data-tag="hint" data-style="info" class="hint hint-info"><p><strong>Tip</strong></p><p>Because a Container object is also an RDF Resource, to determine if the object is a non-Container RDF Resource, check that neither <strong><code>http://www.w3.org/ns/ldp#Container</code></strong> nor <strong><code>http://www.w3.org/ns/ldp#BasicContainer</code></strong> appear as elements of <strong><code>object.type</code></strong>.</p></div></td></tr><tr><td><strong><code>object.id</code></strong></td><td>String indicating the Resource URL.</td></tr><tr><td><strong><code>object.type</code></strong></td><td><p>An array indicating the Resource type(s).</p><p>Tip</p><p>Because a Container object is also an RDF Resource, to determine if the object is a non-Container RDF Resource, check that neither <strong><code>http://www.w3.org/ns/ldp#Container</code></strong> nor <strong><code>http://www.w3.org/ns/ldp#BasicContainer</code></strong> appear as elements of <strong><code>object.type</code></strong>.</p></td></tr><tr><td><strong><code>published</code></strong></td><td>The date and time the event is published.</td></tr></tbody></table>


# Error Codes

The following is a non-exhaustive list of various error codes that you may encounter and provides some possible causes for them.

### 401 Unauthorized

Indicates that the Resource to access is only accessible to certain agents, but the current user is not logged in.

If the user *is* logged in but still receives this error, you might not have passed the session’s [fetch()](https://inrupt.github.io/solid-client-authn-js/browser/functions.html#fetch) function as an option to the function sending the request.

### 403 Forbidden

Indicates that the current user is logged in but does not have the required level of access to the resource.

### 404 Not Found

Indicates that the Resource the user is trying to fetch does not exist.

### 409 Conflict

Indicates that the data you are trying to modify has been changed since your fetch operation, e.g., by a different person or on a different device.

Specifically, a SolidDataset keeps a changelog that tracks both the old value and new values of the property being modified. Then, the save operation applies the changes from the changelog to the current SolidDataset. If the old value specified in the changelog does not correspond to the value currently in the Pod, the save operation returns a [409 Conflict](#409-Conflict) error. For more information, see [Changelog Considerations](https://docs.inrupt.com/ess/latest/releases/changelog) .

Inrupt’s Enterprise Solid Server will also return [409 Conflict](#409-Conflict) error when it encounters unexpected data, like setting a Thing’s `rdf::type` to an integer rather than a URL.

### 412 Precondition Failed

Indicates that a condition that is set by `solid-client` before proceeding with the request is not met.

For example, [saveSolidDatasetAt](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#savesoliddatasetat) attempts to create a new Resource at the specified URL if the passed-in Resource has no URL identifying it. When creating a new Resource, the function adds the precondition that the Resource must not already exist. If the Resource already exists, [412 Precondition Failed](#412-Precondition-Failed) error is returned.

Consider the following code sequence which results in the 412 error:

```javascript
let myReadingList = createSolidDataset();
// ... Modify the myReadingList as needed.
// Perform Save: Create the Resource at specified location
let savedReadingList = await saveSolidDatasetAt(
  READING_LIST_URL,       // Location to save the new Resource
  myReadingList,          // No identifying URL associated with the `myReadingList`
  { fetch: fetch }
);
// ... Modify the myReadingList as needed.
// Perform Save: Attempts to create the Resource instead of overwrite.
savedReadingList = await saveSolidDatasetAt(
  READING_LIST_URL,
  myReadingList,          // Still no identifying URL associated with `myReadingList`
  { fetch: fetch }
);
```

The code sequence:

1. Creates a local instance of a SolidDataset (i.e., has no identifying URL information).
2. Calls [saveSolidDatasetAt](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#savesoliddatasetat) to save the SolidDataset in a Pod. Since there is no identifying URL associated with the `myReadingList` , the operation performs a create operation:

* The returned `savedReadingList` is the saved version of the SolidDataset you sent and has its identifying URL information, whereas
* The local `myReadingList` is unaffected by the save operation and still has no identifying URL information.

3. Calls [saveSolidDatasetAt](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#savesoliddatasetat) again with `myReadingList` . Since there is no identifying URL associated with the `myReadingList` , the operation attempts to perform a create operation. However, since the previous `saveSolidDatasetAt` has created the Resource in the Pod, the second `saveSolidDatasetAt` with `myReadingList` fails with a 412 error.

To avoid the 412 error on the second save, you can use the SolidDataset returned by the function, in this example `savedReadingList` which reflects your changes, or explicitly fetch the SolidDataset (e.g., using [getSolidDataset](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#getsoliddataset) ) which reflects the most recent changes at the time of the fetch.

By using either the returned SolidDataset or the fetched SolidDataset, [saveSolidDatasetAt](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#savesoliddatasetat) attempts to update, rather than create, the SolidDataset.

{% hint style="info" %}
Note

* When performing an update of an existing SolidDataset, you may encounter [409 Conflict](#409-Conflict) error if another operation has made conflicting modifications to the Resource you are trying to save.
* [saveSolidDatasetAt](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#savesoliddatasetat) returns a SolidDataset that reflects only the sent data. That means, if another process made separate non-conflicting modifications to the SolidDataset before you save, the returned SolidDataset only reflects your changes. To make sure you have the latest data, fetch the SolidDataset again with [getSolidDataset](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#getsoliddataset) .
  {% endhint %}


# Release Notes

| Library                              | Release Notes                                                                     |
| ------------------------------------ | --------------------------------------------------------------------------------- |
| `@inrupt/solid-client`               | [Release Notes](https://github.com/inrupt/solid-client-js/releases)               |
| `@inrupt/solid-client-authn-browser` | [Release Notes](https://github.com/inrupt/solid-client-authn-js/releases)         |
| `@inrupt/solid-client-authn-node`    | [Release Notes](https://github.com/inrupt/solid-client-authn-js/releases)         |
| `@inrupt/solid-client-access-grants` | [Release Notes](https://github.com/inrupt/solid-client-access-grants-js/releases) |
| `@inrupt/solid-client-notifications` | [Release Notes](https://github.com/inrupt/solid-client-notifications-js/releases) |
| `@inrupt/vocab-common-rdf`           | [Release Notes](https://github.com/inrupt/solid-common-vocab-rdf/releases)        |
| `@inrupt/vocab-solid`                | [Release Notes](https://github.com/inrupt/solid-common-vocab-rdf/releases)        |
| `@inrupt/vocab-inrupt-core`          | [Release Notes](https://github.com/inrupt/solid-common-vocab-rdf/releases)        |


# Java SDK

## Inrupt Java Client Libraries

Inrupt provides various Java client libraries to help developers create Solid applications.

### JDK Version

The Java client libraries requires JDK 11+.

### GitHub

* [inrupt/solid-client-java](https://github.com/inrupt/solid-client-java)
* [inrupt/rdf-wrapping-java](https://github.com/inrupt/rdf-wrapping-java)

## Library Modules

Inrupt’s Java Client Libraries are composed of different modules.

### Solid Client and Data Modules

<table><thead><tr><th width="187.921875">Module</th><th>Description</th></tr></thead><tbody><tr><td><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/package-summary.html">solid-client-solid</a></td><td><p>Provides support for Solid clients and Solid resources, such as:</p><ul><li><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidClient.html">SolidClient</a></li><li><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidSyncClient.html">SolidSyncClient</a></li><li><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidRDFSource.html">SolidRDFSource</a></li></ul></td></tr><tr><td><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/webid/package-summary.html">solid-client-webid</a></td><td>Provides support for WebID Profiles.</td></tr></tbody></table>

### Solid Authentication and Authorization Modules

<table><thead><tr><th width="238.85546875">Module</th><th>Description</th></tr></thead><tbody><tr><td><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/openid/package-summary.html">solid-client-openid</a></td><td><p>Provides support for OpenID, such as:</p><ul><li><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/openid/OpenIdSession.html">OpenIdSession</a></li></ul></td></tr><tr><td><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/package-summary.html">solid-client-access-grant</a></td><td><p>Provides support for Access Grants, such as:</p><ul><li><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantSession.html">AccessGrantSession</a></li></ul></td></tr></tbody></table>

### Additional Modules

**Underlying APIs (Publicly Available)**

The following modules provide the underlying APIs that are used by the other modules and are available for general use:

<table><thead><tr><th width="199.5546875">Base APIs</th><th>Description</th></tr></thead><tbody><tr><td><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/package-summary.html">solid-client-api</a></td><td>A layer that defines all the central abstractions used by the Java Client Libraries.</td></tr></tbody></table>

<table><thead><tr><th width="199.703125">RDF Processing</th><th>Description</th></tr></thead><tbody><tr><td><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/jena/package-summary.html">solid-client-jena</a></td><td>RDF processing that uses the Jena library.</td></tr><tr><td><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/rdf4j/package-summary.html">solid-client-rdf4j</a></td><td>RDF processing that uses the RDF4J library.</td></tr></tbody></table>

<table><thead><tr><th width="200.3828125">Vocabulary</th><th>Description</th></tr></thead><tbody><tr><td><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/vocabulary/package-summary.html">solid-client-vocabulary</a></td><td>Provides convenience objects for Solid-related identifiers.</td></tr></tbody></table>

**Underlying APIs (Internal Use)**

The following modules provide the underlying APIs that are used by the other modules; i.e., included as part of the other modules’ dependencies list but not intended for general use.

<table><thead><tr><th width="226.296875">Module</th><th>Description</th></tr></thead><tbody><tr><td><strong><code>solid-client-core</code></strong></td><td>Runtime implementations of Java Client Library APIs.</td></tr><tr><td><strong><code>solid-client-uma</code></strong></td><td>Provides support for UMA Token Exchange.</td></tr><tr><td><strong><code>solid-client-jackson</code></strong></td><td>JSON processing that uses the Jackson library.</td></tr><tr><td><strong><code>solid-client-jsonb</code></strong></td><td>JSON processing that uses the JSON-B library.</td></tr></tbody></table>

### Bugs and Feature Requests

For public feedback, bug reports, and feature requests, please file an issue via GitHub.

* [**`solid-client-java`**](https://github.com/inrupt/solid-client-java/issues)
* [**`rdf-wrapping-java`**](https://github.com/inrupt/rdf-wrapping-java/issues)


# Installation

To use Inrupt’s Java client library, add the **`inrupt-client-bom`** and specific library modules to your [Maven](https://maven.apache.org/) or [Gradle](https://gradle.org/) project.

### 1. Add **`inrupt-client-bom`**

{% tabs %}
{% tab title="Maven" %}
To your project’s **`pom.xml`**:

1. Add the **`inrupt-client-bom`** dependency in the project’s **`<dependencyManagement>`** section.
2. Replace **`SUBSTITUTE_VERSION`** with the version to use.

<pre class="language-java"><code class="lang-java">&#x3C;dependencyManagement>
   &#x3C;dependencies>
      &#x3C;dependency>
         &#x3C;groupId>com.inrupt.client&#x3C;/groupId>
         &#x3C;artifactId>inrupt-client-bom&#x3C;/artifactId>
<strong>         &#x3C;version>SUBSTITUTE_VERSION&#x3C;/version>
</strong>         &#x3C;type>pom&#x3C;/type>
         &#x3C;scope>import&#x3C;/scope>
      &#x3C;/dependency>
   &#x3C;/dependencies>
&#x3C;/dependencyManagement>
</code></pre>

For the latest version of **`inrupt-client-bom`**,

1. Go to [Maven Central](https://central.sonatype.com/search).
2. Search for **`inrupt-client-bom`**. Get the version for the package with **`com.inrupt.client`** namespace.
   {% endtab %}

{% tab title="Gradle (Groovy)" %}
To your project’s build script `build.gradle`:

1. Add the `inrupt-client-bom` platform dependency.
2. Replace `SUBSTITUTE_VERSION` with the version to use.

<pre class="language-java"><code class="lang-java">dependencies {
<strong>    implementation platform("com.inrupt.client:inrupt-client-bom:SUBSTITUTE_VERSION")
</strong>}
</code></pre>

For the latest version of `inrupt-client-bom`,

1. Go to [Maven Central](https://central.sonatype.com/search).
2. Search for `inrupt-client-bom`. Get the version for the package with `com.inrupt.client` namespace.
   {% endtab %}

{% tab title="Gradle (Kotlin)" %}
To your project’s build script `build.gradle.kts`:

1. Add the `inrupt-client-bom` platform dependency.
2. Replace `SUBSTITUTE_VERSION` with the version to use.

<pre class="language-java"><code class="lang-java">dependencies {
    //... additional dependencies
<strong>    implementation(platform("com.inrupt.client:inrupt-client-bom:SUBSTITUTE_VERSION"))
</strong>}
</code></pre>

For the latest version of `inrupt-client-bom`,

1. Go to [Maven Central](https://central.sonatype.com/search).
2. Search for `inrupt-client-bom`. Get the version for the package with `com.inrupt.client` namespace.
   {% endtab %}
   {% endtabs %}

### 2. Add Specific Module Dependencies

{% tabs %}
{% tab title="Maven" %}
To your project’s **`pom.xml`** file, you can either:

* Add the **`inrupt-client-runtime`** to include all recommended Java Client Libraries runtime modules; or
* Add specific Java Client Libraries modules.

{% tabs %}
{% tab title="inrupt-client-runtime" %}
To include all recommended runtime modules from the Java Client Libraries, add the following dependency to the **`<dependencies>`** section in your project’s **`pom.xml`** file.

The recommended modules include those modules used to:

* Access your WebID,
* Perform read and write operations (both RDF and non-RDF resources) on your Pod,
* For the read of RDF resources, return the RDF resources in Turtle and JSON, and
* Use access requests and access grants.

```java
<dependency>
    <groupId>com.inrupt.client</groupId>
    <artifactId>inrupt-client-runtime</artifactId>
</dependency>
```

{% endtab %}

{% tab title="Specific Modules" %}
To your project’s `pom.xml` file, add the specific library modules in your project `<dependencies>` section. The following example includes the modules used to:

* Access your WebID
* Perform read and write operations (both RDF and non-RDF resources) on your Pod, and
* For the read of RDF resources, return the RDF resources in Turtle and JSON, and
* Use access requests and access grants.

```java
<dependency>
    <groupId>com.inrupt.client</groupId>
    <artifactId>inrupt-client-api</artifactId>
</dependency>
<dependency>
    <groupId>com.inrupt.client</groupId>
    <artifactId>inrupt-client-solid</artifactId>
</dependency>
<dependency>
    <groupId>com.inrupt.client</groupId>
    <artifactId>inrupt-client-core</artifactId>
</dependency>
<dependency>
    <groupId>com.inrupt.client</groupId>
    <artifactId>inrupt-client-okhttp</artifactId>
</dependency>
<dependency>
    <groupId>com.inrupt.client</groupId>
    <artifactId>inrupt-client-jackson</artifactId>
</dependency>
<dependency>
    <groupId>com.inrupt.client</groupId>
    <artifactId>inrupt-client-jena</artifactId>
</dependency>
<dependency>
    <groupId>com.inrupt.client</groupId>
    <artifactId>inrupt-client-openid</artifactId>
</dependency>
<dependency>
    <groupId>com.inrupt.client</groupId>
    <artifactId>inrupt-client-accessgrant</artifactId>
</dependency>
<dependency>
    <groupId>com.inrupt.client</groupId>
    <artifactId>inrupt-client-uma</artifactId>
</dependency>
<dependency>
    <groupId>com.inrupt.client</groupId>
    <artifactId>inrupt-client-vocabulary</artifactId>
</dependency>
<dependency>
    <groupId>com.inrupt.client</groupId>
    <artifactId>inrupt-client-webid</artifactId>
</dependency>
```

{% endtab %}
{% endtabs %}

Inrupt’s Java Client Libraries are composed of different modules. See [Library Modules](/sdk/java-sdk#library-modules) for the list of available modules and their description.
{% endtab %}

{% tab title="Gradle (Groovy)" %}
To your project’s build script `build.gradle`, add the specific library modules as module dependencies. The following example includes the modules used to:

* Access your WebID,
* Perform read and write operations (both RDF and non-RDF resources) on your Pod, and
* For the read of RDF resources, return the RDF resources in Turtle and JSON, and
* Use Access Requests and Access Grants.

<pre class="language-java"><code class="lang-java">dependencies {
    implementation platform("com.inrupt.client:inrupt-client-bom:SUBSTITUTE_VERSION")
<strong>    implementation "com.inrupt.client:inrupt-client-api"
</strong><strong>    implementation "com.inrupt.client:inrupt-client-solid"
</strong><strong>    implementation "com.inrupt.client:inrupt-client-core"
</strong><strong>    implementation "com.inrupt.client:inrupt-client-okhttp"
</strong><strong>    implementation "com.inrupt.client:inrupt-client-jackson"
</strong><strong>    implementation "com.inrupt.client:inrupt-client-jena"
</strong><strong>    implementation "com.inrupt.client:inrupt-client-accessgrant"
</strong><strong>    implementation "com.inrupt.client:inrupt-client-openid"
</strong><strong>    implementation "com.inrupt.client:inrupt-client-uma"
</strong><strong>    implementation "com.inrupt.client:inrupt-client-vocabulary"
</strong><strong>    implementation "com.inrupt.client:inrupt-client-webid"
</strong>}
</code></pre>

Inrupt’s Java Client Libraries are composed of different modules. See [Library Modules](/sdk/java-sdk#library-modules) for the list of available modules and their description.
{% endtab %}

{% tab title="Gradle (Kotlin)" %}
To your project’s build script `build.gradle.kts`, add the specific library modules as module dependencies. The following example includes the modules used to:

* Add the `inrupt-client-runtime` to include all recommended Java Client Libraries runtime modules; or
* Add specific Java Client Libraries modules.

{% tabs %}
{% tab title="inrupt-client-runtime" %}
To include all recommended runtime modules from the Java Client Libraries, add the following `inrupt-client-runtime` dependency. The recommended modules include those modules used to:

* Access your WebID,
* Perform read and write operations (both RDF and non-RDF resources) on your Pod,
* For the read of RDF resources, return the RDF resources in Turtle and JSON, and
* Use Access Requests and Access Grants.

<pre class="language-java"><code class="lang-java">dependencies {
    //... additional dependencies
    implementation(platform("com.inrupt.client:inrupt-client-bom:SUBSTITUTE_VERSION"))
<strong>    implementation("com.inrupt.client:inrupt-client-runtime")
</strong>}
</code></pre>

{% endtab %}

{% tab title="Specific Modules" %}
You can add specific library modules. The following example includes the modules used to:

* Access your WebID,
* Perform read and write operations (both RDF and non-RDF resources) on your Pod,
* For the read of RDF resources, return the RDF resources in Turtle and JSON, and
* Use Access Requests and Access Grants.

<pre class="language-java"><code class="lang-java">dependencies {
    //... additional dependencies
    implementation(platform("com.inrupt.client:inrupt-client-bom:SUBSTITUTE_VERSION"))
<strong>    implementation("com.inrupt.client:inrupt-client-api")
</strong><strong>    implementation("com.inrupt.client:inrupt-client-solid")
</strong><strong>    implementation("com.inrupt.client:inrupt-client-core")
</strong><strong>    implementation("com.inrupt.client:inrupt-client-okhttp")
</strong><strong>    implementation("com.inrupt.client:inrupt-client-jackson")
</strong><strong>    implementation("com.inrupt.client:inrupt-client-jena")
</strong><strong>    implementation("com.inrupt.client:inrupt-client-accessgrant")
</strong><strong>    implementation("com.inrupt.client:inrupt-client-openid")
</strong><strong>    implementation("com.inrupt.client:inrupt-client-uma")
</strong><strong>    implementation("com.inrupt.client:inrupt-client-vocabulary")
</strong><strong>    implementation("com.inrupt.client:inrupt-client-webid")
</strong>}
</code></pre>

{% endtab %}
{% endtabs %}

Inrupt’s Java Client Libraries are composed of different modules. See [Library Modules](/sdk/java-sdk#library-modules) for the list of available modules and their description.
{% endtab %}
{% endtabs %}


# Tutorial

The tutorial in this section creates a Spring Boot personal (i.e., single-user) Web service on **`http://localhost:8080`** that uses Inrupt’s Java client library to:

* Read the Pod URLs associated with the user’s [WebID](/reference/glossary#webid).
* Store and manage expense records in the user’s Pod. The expense records are stored as [Resource Description Framework (RDF) resource](/reference/glossary#rdf-resource).

{% hint style="info" %}
The locally-run personal Web service uses the [Client Credentials](https://www.rfc-editor.org/rfc/rfc6749) flow; that is, the service logs in with its credentials on behalf of the user who registered the client.
{% endhint %}

For this part of the tutorial, an expense record (with date, description, provider, amount, currency, category information) is stored as a file in the Pod with the following structure and sample values (shown in Turtle):

```turtle
<https://storage.example.com/{rootContainer}/expenses/20230306/teamLunchExpense>
    <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> &#x3C;https://schema.org/Invoice> ;
    <https://schema.org/purchaseDate>  "2023-03-07T00:00:00Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> ;
    <https://schema.org/provider>      "Example Restaurant" ;
    <https://schema.org/description>   "Team Lunch";
    <https://schema.org/category>      "Travel and Entertainment" ;
    <https://schema.org/priceCurrency> "USD" ;
    <https://schema.org/totalPrice>    "120"^^<http://www.w3.org/2001/XMLSchema#decimal> .
```

{% hint style="info" %}

* The **`subject`** and the **`predicates`** are URLs.
* The **`object`** may be URLs or literals.
  {% endhint %}

Attach a receipt to the expense:

* Store the receipt (as a non-RDF **`.png`** , **`.pdf`** , or **`.jpeg`** file) to the user’s Pod.
* Update the the Expense resource by adding a link to the saved receipt file.

```turtle
 <https://storage.example.com/{rootContainer}/expenses/20230306/teamLunchExpense>
     <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://schema.org/Invoice> ;
     <https://schema.org/purchaseDate>  "2023-03-07T00:00:00Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> ;
     <https://schema.org/provider>      "Example Restaurant" ;
     <https://schema.org/description>   "Team Lunch";
     <https://schema.org/category>      "Travel and Entertainment" ;
     <https://schema.org/priceCurrency> "USD" ;
     <https://schema.org/totalPrice>    "120"^^<http://www.w3.org/2001/XMLSchema#decimal> ;
    <strong> <https://schema.org/image>     <https://storage.example.com/{rootContainer}/expenses/20230306/teamLunchExpense_somerandomsuffix.jpg> .
```


# Prerequisites

### JDK

The Java client libraries requires JDK 11+. This tutorial uses JDK 17.

### IDE

Use an IDE of your choice.

{% hint style="info" %}
**Note**

* This tutorial uses Inrupt’s [PodSpaces](https://github.com/inrupt/docs-gitbook/tree/main/.gitbook/includes/broken-reference/README.md) and provides instructions on creating an account, WebID and a Pod through PodSpaces.
* PodSpaces is currently available as Developer Preview. Do not use for production or storing sensitive/personal data.
  {% endhint %}

To get a WebID and a Pod on [PodSpaces](https://github.com/inrupt/docs-gitbook/tree/main/.gitbook/includes/broken-reference/README.md) :

1. Go to [PodSpaces](https://start.inrupt.com/).
2. To create an account, you must agree to the Inrupt’s Terms of Service. To agree, select the checkbox.
3. If you agree to Inrupt’s Terms of Service, click on the <mark style="background-color:blue;">**Sign Up**</mark> button.
4. If you have not registered an account with the Inrupt Identity Provider, click on the <mark style="background-color:blue;">**Sign up**</mark> link to create an account:
   1. Fill in your username, email, and password.
   2. Click <mark style="background-color:blue;">**Sign Up**</mark>. You are sent a verification email.
   3. Check your email for the verification email. Follow the instructions in the email to verify. Check your spam if you do not see the email in your inbox.
   4. Once verified, return to click <mark style="background-color:blue;">**Continue**</mark> to go to the Sign in page:
      1. Enter your username and password.
      2. Click <mark style="background-color:blue;">**Sign in**</mark> to your account. The screen displays the access required to continue.
   5. To allow and continue, click <mark style="background-color:blue;">**Allow**</mark>.\
      The application displays your WebID and Pod Storage details:
      1. WebID: **`https://id.inrupt.com/{username}`** .\
         Pod Storage: **`https://storage.inrupt.com/{Root Container}`**

### Client Credentials

Inrupt’s PodSpaces provides an [Application Registration page](https://login.inrupt.com/registration.html) where you can statically register your applications to generate credentials for them.

1. Go to PodSpaces [Application Registration](https://login.inrupt.com/registration.html) page.
2. If not already logged in, you will redirect to the login page. Log in with your username and password.
3. In the <mark style="background-color:blue;">**Register an app**</mark> textbox, enter your application’s name and click <mark style="background-color:blue;">**Register**</mark>.
4. The Client ID and Client Secret for your application appears under Apps You’ve Registered list.

{% hint style="danger" %}
Safeguard your **`Client ID`** and **`Client Secret`** values. Do not share these with any third parties as anyone with your **`Client ID`** and **`Client Secret`** values can impersonate you and act fully on your behalf.
{% endhint %}

### Initialized Spring Boot Web Project

{% hint style="info" %}
This tutorial assumes an initialized Spring Boot Web Maven Java Project or Spring Boot Web Gradle Kotlin Project.
{% endhint %}

{% tabs %}
{% tab title="Java" %}
For Java, this tutorial uses Spring Boot Web Maven project. Initialize a Spring Boot Web Maven/Java project.

If you are initializing a new project at <https://start.spring.io/> , specify the following:

<table data-header-hidden><thead><tr><th width="135.80859375"></th><th></th></tr></thead><tbody><tr><td>Project</td><td>Select <strong><code>Maven</code></strong>.</td></tr><tr><td>Language</td><td>Select <strong><code>Java</code></strong>.</td></tr><tr><td>Spring Boot</td><td>Select a version.</td></tr><tr><td>Project Metadata</td><td>bCZIVpmU6Btn</td></tr><tr><td>Group</td><td>com.example</td></tr><tr><td>Artifact</td><td>getting-started</td></tr><tr><td>Name</td><td>getting-started</td></tr><tr><td>Description</td><td>Demo Getting Started project for Solid</td></tr><tr><td>Package Name</td><td>com.example.gettingstarted</td></tr><tr><td>Packaging</td><td>Jar</td></tr><tr><td>Java</td><td>17</td></tr><tr><td>Dependencies</td><td>Spring Web</td></tr></tbody></table>

Click <mark style="background-color:blue;">**Generate**</mark>.

Once you have generated and downloaded the resulting `zip` file, unzip the file to your destination directory and open the project in your IDE.
{% endtab %}

{% tab title="Kotlin" %}
For Kotlin, this tutorial uses Spring Boot Web Gradle project. Initialize a Spring Boot Web Gradle Kotlin project.

If you are initializing a new project at <https://start.spring.io/> , specify the following:

<table data-header-hidden><thead><tr><th width="140.90234375"></th><th></th></tr></thead><tbody><tr><td>Project</td><td>Select <code>Gradle-Kotlin</code>.</td></tr><tr><td>Language</td><td>Select <code>Kotlin</code>.</td></tr><tr><td>Spring Boot</td><td>Select a version.</td></tr><tr><td>Project Metadata</td><td>W0ReSePvnvQc</td></tr><tr><td>Group</td><td>com.example</td></tr><tr><td>Artifact</td><td>getting-started</td></tr><tr><td>Name</td><td>getting-started</td></tr><tr><td>Description</td><td>Demo Getting Started project for Solid</td></tr><tr><td>Package Name</td><td>com.example.gettingstarted</td></tr><tr><td>Packaging</td><td>Jar</td></tr><tr><td>Java</td><td>17</td></tr><tr><td>Dependencies</td><td>Spring Web</td></tr></tbody></table>

Click <mark style="background-color:blue;">**Generate**</mark>.

Once you have generated and downloaded the resulting `zip` file, unzip the file to your destination directory and open the project in your IDE.
{% endtab %}
{% endtabs %}


# Step 1: Add Inrupt Java Client Libraries

If you have not already, open your Spring Boot project in your IDE.

* For Java, this tutorial assumes an initialized Spring Boot Web Maven Project.
* For Kotlin, this tutorial assumes an initialized Spring Boot Web Gradle Project.

## 1. Add inrupt-client-bom

{% tabs %}
{% tab title="Java" %}
For Java, this tutorial assumes an initialized Spring Boot Web Maven Project.

To your project’s **`pom.xml`** :

1. Add the **`inrupt-client-bom`** dependency in the project’s **`<dependencyManagement>`** section.
2. Replace **`SUBSTITUTE_VERSION`** with the version to use.

<pre class="language-xml"><code class="lang-xml">
&#x3C;dependencyManagement>
   &#x3C;dependencies>
      &#x3C;dependency>
         &#x3C;groupId>com.inrupt.client&#x3C;/groupId>
         &#x3C;artifactId>inrupt-client-bom&#x3C;/artifactId>
<strong>         &#x3C;version>SUBSTITUTE_VERSION&#x3C;/version>
</strong>         &#x3C;type>pom&#x3C;/type>
         &#x3C;scope>import&#x3C;/scope>
      &#x3C;/dependency>
   &#x3C;/dependencies>
&#x3C;/dependencyManagement>
</code></pre>

For the latest version of **`inrupt-client-bom`** ,

1. Go to [Maven Central](https://central.sonatype.com/search) .
2. Search for **`inrupt-client-bom`** . Get the version for the package with **`com.inrupt.client`** namespace.
   {% endtab %}

{% tab title="Kotlin" %}
For Kotlin, this tutorial assumes an initialized Spring Boot Web Gradle Project.

To your project’s build script **`build.gradle.kts`** :

1. Add the **`inrupt-client-bom`** platform dependency.
2. Replace **`SUBSTITUTE_VERSION`** with the version to use.

<pre class="language-groovy"><code class="lang-groovy">
dependencies {
    //... additional dependencies
<strong>    implementation(platform("com.inrupt.client:inrupt-client-bom:SUBSTITUTE_VERSION"))
</strong>}
</code></pre>

For the latest version of **`inrupt-client-bom`** ,

1. Go to [Maven Central](https://central.sonatype.com/search) .
2. Search for **`inrupt-client-bom`** . Get the version for the package with **`com.inrupt.client`** namespace.
   {% endtab %}
   {% endtabs %}

## 2. Add Specific Module Dependencies

{% tabs %}
{% tab title="Java" %}
To your project’s **`pom.xml`** file, you can either:

* Add the **`inrupt-client-runtime`** to include all recommended Java Client Libraries runtime modules; or
* Add specific Java Client Libraries modules.

{% tabs %}
{% tab title="inrupt-client-runtime" %}
To include all recommended runtime modules from the Java Client Libraries, add the following dependency to the **`<dependencies>`** section in your project’s **`pom.xml`** file.

The recommended modules include those modules used to:

* Access your WebID,
* Perform read and write operations (both RDF and non-RDF resources) on your Pod,
* For the read of RDF resources, return the RDF resources in Turtle and JSON, and
* Use access requests and access grants.

```xml
<dependency>
    <groupId>com.inrupt.client</groupId>
    <artifactId>inrupt-client-runtime</artifactId>
</dependency>
```

{% endtab %}

{% tab title="Specific Modules" %}
To your project’s **`pom.xml`** file, add the specific library modules in your project **`<dependencies>`** section. The following example includes the modules used to:

* Access your WebID,
* Perform read and write operations (both RDF and non-RDF resources) on your Pod, and
* For the read of RDF resources, return the RDF resources in Turtle and JSON, and
* Use access requests and access grants.

```xml
<dependency>
    <groupId>com.inrupt.client</groupId>
    <artifactId>inrupt-client-api</artifactId>
</dependency>
<dependency>
    <groupId>com.inrupt.client</groupId>
    <artifactId>inrupt-client-solid</artifactId>
</dependency>
<dependency>
    <groupId>com.inrupt.client</groupId>
    <artifactId>inrupt-client-core</artifactId>
</dependency>
<dependency>
    <groupId>com.inrupt.client</groupId>
    <artifactId>inrupt-client-okhttp</artifactId>
</dependency>
<dependency>
    <groupId>com.inrupt.client</groupId>
    <artifactId>inrupt-client-jackson</artifactId>
</dependency>
<dependency>
    <groupId>com.inrupt.client</groupId>
    <artifactId>inrupt-client-jena</artifactId>
</dependency>
<dependency>
    <groupId>com.inrupt.client</groupId>
    <artifactId>inrupt-client-openid</artifactId>
</dependency>
<dependency>
    <groupId>com.inrupt.client</groupId>
    <artifactId>inrupt-client-accessgrant</artifactId>
</dependency>
<dependency>
    <groupId>com.inrupt.client</groupId>
    <artifactId>inrupt-client-uma</artifactId>
</dependency>
<dependency>
    <groupId>com.inrupt.client</groupId>
    <artifactId>inrupt-client-vocabulary</artifactId>
</dependency>
<dependency>
    <groupId>com.inrupt.client</groupId>
    <artifactId>inrupt-client-webid</artifactId>
</dependency>
```

{% endtab %}
{% endtabs %}

Inrupt’s Java Client Libraries are composed of different modules . See [Library Modules](/sdk/java-sdk#library-modules) for the list of available modules and their description.

Once you have modified your **`pom.xml`** , reload your Maven dependencies if your IDE has not automatically done so.
{% endtab %}

{% tab title="Kotlin" %}
To your project’s build script **`build.gradle.kts`** , add the specific library modules as module dependencies. The following example includes the modules used to:

* Add the **`inrupt-client-runtime`** to include all recommended Java Client Libraries runtime modules; or
* Add specific Java Client Libraries modules.

{% tabs %}
{% tab title="inrupt-client-runtime" %}
To include all recommended runtime modules from the Java Client Libraries, add the following **`inrupt-client-runtime`** dependency. The recommended modules include those modules used to:

* Access your WebID,
* Perform read and write operations (both RDF and non-RDF resources) on your Pod,
* For the read of RDF resources, return the RDF resources in Turtle and JSON, and
* Use Access Requests and Access Grants.

<pre class="language-none"><code class="lang-none">
dependencies {
    //... additional dependencies
    implementation(platform("com.inrupt.client:inrupt-client-bom:SUBSTITUTE_VERSION"))
<strong>    implementation("com.inrupt.client:inrupt-client-runtime")
</strong>}
</code></pre>

{% endtab %}

{% tab title="Specific Modules" %}
You can add specific library modules. The following example includes the modules used to:

* Access your WebID,
* Perform read and write operations (both RDF and non-RDF resources) on your Pod,
* For the read of RDF resources, return the RDF resources in Turtle and JSON, and
* Use Access Requests and Access Grants.

<pre class="language-none"><code class="lang-none">
dependencies {
    //... additional dependencies
    implementation(platform("com.inrupt.client:inrupt-client-bom:SUBSTITUTE_VERSION"))
<strong>    implementation("com.inrupt.client:inrupt-client-api")
</strong><strong>    implementation("com.inrupt.client:inrupt-client-solid")
</strong><strong>    implementation("com.inrupt.client:inrupt-client-core")
</strong><strong>    implementation("com.inrupt.client:inrupt-client-okhttp")
</strong><strong>    implementation("com.inrupt.client:inrupt-client-jackson")
</strong><strong>    implementation("com.inrupt.client:inrupt-client-jena")
</strong><strong>    implementation("com.inrupt.client:inrupt-client-accessgrant")
</strong><strong>    implementation("com.inrupt.client:inrupt-client-openid")
</strong><strong>    implementation("com.inrupt.client:inrupt-client-uma")
</strong><strong>    implementation("com.inrupt.client:inrupt-client-vocabulary")
</strong><strong>    implementation("com.inrupt.client:inrupt-client-webid")
</strong>}
</code></pre>

{% endtab %}
{% endtabs %}

Inrupt’s Java Client Libraries are composed of different modules . See [Library Modules](/sdk/java-sdk#library-modules) for the list of available modules and their description.

Once you have modified your build script (e.g., **`build.gradle`** or **`build.gradle.kts`** ), reload your dependencies if your IDE has not automatically done so.
{% endtab %}
{% endtabs %}


# Step 2: Expense Class

In this tutorial, an expense record (with date, description, provider, amount, currency, category information) is stored as an [RDF (Resource Description Framework)](/reference/glossary#rdf-resource) file. For example, a saved expense RDF file may contain the following content (shown in Turtle format):

```turtle
<https://storage.example.com/{rootContainer}/expenses/20230306/teamLunchExpense>
    <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://schema.org/Invoice> ;
    <https://schema.org/purchaseDate>  "2023-03-07T00:00:00Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> ;
    <https://schema.org/provider>      "Example Restaurant" ;
    <https://schema.org/description>   "Team Lunch";
    <https://schema.org/category>      "Travel and Entertainment" ;
    <https://schema.org/priceCurrency> "USD" ;
    <https://schema.org/totalPrice>    "120"^^<http://www.w3.org/2001/XMLSchema#decimal> .
```

{% hint style="info" %}
In addition to the expense data, the triples also include an RDF type statement, which acts to describe the resource as a whole; in this example, an <https://schema.org/Invoice> .
{% endhint %}

## Create the Expense Class

{% hint style="info" %}
Tip\
Various aspects related to modeling a Solid RDF Resource are noted as comments in the code. For more details, see [Modeling an RDF Resource](/sdk/java-sdk/crud-rdf-data/modeling-rdf-data).
{% endhint %}

{% tabs %}
{% tab title="Java" %}
In the **`src/main/java/com/example/gettingstarted/`** directory, create an **`Expense.java`** class with the following content:

```java
package com.example.gettingstarted;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.inrupt.client.Headers;
import com.inrupt.client.solid.SolidRDFSource;
import com.inrupt.rdf.wrapping.commons.RDFFactory;
import com.inrupt.rdf.wrapping.commons.TermMappings;
import com.inrupt.rdf.wrapping.commons.ValueMappings;
import com.inrupt.rdf.wrapping.commons.WrapperIRI;
import org.apache.commons.rdf.api.Dataset;
import org.apache.commons.rdf.api.Graph;
import org.apache.commons.rdf.api.IRI;
import org.apache.commons.rdf.api.RDFTerm;
import java.math.BigDecimal;
import java.net.URI;
import java.time.Instant;
import java.util.Date;
import java.util.Objects;
/**
 * Part 1
 * Note: extends SolidRDFSource
 * To model the Expense class as an RDF resource, the Expense class extends SolidRDFSource.
 * <p>
 * The @JsonIgnoreProperties annotation is added to ignore non-class-member fields
 * when serializing Expense data as JSON.
 */
@JsonIgnoreProperties(value = { "metadata", "headers", "graph", "graphNames", "entity", "contentType" })
public class Expense extends SolidRDFSource {
    /**
     * Note 2a: Predicate Definitions
     * The following constants define the Predicates used in our triple statements.
     */
    static IRI RDF_TYPE = rdf.createIRI("http://www.w3.org/1999/02/22-rdf-syntax-ns#type");
    static IRI SCHEMA_ORG_PURCHASE_DATE = rdf.createIRI("https://schema.org/purchaseDate");
    static IRI SCHEMA_ORG_PROVIDER = rdf.createIRI("https://schema.org/provider");
    static IRI SCHEMA_ORG_DESCRIPTION = rdf.createIRI("https://schema.org/description");
    static IRI SCHEMA_ORG_TOTAL_PRICE = rdf.createIRI("https://schema.org/totalPrice");
    static IRI SCHEMA_ORG_PRICE_CURRENCY = rdf.createIRI("https://schema.org/priceCurrency");
    static IRI SCHEMA_ORG_CATEGORY = rdf.createIRI("https://schema.org/category");
    /**
     * Note 2b: Value Definition
     * The following constant define the value for the predicate RDF_TYPE.
     */
    static URI MY_RDF_TYPE_VALUE = URI.create("https://schema.org/Invoice");
    /**
     * Note 3: Node class
     * The Node class is an inner class (defined below) that handles the mapping between expense data and RDF triples.
     * The subject contains the expense data.
     */
    private final Node subject;
    /**
     * Note 4: Constructors
     * Expense constructors to handle SolidResource fields:
     * - identifier: The destination URI of the resource; e.g., https://myPod.example.com/myPod/expense1
     * - dataset: The org.apache.commons.rdf.api.Dataset that corresponding to the resource.
     * - headers:  The com.inrupt.client.Headers that contains HTTP header information.
     * <p>
     * In addition, the subject field is initialized.
     */
    public Expense(final URI identifier, final Dataset dataset, final Headers headers) {
        super(identifier, dataset, headers);
        this.subject = new Node(rdf.createIRI(identifier.toString()), getGraph());
    }
    public Expense(final URI identifier) {
        this(identifier, null, null);
    }
    @JsonCreator
    public Expense(@JsonProperty("identifier") final URI identifier,
                   @JsonProperty("merchantProvider") String merchantProvider,
                   @JsonProperty("expenseDate") Date expenseDate,
                   @JsonProperty("description") String description,
                   @JsonProperty("amount") BigDecimal amount,
                   @JsonProperty("currency") String currency,
                   @JsonProperty("category") String category) {
        this(identifier);
        this.setRDFType(MY_RDF_TYPE_VALUE);
        this.setMerchantProvider(merchantProvider);
        this.setExpenseDate(expenseDate);
        this.setDescription(description);
        this.setAmount(amount);
        this.setCurrency(currency);
        this.setCategory(category);
    }
    /**
     * Note 5: Various getters/setters.
     * The getters and setters reference the subject's methods.
     */
    public URI getRDFType() {
        return subject.getRDFType();
    }
    public void setRDFType(URI rdfType) {
        subject.setRDFType(rdfType);
    }
    public String getMerchantProvider() {
        return subject.getMerchantProvider();
    }
    public void setMerchantProvider(String merchantProvider) {
        subject.setMerchantProvider(merchantProvider);
    }
    public Date getExpenseDate() {
        return subject.getExpenseDate();
    }
    public void setExpenseDate(Date expenseDate) {
        subject.setExpenseDate(expenseDate);
    }
    public String getDescription() {
        return subject.getDescription();
    }
    public void setDescription(String description) {
        subject.setDescription(description);
    }
    public BigDecimal getAmount() {
        return subject.getAmount();
    }
    public void setAmount(BigDecimal amount) {
        subject.setAmount(amount);
    }
    public String getCurrency() {
        return subject.getCurrency();
    }
    public void setCurrency(String currency) {
        subject.setCurrency(currency);
    }
    public String getCategory() {
        return subject.getCategory();
    }
    public void setCategory(String category) {
        subject.setCategory(category);
    }
    /**
     * Note 6: Inner class ``Node`` that extends WrapperIRI
     * Node class handles the mapping of the expense data (date, provider,
     * description, category, priceCurrency, total) to RDF triples
     * <subject> <predicate> <object>.
     * <p>
     * Nomenclature Background: A set of RDF triples is called a Graph.
     */
    class Node extends WrapperIRI {
        Node(final RDFTerm original, final Graph graph) {
            super(original, graph);
        }
        URI getRDFType() {
            return anyOrNull(RDF_TYPE, ValueMappings::iriAsUri);
        }
        /**
         * Note 7: In its getters, the ``Node`` class calls WrapperBlankNodeOrIRI
         * method ``anyOrNull`` to return either 0 or 1 value mapped to the predicate.
         * You can use ValueMappings method to convert the value to a specified type.
         * <p>
         * In its setters, the ``Node`` class calls WrapperBlankNodeOrIRI
         * method ``overwriteNullable`` to return either 0 or 1 value mapped to the predicate.
         * You can use TermMappings method to store the value with the specified type information.
         */
        void setRDFType(URI type) {
            overwriteNullable(RDF_TYPE, type, TermMappings::asIri);
        }
        String getMerchantProvider() {
            return anyOrNull(SCHEMA_ORG_PROVIDER, ValueMappings::literalAsString);
        }
        void setMerchantProvider(String provider) {
            overwriteNullable(SCHEMA_ORG_PROVIDER, provider, TermMappings::asStringLiteral);
        }
        public Date getExpenseDate() {
            Instant expenseInstant = anyOrNull(SCHEMA_ORG_PURCHASE_DATE, ValueMappings::literalAsInstant);
            if (expenseInstant != null) return Date.from(expenseInstant);
            else return null;
        }
        public void setExpenseDate(Date expenseDate) {
            overwriteNullable(SCHEMA_ORG_PURCHASE_DATE, expenseDate.toInstant(), TermMappings::asTypedLiteral);
        }
        String getDescription() {
            return anyOrNull(SCHEMA_ORG_DESCRIPTION, ValueMappings::literalAsString);
        }
        void setDescription(String description) {
            overwriteNullable(SCHEMA_ORG_DESCRIPTION, description, TermMappings::asStringLiteral);
        }
        public BigDecimal getAmount() {
            String priceString = anyOrNull(SCHEMA_ORG_TOTAL_PRICE, ValueMappings::literalAsString);
            if (priceString != null) return new BigDecimal(priceString);
            else return null;
        }
        /**
         * Note 8: You can write your own TermMapping helper.
         */
        public void setAmount(BigDecimal totalPrice) {
            overwriteNullable(SCHEMA_ORG_TOTAL_PRICE, totalPrice, (final BigDecimal value, final Graph graph) -> {
                Objects.requireNonNull(value, "Value must not be null");
                Objects.requireNonNull(graph, "Graph must not be null");
                return RDFFactory.getInstance().
                        createLiteral(
                                value.toString(),
                                RDFFactory.getInstance().createIRI("http://www.w3.org/2001/XMLSchema#decimal")
                        );
            });
        }
        public String getCurrency() {
            return anyOrNull(SCHEMA_ORG_PRICE_CURRENCY, ValueMappings::literalAsString);
        }
        public void setCurrency(String currency) {
            overwriteNullable(SCHEMA_ORG_PRICE_CURRENCY, currency, TermMappings::asStringLiteral);
        }
        public String getCategory() {
            return anyOrNull(SCHEMA_ORG_CATEGORY, ValueMappings::literalAsString);
        }
        public void setCategory(String category) {
            overwriteNullable(SCHEMA_ORG_CATEGORY, category, TermMappings::asStringLiteral);
        }
    }
}
```

{% endtab %}

{% tab title="Kotlin" %}
In the **`src/main/kotlin/com/example/gettingstarted/`** directory, create an **`Expense.kt`** Kotlin class with the following content:

```kotlin
package com.example.gettingstarted
import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.fasterxml.jackson.annotation.JsonProperty
import com.inrupt.client.Headers
import com.inrupt.client.solid.SolidRDFSource
import com.inrupt.rdf.wrapping.commons.*
import org.apache.commons.rdf.api.Dataset
import org.apache.commons.rdf.api.Graph
import org.apache.commons.rdf.api.IRI
import org.apache.commons.rdf.api.RDFTerm
import java.math.BigDecimal
import java.net.URI
import java.time.Instant
import java.util.*
/**
 * Part 1
 * Note: extends SolidRDFSource
 * To model the Expense class as an RDF resource, the Expense class extends SolidRDFSource.
 *
 *
 * The @JsonIgnoreProperties annotation is added to ignore the non-class-member fields
 * when serializing Expense data as JSON.
 */
@JsonIgnoreProperties(value = [ "metadata", "headers", "graph", "graphNames", "entity", "contentType" ])
class Expense(
    identifier: URI,
    dataset: Dataset = rdf.createDataset(),
    headers: Headers = Headers.empty(),
) : SolidRDFSource(identifier, dataset, headers) {
    companion object {
        /**
         * Note 2a: Predicate Definitions
         * The following constants define the Predicates used in our triple statements.
         */
        var RDF_TYPE: IRI = rdf.createIRI("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")
        var SCHEMA_ORG_PURCHASE_DATE: IRI = rdf.createIRI("https://schema.org/purchaseDate")
        var SCHEMA_ORG_PROVIDER: IRI = rdf.createIRI("https://schema.org/provider")
        var SCHEMA_ORG_DESCRIPTION: IRI = rdf.createIRI("https://schema.org/description")
        var SCHEMA_ORG_TOTAL_PRICE: IRI = rdf.createIRI("https://schema.org/totalPrice")
        var SCHEMA_ORG_PRICE_CURRENCY: IRI = rdf.createIRI("https://schema.org/priceCurrency")
        var SCHEMA_ORG_CATEGORY: IRI = rdf.createIRI("https://schema.org/category")
        /**
         * Note 2b: Value Definition
         * The following constant defines the value for the predicate RDF_TYPE.
         */
        var MY_RDF_TYPE_VALUE: URI = URI.create("https://schema.org/Invoice")
    }
    /**
     * Note 3: Node class
     * The Node class is an inner class (defined below) that handles the mapping between expense data and RDF triples.
     * The subject contains the expense data.
     */
    private val subject: Node = Node(rdf.createIRI(identifier.toString()), dataset.graph)
    /**
     * Note 4: Constructors
     * Expense constructors to handle SolidResource fields:
     * - identifier: The destination URI of the resource; e.g., https://myPod.example.com/myPod/expense1
     * - dataset: The org.apache.commons.rdf.api.Dataset that corresponds to the resource.
     * - headers:  The com.inrupt.client.Headers that contains header information.
     *
     *
     * In addition, the subject field is initialized.
     */
    @JsonCreator
    constructor(
        @JsonProperty("identifier") identifier: URI,
        @JsonProperty("merchantProvider") merchantProvider: String?,
        @JsonProperty("expenseDate") expenseDate: Date,
        @JsonProperty("description") description: String?,
        @JsonProperty("amount") amount: BigDecimal?,
        @JsonProperty("currency") currency: String?,
        @JsonProperty("category") category: String?
    ) : this(identifier, rdf.createDataset(), Headers.empty()) {
        this.rdfType = MY_RDF_TYPE_VALUE
        this.merchantProvider = merchantProvider
        this.expenseDate = expenseDate
        this.description = description
        this.amount = amount
        this.currency = currency
        this.category = category
    }
    constructor(
        identifier: URI
    ) : this(identifier, rdf.createDataset(), Headers.empty())
    /**
     * Note 5: Various getters/setters.
     * The getters and setters reference the subject's methods.
     */
    var rdfType: URI?
        get() = subject.rdfType
        set(rdfType) {
            subject.rdfType = rdfType
        }
    var merchantProvider: String?
        get() = subject.merchantProvider
        set(merchantProvider) {
            subject.merchantProvider = merchantProvider
        }
    var expenseDate: Date?
        get() = subject.expenseDate
        set(expenseDate) {
            subject.expenseDate = expenseDate
        }
    var description: String?
        get() = subject.description
        set(description) {
            subject.description = description
        }
    var amount: BigDecimal?
        get() = subject.amount
        set(amount) {
            subject.amount = amount
        }
    var currency: String?
        get() = subject.currency
        set(currency) {
            subject.currency = currency
        }
    var category: String?
        get() = subject.category
        set(category) {
            subject.category = category
        }
    /**
     * Note 6: Inner class ``Node`` that extends WrapperIRI
     * Node class handles the mapping of the expense data (date, provider,
     * description, category, priceCurrency, total) to RDF triples
     * <subject> <predicate> <object>.
     *
     * Nomenclature Background: A set of RDF triples is called a Graph.
     */
    internal class Node(original: RDFTerm, graph: Graph) :
        WrapperIRI(original, graph) {
        /**
         * Note 7: In its getters, the ``Node`` class calls WrapperBlankNodeOrIRI
         * method ``anyOrNull`` to return either 0 or 1 value mapped to the predicate.
         * You can use ValueMappings method to convert the value to a specified type.
         *
         * In its setters, the ``Node`` class calls WrapperBlankNodeOrIRI
         * method ``overwriteNullable`` to return either 0 or 1 value mapped to the predicate.
         * You can use the TermMappings method to store the value with the specified type information.
         */
        var rdfType: URI?
            get() = anyOrNull(RDF_TYPE) { term: RDFTerm, graph: Graph ->
                ValueMappings.iriAsUri(term, graph)
            }
            set(type) {
                overwriteNullable(RDF_TYPE, type) { value: URI?, graph: Graph ->
                    TermMappings.asIri(value, graph)
                }
            }
        var merchantProvider: String?
            get() = anyOrNull(SCHEMA_ORG_PROVIDER) { term: RDFTerm, graph: Graph ->
                ValueMappings.literalAsString(term, graph)
            }
            set(provider) {
                overwriteNullable(SCHEMA_ORG_PROVIDER, provider) { value: String?, graph: Graph ->
                    TermMappings.asStringLiteral(value, graph)
                }
            }
        var expenseDate: Date?
            get() {
                val expenseInstant: Instant? = anyOrNull(SCHEMA_ORG_PURCHASE_DATE) { term: RDFTerm, graph: Graph ->
                    ValueMappings.literalAsInstant(term, graph)
                }
                return if (expenseInstant != null) Date.from(expenseInstant) else null
            }
            set(expenseDate) {
                val expenseInstant: Instant? = if (expenseDate != null) expenseDate.toInstant() else null
                overwriteNullable(SCHEMA_ORG_PURCHASE_DATE, expenseInstant) { value: Instant?, graph: Graph ->
                    TermMappings.asTypedLiteral(value, graph)
                }
            }
        var description: String?
            get() = anyOrNull(SCHEMA_ORG_DESCRIPTION) { term: RDFTerm?, graph: Graph ->
                ValueMappings.literalAsString(term, graph)
            }
            set(description) {
                overwriteNullable(SCHEMA_ORG_DESCRIPTION, description) { value: String?, graph: Graph ->
                    TermMappings.asStringLiteral(value, graph)
                }
            }
        /**
         * Note 8: You can write your own TermMapping helper.
         */
        var amount: BigDecimal?
            get() {
                val priceString: String? = anyOrNull(SCHEMA_ORG_TOTAL_PRICE) { term: RDFTerm?, graph: Graph ->
                    ValueMappings.literalAsString(term, graph)
                }
                return if (priceString != null) BigDecimal(priceString) else null
            }
            set(totalPrice) {
                overwriteNullable(SCHEMA_ORG_TOTAL_PRICE, totalPrice) { value, _ ->
                    RDFFactory.getInstance().createLiteral(value.toString(),
                        RDFFactory.getInstance().createIRI("http://www.w3.org/2001/XMLSchema#decimal"))
                }
            }
        var currency: String?
            get() = anyOrNull(SCHEMA_ORG_PRICE_CURRENCY) { term: RDFTerm?, graph: Graph ->
                ValueMappings.literalAsString(term, graph)
            }
            set(currency) {
                overwriteNullable(SCHEMA_ORG_PRICE_CURRENCY, currency) { value: String?, graph: Graph ->
                    TermMappings.asStringLiteral(value, graph)
                }
            }
        var category: String?
            get() = anyOrNull(SCHEMA_ORG_CATEGORY) { term: RDFTerm?, graph: Graph ->
                ValueMappings.literalAsString(term, graph)
            }
            set(category) {
                overwriteNullable(SCHEMA_ORG_CATEGORY, category) { value: String?, graph: Graph ->
                    TermMappings.asStringLiteral(value, graph)
                }
            }
    }
}
```

{% endtab %}
{% endtabs %}

## Additional Information

For more information, see:

* [CRUD RDF Data](/sdk/javascript-sdk/read-and-write-rdf-data)
* [SolidRDFSource](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidRDFSource.html)
* [WrapperIRI](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/rdf/wrapping/commons/WrapperIRI.html)
* [WrapperBlankNodeOrIRI](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/rdf/wrapping/commons/WrapperBlankNodeOrIRI.html)
* [ValueMappings](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/rdf/wrapping/commons/ValueMappings.html)
* [TermMappings](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/rdf/wrapping/commons/TermMappings.html)


# Step 3: ExpenseController Class

For the WebService in this tutorial, the **`ExpenseController`** defines the endpoints for the various Read and Write (CRUD) operations.

## Create the ExpenseController Class

{% hint style="info" %}
Tip\
Various aspects related to CRUD operations with the Inrupt Client Libraries are noted as comments in the code. For more details, see [CRUD](/sdk/java-sdk/crud-rdf-data) .
{% endhint %}

{% tabs %}
{% tab title="Java" %}
In the **`src/main/java/com/example/gettingstarted/`** directory, create **`ExpenseController.java`** file with the content below:

```java
package com.example.gettingstarted;
import com.inrupt.client.auth.Session;
import com.inrupt.client.openid.OpenIdSession;
import com.inrupt.client.solid.SolidSyncClient;
import com.inrupt.client.webid.WebIdProfile;
import com.inrupt.client.solid.PreconditionFailedException;
import com.inrupt.client.solid.ForbiddenException;
import com.inrupt.client.solid.NotFoundException;
import org.springframework.web.bind.annotation.*;
import org.apache.commons.rdf.api.RDFSyntax;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URI;
import java.util.Set;
@RequestMapping("/api")
@RestController
public class ExpenseController {
    /**
     * Note 1: Authenticated Session
     * Using the client credentials, create an authenticated session.
     */
    final Session session = OpenIdSession.ofClientCredentials(
            URI.create(System.getenv("MY_SOLID_IDP")),
            System.getenv("MY_SOLID_CLIENT_ID"),
            System.getenv("MY_SOLID_CLIENT_SECRET"),
            System.getenv("MY_AUTH_FLOW"));
    /**
     * Note 2: SolidSyncClient
     * Instantiates a synchronous client for the authenticated session.
     * The client has methods to perform CRUD operations.
     */
    final SolidSyncClient client = SolidSyncClient.getClient().session(session);
    private final PrintWriter printWriter = new PrintWriter(System.out, true);
    /**
     * Note 3: SolidSyncClient.read()
     * Using the SolidSyncClient client.read() method, reads the user's WebID Profile document and returns the Pod URI(s).
     */
    @GetMapping("/pods")
    public Set<URI> getPods(@RequestParam(value = "webid", defaultValue = "") String webID) {
        printWriter.println("ExpenseController:: getPods");
        try (final var profile = client.read(URI.create(webID), WebIdProfile.class)) {
            return profile.getStorages();
        }
    }
    /**
     * Note 4: SolidSyncClient.create()
     * Using the SolidSyncClient client.create() method,
     * - Saves the Expense as an RDF resource to the location specified in the Expense.identifier field.
     */
    @PostMapping(path = "/expenses/create")
    public Expense createExpense(@RequestBody Expense newExpense) {
        printWriter.println("ExpenseController:: createExpense");
        try (var createdExpense = client.create(newExpense)) {
            printExpenseAsTurtle(createdExpense);
            return createdExpense;
        } catch(PreconditionFailedException e1) {
            // Errors if the resource already exists
            printWriter.println(String.format("[%s] com.inrupt.client.solid.PreconditionFailedException:: %s", e1.getStatusCode(), e1.getMessage()));
        } catch(ForbiddenException e2) {
            // Errors if user does not have access to create
            printWriter.println(String.format("[%s] com.inrupt.client.solid.ForbiddenException:: %s", e2.getStatusCode(), e2.getMessage()));
        } catch(Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * Note 5: SolidSyncClient.read()
     * Using the SolidSyncClient client.read() method,
     * - Reads the RDF resource into the Expense class.
     */
    @GetMapping("/expenses/get")
    public Expense getExpense(@RequestParam(value = "resourceURL", defaultValue = "") String resourceURL) {
        printWriter.println("ExpenseController:: getExpense");
        try (var resource = client.read(URI.create(resourceURL), Expense.class)) {
            return resource;
        } catch (NotFoundException e1) {
            // Errors if resource is not found
            printWriter.println(String.format("[%s] com.inrupt.client.solid.NotFoundException:: %s", e1.getStatusCode(), e1.getMessage()));
        } catch(ForbiddenException e2) {
            // Errors if user does not have access to read
            printWriter.println(String.format("[%s] com.inrupt.client.solid.ForbiddenException:: %s", e2.getStatusCode(), e2.getMessage()));
        } catch(Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * Note 6: SolidSyncClient.update()
     * Using the SolidSyncClient client.update() method,
     * - Updates the Expense resource.
     */
    @PutMapping("/expenses/update")
    public Expense updateExpense(@RequestBody Expense expense) {
        printWriter.println("ExpenseController:: updateExpense");
        try(var updatedExpense = client.update(expense)) {
            printExpenseAsTurtle(updatedExpense);
            return updatedExpense;
        } catch (NotFoundException e1) {
            // Errors if resource is not found
            printWriter.println(String.format("[%s] com.inrupt.client.solid.NotFoundException:: %s", e1.getStatusCode(), e1.getMessage()));
        } catch(ForbiddenException e2) {
            // Errors if user does not have access to read
            printWriter.println(String.format("[%s] com.inrupt.client.solid.ForbiddenException:: %s", e2.getStatusCode(), e2.getMessage()));
        } catch(Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * Note 7: SolidSyncClient.delete()
     * Using the SolidSyncClient client.delete() method,
     * - Deletes the resource located at the resourceURL.
     */
    @DeleteMapping("/expenses/delete")
    public void deleteExpense(@RequestParam(value = "resourceURL") String resourceURL) {
        printWriter.println("ExpenseController:: deleteExpense");
        try {
            client.delete(URI.create(resourceURL));
            // Alternatively, you can specify an Expense object to the delete method.
            // The delete method deletes  the Expense recorde located in the Expense.identifier field. 
            // For example: client.delete(new Expense(URI.create(resourceURL)));
        } catch (NotFoundException e1) {
            // Errors if resource is not found
            printWriter.println(String.format("[%s] com.inrupt.client.solid.NotFoundException:: %s", e1.getStatusCode(), e1.getMessage()));
        } catch(ForbiddenException e2) {
            // Errors if user does not have access to read
            printWriter.println(String.format("[%s] com.inrupt.client.solid.ForbiddenException:: %s", e2.getStatusCode(), e2.getMessage()));
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * Note 8: Prints the expense resource in Turtle.
     */
    private void printExpenseAsTurtle(Expense expense) {
        printWriter.println("ExpenseController:: printExpenseAsTurtle");
        ByteArrayOutputStream content = new ByteArrayOutputStream();
        try  {
            expense.serialize(RDFSyntax.TURTLE, content);
            printWriter.println(content.toString("UTF-8"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
```

{% endtab %}

{% tab title="Kotlin" %}
In the **`src/main/kotlin/com/example/gettingstarted/`** directory, create an **`ExpenseController.kt`** Kotlin class with the following content:

```kotlin
package com.example.gettingstarted
import com.inrupt.client.openid.OpenIdSession
import com.inrupt.client.solid.SolidSyncClient
import com.inrupt.client.webid.WebIdProfile
import com.inrupt.client.solid.PreconditionFailedException
import com.inrupt.client.solid.ForbiddenException
import com.inrupt.client.solid.NotFoundException
import org.springframework.web.bind.annotation.*
import java.io.PrintWriter
import java.net.URI
import org.apache.commons.rdf.api.RDFSyntax
import java.io.ByteArrayOutputStream
import java.io.IOException
@RequestMapping("/api")
@RestController
class ExpenseController {
    /**
     * Note 1: Authenticated Session
     * Using the client credentials, create an authenticated session.
     */
    val session = OpenIdSession.ofClientCredentials(
        URI.create(System.getenv("MY_SOLID_IDP")),
        System.getenv("MY_SOLID_CLIENT_ID"),
        System.getenv("MY_SOLID_CLIENT_SECRET"),
        System.getenv("MY_AUTH_FLOW")
    )
    /**
     * Note 2: SolidSyncClient
     * Instantiates a synchronous client for the authenticated session.
     * The client has methods to perform CRUD operations.
     */
    val client = SolidSyncClient.getClient().session(session)
    private val printWriter = PrintWriter(System.out, true)
    /**
     * Note 3: SolidSyncClient.read()
     * Using the SolidSyncClient client.read() method, reads the user's WebID Profile document and returns the Pod URI(s).
     */
    @GetMapping("/pods")
    fun getPods(@RequestParam(value = "webid", defaultValue = "") webID: String): Set<URI> {
        printWriter.println("ExpenseController:: getPods")
        client.read(URI.create(webID), WebIdProfile::class.java).use { profile -> return profile.storages }
    }
    /**
     * Note 4: SolidSyncClient.create()
     * Using the SolidSyncClient client.create() method,
     * - Saves the Expense as an RDF resource to the location specified in the Expense.identifier field.
     */
    @PostMapping("/expenses/create")
    fun createExpense(@RequestBody newExpense: Expense): Expense? {
        printWriter.println("ExpenseController:: createExpense")
        try {
           val createdExpense = client.create(newExpense)
           printExpenseAsTurtle(createdExpense)
           return createdExpense
        } catch(e1: PreconditionFailedException) {
            // Errors if the resource already exists
            printWriter.println(String.format("[%s] com.inrupt.client.solid.PreconditionFailedException:: %s", e1.statusCode, e1.localizedMessage))
        } catch(e2: ForbiddenException) {
            // Errors if user does not have access to create
            printWriter.println(String.format("[%s] com.inrupt.client.solid.ForbiddenException:: %s", e2.statusCode, e2.localizedMessage))
        } catch(e: Exception) {
            e.printStackTrace()
        }
        return null
    }
    /**
     * Note 5: SolidSyncClient.read()
     * Using the SolidSyncClient client.read() method,
     * - Reads the RDF resource into the Expense class.
     */
    @GetMapping("/expenses/get")
    fun getExpense(
        @RequestParam(
            value = "resourceURL", defaultValue = ""
        ) resourceURL: String
    ): Expense? {
        printWriter.println("ExpenseController:: getExpense")
        try {
            client.read(
                URI.create(resourceURL), Expense::class.java
            ).use { resource -> return resource }
        } catch(e1: NotFoundException) {
            // Errors if the resource is not found
            printWriter.println(String.format("[%s] com.inrupt.client.solid.NotFoundException:: %s", e1.statusCode, e1.localizedMessage))
        } catch(e2: ForbiddenException) {
            // Errors if user does not have access to read
            printWriter.println(String.format("[%s] com.inrupt.client.solid.ForbiddenException:: %s", e2.statusCode, e2.localizedMessage))
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return null
    }
    /**
     * Note 6: SolidSyncClient.update()
     * Using the SolidSyncClient client.update() method,
     * - Updates the Expense resource.
     */
    @PutMapping("/expenses/update")
    fun updateExpense(@RequestBody expense: Expense): Expense? {
        printWriter.println("ExpenseController:: updateExpense")
        try {
           val updatedExpense = client.update(expense)
           printExpenseAsTurtle(updatedExpense)
           return updatedExpense
        } catch(e1: NotFoundException) {
            // Errors if the resource is not found
            printWriter.println(String.format("[%s] com.inrupt.client.solid.NotFoundException:: %s", e1.statusCode, e1.localizedMessage))
        } catch(e2: ForbiddenException) {
            // Errors if user does not have access to read
            printWriter.println(String.format("[%s] com.inrupt.client.solid.ForbiddenException:: %s", e2.statusCode, e2.localizedMessage))
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return null
    }
    /**
     * Note 7: SolidSyncClient.delete()
     * Using the SolidSyncClient client.delete() method,
     * - Deletes the resource located at the resourceURL.
     */
    @DeleteMapping("/expenses/delete")
    fun deleteExpense(@RequestParam(value = "resourceURL") resourceURL: String) {
        printWriter.println("ExpenseController:: deleteExpense")
        try {
            client.delete(URI.create(resourceURL))
            // Alternatively, you can specify an Expense object to the delete method.
            // The delete method deletes  the Expense recorde located in the Expense.identifier field.
            // For example: client.delete(Expense(URI.create(resourceURL)))
        } catch(e1: NotFoundException) {
            // Errors if the resource is not found
            printWriter.println(String.format("[%s] com.inrupt.client.solid.NotFoundException:: %s", e1.statusCode, e1.localizedMessage))
        } catch(e2: ForbiddenException) {
            // Errors if user does not have access to read
            printWriter.println(String.format("[%s] com.inrupt.client.solid.ForbiddenException:: %s", e2.statusCode, e2.localizedMessage))
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
    /**
     * Note 8: Prints the expense resource in Turtle.
     */
    fun printExpenseAsTurtle(
        expense: Expense
    ) {
        printWriter.println("ExpenseController:: printExpenseAsTurtle")
        val content = ByteArrayOutputStream()
        try  {
            expense.serialize(RDFSyntax.TURTLE, content)
            printWriter.println(content.toString("UTF-8"))
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }
}
```

{% endtab %}
{% endtabs %}


# Step 4: Run (Part 1)

## Run Your Local Web Server

Open a terminal window.

### Enter Your Client Credentials

{% hint style="danger" %}
Safeguard your **`Client ID`** and **`Client Secret`** values. Do not share these with any third parties as anyone with your **`Client ID`** and **`Client Secret`** values can impersonate you and act fully on your behalf.
{% endhint %}

Export your registered client credentials (see the [Prerequisites](/sdk/java-sdk/tutorial/prerequisites)) as environment variables.

1\. Identity Provider (the IDP with whom you registered your application):

```sh
read -s MY_SOLID_IDP && export MY_SOLID_IDP
```

Enter `https://login.inrupt.com`

2\. Client ID:

```sh
read -s MY_SOLID_CLIENT_ID && export MY_SOLID_CLIENT_ID
```

Enter your Client ID.

3\. Client Secret:

```sh
read -s MY_SOLID_CLIENT_SECRET && export MY_SOLID_CLIENT_SECRET
```

Enter your Client Secret.

4\. Authentication Flow Method:

```sh
read -s MY_AUTH_FLOW && export MY_AUTH_FLOW
```

Enter `client_secret_basic`

### Run the Application

Once you have entered your client credentials, start your application. From your project ( `getting-started/` ) directory, run your Spring Boot application:

* For Java, this tutorial assumes a Spring Boot Web Maven Project.
* For Kotlin, this tutorial assumes a Spring Boot Web Gradle Project.

{% tabs %}
{% tab title="Java" %}

```sh
./mvnw spring-boot:run
```

{% endtab %}

{% tab title="Kotlin" %}

```sh
./gradlew bootRun
```

{% endtab %}
{% endtabs %}

Your Web service runs on `http://localhost:8080` .

{% hint style="info" %}
Reminder\
The application is running **as** you, the user who registered it.
{% endhint %}

## Test the Service

Open another terminal window. To test, call the various endpoints defined in the **`ExpenseController`** class:

<table><thead><tr><th width="223.58984375">Endpoint</th><th>Description</th></tr></thead><tbody><tr><td><strong><code>/api/pods</code></strong></td><td>Gets Pod URL(s) from the WebID Profile.</td></tr><tr><td><strong><code>/api/expenses/create</code></strong></td><td>Saves an Expense object as a new RDF resource. Returns the saved Expense object.</td></tr><tr><td><strong><code>/api/expenses/get</code></strong></td><td>Reads an RDF resource as an Expense object. Returns the Expense object.</td></tr><tr><td><strong><code>/api/expenses/update</code></strong></td><td><p>Saves an Expense object as an RDF resource.</p><ul><li>If a resource already exists, overwrites the existing resource.</li><li>If no resource exists, creates a new resource.</li></ul><p>Returns the saved Expense object.</p></td></tr><tr><td><strong><code>/api/expenses/delete</code></strong></td><td>Deletes an Expense resource (i.e., the RDF resource associated with the <strong><code>Expense</code></strong>).</td></tr></tbody></table>

For simplicity, the calls to the Web Server uses **`curl`** . However, you can access the endpoints from your front-end app as well.

### Get Pod URL

To find your Pod URL, issue the following **`curl`** command, substituting your WebID (e.g., **`https://id.inrupt.com/yourUserName`** ):

```sh
curl -X GET http://localhost:8080/api/pods\?webid\=SUBSTITUTE_YOUR_WEBID
```

Upon success, the operation should return an array with your Pod Root URL; for example:

```sh
["https://storage.inrupt.com/your-root-container/"]
```

Since the application is running as you, it should have access to your Pod for the following CRUD operations.

{% hint style="info" %}
Note\
In the following CRUD operations, substitue **`your-root-container`** with the value of your root container.
{% endhint %}

### Create an Expense Record

To create an expense record as an RDF resource on your Pod at **`https://storage.inrupt.com/your-root-container/expenses/20230315/expense1`** , issue the following **`curl`** command, <mark style="color:red;">**substituting**</mark> your root container in the request body:

<pre class="language-sh"><code class="lang-sh">curl -X POST http://localhost:8080/api/expenses/create \
   -H 'Content-type:application/json'  \
   -d '{
<strong>      "identifier": "https://storage.inrupt.com/your-root-container/expenses/20230315/expense1",
</strong>      "merchantProvider": "Example Restaurant",
      "description": "Team Lunch",
      "expenseDate": "2023-03-06",
      "amount": 100,
      "currency": "USD",
      "category": "Travel &#x26; Entertainment" }'
</code></pre>

{% hint style="info" %}
Tip

* If you encounter a `ForbiddenException` , double check that you have substituted `your-root-container` in the command.
* If you encounter a `PreconditionFailedException` , check that the resource does not already exist at the the specified identifier. The `.create()` operation errors with `PreconditionFailedException` if a resource already exists.
  {% endhint %}

Upon success, the operation returns the created Expense object:

```json
{
    "identifier": "https://storage.inrupt.com/your-root-container/expenses/20230315/expense1",
    "merchantProvider": "Example Restaurant",
    "expenseDate": "2023-03-06T00:00:00.000+00:00",
    "description": "Team Lunch",
    "amount": 100,
    "currency": "USD",
    "category": "Travel & Entertainment",
    "rdftype": "https://schema.org/Invoice"
}
```

For illustrative purposes, the server also prints out the content of the resource, formatted in Turtle:

```turtle
<https://storage.inrupt.com/your-root-container/expenses/20230315/expense1>
        a                              <https://schema.org/Invoice> ;
        <https://schema.org/purchaseDate>  "2023-03-06T00:00:00Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> ;
        <https://schema.org/category>  "Travel & Entertainment" ;
        <https://schema.org/description>
                "Team Lunch" ;
        <https://schema.org/priceCurrency>
                "USD" ;
        <https://schema.org/provider>  "Example Restaurant" ;
        <https://schema.org/totalPrice>
                "100"^^<http://www.w3.org/2001/XMLSchema#decimal> .
```

See also [CRUD Module](/sdk/java-sdk/crud-data).

### Read the Expense RDF Resource

To read the content at **`https://storage.inrupt.com/your-root-container/expenses/20230315/expense1`** , map it to the **`Expense`** class and return it serialized as a JSON, issue the following **`curl`** command, <mark style="color:red;">**substituting**</mark> your your root container :

```sh
curl -X GET http://localhost:8080/api/expenses/get\?resourceURL\=https://storage.inrupt.com/your-root-container/expenses/20230315/expense1
```

{% hint style="info" %}
Tip\
If you encounter an `HTTP 403 Forbidden` error, double check that you have substituted `your-root-container` in the command.
{% endhint %}

Upon success, the operation should return the contents as JSON:

```json
{"identifier":"https://storage.inrupt.com/your-root-container/expenses/20230315/expense1","merchantProvider":"Example Restaurant","expenseDate":"2023-03-06T00:00:00.000+00:00","description":"Team Lunch","amount":100,"currency":"USD","category":"Travel & Entertainment","rdftype":"https://schema.org/Invoice"}
```

See also [CRUD Module](/sdk/java-sdk/crud-data).

### Update the Expense RDF Resource

To update the content at **`https://storage.inrupt.com/your-root-container/expenses/20230315/expense1`** , issue the following **`curl`** command, <mark style="color:red;">**substituting**</mark> your your root container in the request body (the **`expenseDate`** field has changed):

<pre class="language-sh"><code class="lang-sh">
curl -X PUT http://localhost:8080/api/expenses/update \
   -H 'Content-type:application/json'  \
   -d '{
<strong>      "identifier": "https://storage.inrupt.com/your-root-container/expenses/20230315/expense1",
</strong>      "merchantProvider": "Example Restaurant",
      "description": "Team Lunch",
      "expenseDate": "2023-03-15",
      "amount": 100,
      "currency": "USD",
      "category": "Travel &#x26; Entertainment" }'
</code></pre>

{% hint style="info" %}
Tip\
If you encounter an `HTTP 403 Forbidden` error, double check that you have substituted `your-root-container` in the command.
{% endhint %}

Upon success, the operation should return the updated Expense object as JSON (as well as print out, on the server-side, the content formatted in Turtle):

{% tabs %}
{% tab title="Returned Expense Object" %}

```json
{
    "identifier": "https://storage.inrupt.com/your-root-container/expenses/20230315/expense1",
    "merchantProvider": "Example Restaurant",
    "expenseDate": "2023-03-15T00:00:00.000+00:00",
    "description": "Team Lunch",
    "amount": 100,
    "currency": "USD",
    "category": "Travel & Entertainment",
    "rdftype": "https://schema.org/Invoice"
}
```

{% endtab %}

{% tab title="Content Formatted as Turtle" %}

```turtle
<https://storage.inrupt.com/your-root-container/expenses/20230315/expense1>
     a                              <https://schema.org/Invoice> ;
     <https://schema.org/purchaseDate>
             "2023-03-15T00:00:00Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> ;
     <https://schema.org/category>  "Travel & Entertainment" ;
     <https://schema.org/description>
             "Team Lunch" ;
     <https://schema.org/priceCurrency>
             "USD" ;
     <https://schema.org/provider>  "Example Restaurant" ;
     <https://schema.org/totalPrice>
             "100"^^<http://www.w3.org/2001/XMLSchema#decimal> .
```

{% endtab %}
{% endtabs %}

Unlike the **`POST`** request to **`http://localhost:8080/api/expenses/create`** (which uses the **`.create()`** method), the **`PUT`** request to **`http://localhost:8080/api/expenses/update`** (which uses the **`.update()`** method) can either:

* Update an existing resource, or
* Create a new resource if it does not exists.

For example, issue the following **`PUT`** request to the **`api/expenses/update`** endpoint to create another expense resource, <mark style="color:red;">**substitute**</mark> your root container:

<pre class="language-sh"><code class="lang-sh">curl -X PUT http://localhost:8080/api/expenses/update \
   -H 'Content-type:application/json'  \
   -d '{
<strong>      "identifier": "https://storage.inrupt.com/your-root-container/expenses/20230315/expense2",
</strong><strong>      "merchantProvider": "Example Supply Store",
</strong>      "description": "Monitor",
      "expenseDate": "2023-03-15",
      "amount": 300,
      "currency": "USD",
      "category": "Office Equipment &#x26; Supplies" }'
</code></pre>

Upon success, the operation should return the created Expense object as JSON (as well as print out, on the server-side, the content formatted in Turtle):

{% tabs %}
{% tab title="Returned Expense Object" %}

```json
{
    "identifier": "https://storage.inrupt.com/your-root-container/expenses/20230315/expense2",
    "merchantProvider": "Example Supply Store",
    "expenseDate": "2023-03-15T00:00:00.000+00:00",
    "description": "Monitor",
    "amount": 300,
    "currency": "USD",
    "category": "Office Equipment & Supplies",
    "rdftype": "https://schema.org/Invoice"
}
```

{% endtab %}

{% tab title="Content Formatted as Turtle" %}

```turtle
<https://storage.inrupt.com/your-root-container/expenses/20230315/expense2>
     a                              <https://schema.org/Invoice> ;
     <https://schema.org/purchaseDate>
             "2023-03-15T00:00:00Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> ;
     <https://schema.org/category>  "Office Equipment & Supplies" ;
     <https://schema.org/description>
             "Monitor" ;
     <https://schema.org/priceCurrency>
             "USD" ;
     <https://schema.org/provider>  "Example Supply Store" ;
     <https://schema.org/totalPrice>
             "300"^^<http://www.w3.org/2001/XMLSchema#decimal> .
```

{% endtab %}
{% endtabs %}

See also [CRUD Module](/sdk/java-sdk/crud-data).

### Delete the Expense RDF Resource

To delete the resource from your Pod, issue the following **`DELETE`** request, <mark style="color:red;">**substituting**</mark> your root container :

```sh
curl -X DELETE http://localhost:8080/api/expenses/delete\?resourceURL\=https://storage.inrupt.com/your-root-container/expenses/20230315/expense2
```

{% hint style="info" %}
Tip\
If you encounter a **`ForbiddenException`** , double check that you have substituted **`your-root-container`** in the command.
{% endhint %}

To verify, issue a **`GET`** request to the **`http://localhost:8080/api/expenses/get`** endpoint:

```sh
curl -X GET http://localhost:8080/api/expenses/get\?resourceURL\=https://storage.inrupt.com/your-root-container/expenses/20230315/expense2
```

The operation should error with a **`NotFoundException`** .

See also [CRUD Module](/sdk/java-sdk/crud-data).


# Step 5: Add receipt.png

You can use the Java Client Libraries to save non-RDF resources (e.g., **`.png`** , **`.pdf`** ) to your Pod.

For this part of the tutorial, the getting started app uses Inrupt’s Java client library to:

* Store a copy of the expense receipts to your Pod.
* Update the associated expense to link to the URLs of the receipts.

{% hint style="info" %}
You can find the complete code at [Complete Code](/sdk/java-sdk/tutorial/step5/code).
{% endhint %}

## Update **`application.properties`**

Add the following properties to the **`src/main/resources/application.properties`** file:

```json
spring.servlet.multipart.max-file-size=128KB
spring.servlet.multipart.max-request-size=128KB
```

## Modify **`Expense`** Class

1. Open **`Expense`** class file:

{% tabs %}
{% tab title="Java" %}
Open `src/main/java/com/example/gettingstarted/Expense.java`
{% endtab %}

{% tab title="Kotlin" %}
Open `src/main/kotlin/com/example/gettingstarted/Expense.kt`
{% endtab %}
{% endtabs %}

2. Add the **`java.util.*`** import statement:

{% tabs %}
{% tab title="Java" %}

```java
import java.util.*;
```

{% endtab %}

{% tab title="Kotlin" %}

```kotlin
import java.util.*
```

{% endtab %}
{% endtabs %}

3. Add the predicate definition for the receipts in the **`Expense`** class:

{% tabs %}
{% tab title="Java" %}

```java
static IRI SCHEMA_ORG_IMAGE = rdf.createIRI("https://schema.org/image");
```

{% endtab %}

{% tab title="Kotlin" %}

```kotlin
var SCHEMA_ORG_IMAGE: IRI = rdf.createIRI("https://schema.org/image")
```

{% endtab %}
{% endtabs %}

4. Update the **`Expense`** class constructor to include the receipts:

{% tabs %}
{% tab title="Java" %}

<pre class="language-java"><code class="lang-java">@JsonCreator
public Expense(@JsonProperty(&#x26;quot;identifier&#x26;quot;) final URI identifier,
               @JsonProperty(&#x26;quot;merchantProvider&#x26;quot;) String merchantProvider,
               @JsonProperty(&#x26;quot;expenseDate&#x26;quot;) Date expenseDate,
               @JsonProperty(&#x26;quot;description&#x26;quot;) String description,
               @JsonProperty(&#x26;quot;amount&#x26;quot;) BigDecimal amount,
               @JsonProperty(&#x26;quot;currency&#x26;quot;) String currency,
               @JsonProperty(&#x26;quot;category&#x26;quot;) String category,
<strong>               @JsonProperty(&#x26;quot;receipts&#x26;quot;) String[] receipts) {
</strong>    this(identifier);&#x3C;/strong>
    this.setRDFType(MY_RDF_TYPE_VALUE);
    this.setMerchantProvider(merchantProvider);
    this.setExpenseDate(expenseDate);
    this.setDescription(description);
    this.setAmount(amount);
    this.setCurrency(currency);
    this.setCategory(category);
<strong>    this.setReceipts(receipts);
</strong>}
</code></pre>

{% endtab %}

{% tab title="Kotlin" %}

<pre class="language-kotlin"><code class="lang-kotlin">@JsonCreator
constructor(
    @JsonProperty(&#x26;quot;identifier&#x26;quot;) identifier: URI,
    @JsonProperty(&#x26;quot;merchantProvider&#x26;quot;) merchantProvider: String?,
    @JsonProperty(&#x26;quot;expenseDate&#x26;quot;) expenseDate: Date,
    @JsonProperty(&#x26;quot;description&#x26;quot;) description: String?,
    @JsonProperty(&#x26;quot;amount&#x26;quot;) amount: BigDecimal?,
    @JsonProperty(&#x26;quot;currency&#x26;quot;) currency: String?,
    @JsonProperty(&#x26;quot;category&#x26;quot;) category: String?,
<strong>    @JsonProperty(&#x26;quot;receipts&#x26;quot;) receipts: Set&#x3C;String>?
</strong>) : this(identifier, rdf.createDataset(), Headers.empty()) {
    this.rdfType = MY_RDF_TYPE_VALUE
    this.merchantProvider = merchantProvider
    this.expenseDate = expenseDate
    this.description = description
    this.amount = amount
    this.currency = currency
    this.category = category
<strong>    this.receipts = receipts
</strong>}
</code></pre>

{% endtab %}
{% endtabs %}

5. Add the getter and setters for the receipts in the **`Expense`** class:

{% tabs %}
{% tab title="Java" %}

```java
public Set<String> getReceipts() {
    return subject.getReceipts();
}
// Note:: The setters first uses the getter, which returns a Set, and adds the receipt to the set.
public void addReceipt(String receipt) {
    subject.getReceipts().add(receipt);
}
public void setReceipts(String[] receipts) {
    subject.getReceipts().addAll(List.of(receipts));
}
```

{% endtab %}

{% tab title="Kotlin" %}

```kotlin
// Note:: The setters first uses the getter, which returns a Set, and adds the receipt to the set.

var receipts: Set<String>?
    get() = subject.receipts
    set(receipts) {
        if (receipts != null) {
            subject.receipts.addAll(receipts)
        }
    }
fun addReceipt(receipt: String) {
    subject.receipts.add(receipt)
}

```

{% endtab %}
{% endtabs %}

6. Add getter for the receipts in the inner **`Node`** class:

{% tabs %}
{% tab title="Java" %}

```java
public Set<String> getReceipts() {
    return objects(SCHEMA_ORG_IMAGE, TermMappings::asIri, ValueMappings::iriAsString);
}
```

{% endtab %}

{% tab title="Kotlin" %}

```kotlin
val receipts: MutableSet<String>
    get() = objects(SCHEMA_ORG_IMAGE, TermMappings::asIri, ValueMappings::iriAsString)
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
Note\
For more detail with regards to handling lists, see [https://github.com/inrupt/docs-gitbook/tree/main/sdk/java-sdk/tutorial/step5/broken-reference/README.md](https://github.com/inrupt/docs-gitbook/tree/main/sdk/java-sdk/tutorial/step5/broken-reference/README.md "mention").
{% endhint %}

## Modify **`ExpenseController`** Class

1. Open **`ExpenseController`** class file.

{% tabs %}
{% tab title="Java" %}
Open `src/main/java/com/example/gettingstarted/ExpenseController.java`
{% endtab %}

{% tab title="Kotlin" %}
Open `src/main/kotlin/com/example/gettingstarted/ExpenseController.kt`
{% endtab %}
{% endtabs %}

2. Add the following import statements:

{% tabs %}
{% tab title="Java" %}

```java
import com.inrupt.client.solid.SolidNonRDFSource;
import org.springframework.web.multipart.MultipartFile;
```

{% endtab %}

{% tab title="Kotlin" %}

```kotlin
import com.inrupt.client.solid.SolidNonRDFSource
import org.springframework.web.multipart.MultipartFile
```

{% endtab %}
{% endtabs %}

3. Add the following method to handle the upload of non-RDF files:

{% tabs %}
{% tab title="Java" %}

```java

/**
 * Note 9: Stores a non-RDF resource to a Pod
 *
 * Using SolidNonRDFSource and the SolidSyncClient .create() method,
 * - Saves a non-RDF resource at the destinationURL.
 */
@PutMapping("**/resource/nonRDF/add**")
public String addNonRDFFile(@RequestParam(value = "destinationURL") String destinationURL,
                            @RequestParam(value = "file") MultipartFile file) {
    printWriter.println("In addNonRDFFile:: Save Non-RDF File to Pod.");
    try (final var fileStream = file.getInputStream()) {
        SolidNonRDFSource myNonRDFFile = new SolidNonRDFSource(URI.create(destinationURL), file.getContentType(), fileStream);
        return client.create(myNonRDFFile).getIdentifier().toString();
    } catch(PreconditionFailedException e1) {
        // Errors if the resource already exists
        printWriter.println(String.format("[%s] com.inrupt.client.solid.PreconditionFailedException in addNonRDFFile:: %s", e1.getStatusCode(), e1.getMessage()));
    } catch(ForbiddenException e2) {
        // Errors if user does not have access to create
        printWriter.println(String.format("[%s] com.inrupt.client.solid.ForbiddenException in addNonRDFFile:: %s", e2.getStatusCode(), e2.getMessage()));
    } catch(Exception e) {
        e.printStackTrace();
    }
    return null;
}

```

{% endtab %}

{% tab title="Kotlin" %}

```kotlin
/**
 * Note 9: Stores a non-RDF resource to a Pod
 *
 * Using SolidNonRDFSource and the SolidSyncClient's .create() method,
 * - Saves a non-RDF resource at the destinationURL.
 */
@PutMapping("**/resource/nonRDF/add**")
fun addNonRDFFile(
    @RequestParam(value = "destinationURL") destinationURL: String,
    @RequestParam(value = "file") file: MultipartFile
): String? {
    printWriter.println("In addNonRDFFile: Save Non-RDF File to Pod.")
    try {
        file.inputStream.use { fileStream ->
           val myNonRDFFile: SolidNonRDFSource = SolidNonRDFSource(URI.create(destinationURL), file.contentType, fileStream)
           return client.create(myNonRDFFile).identifier.toString()
        }
    } catch(e1: PreconditionFailedException) {
        // Errors if the resource already exists
        printWriter.println(String.format("[%s] com.inrupt.client.solid.PreconditionFailedException in addNonRDFFile:: %s", e1.statusCode, e1.localizedMessage))
    } catch(e2: ForbiddenException) {
        // Errors if user does not have access to create
        printWriter.println(String.format("[%s] com.inrupt.client.solid.ForbiddenException in addNonRDFFile:: %s", e2.statusCode, e2.localizedMessage))
    } catch (e: Exception) {
        e.printStackTrace()
    }
    return ""
}

```

{% endtab %}
{% endtabs %}

4. Add the following method that uploads a receipt file and links it to the associated **`Expense`**.

{% tabs %}
{% tab title="Java" %}

```java
/**
 * Note 10: Stores a non-RDF resource (image of the receipt) to a Pod and Attach to an Expense
 * Using methods defined as part of getting started, addReceiptToExpense:
 * - Calls addNonRDFFile() to store the receipt to a Pod
 * - Calls getExpense() to fetch the associated Expense RDF resource.
 * - Calls the Expense's setter `addReceipt` to add the link to the saved receipt.
 * - Calls updateExpense() to save the updated Expense.
 */
@PutMapping("**/expenses/receipts/add**")
public Expense addReceiptToExpense(@RequestParam(value = "destinationURL") String destinationURL,
                                   @RequestParam(value = "file") MultipartFile file,
                                   @RequestParam(value = "expenseURL") String expenseURL) {
    printWriter.println("In addReceiptToExpense: Save Receipt File to Pod and Update Associated Expense.");
    try {
        String receiptLocation = addNonRDFFile(destinationURL, file);
        if (receiptLocation != null) {
            Expense expense = getExpense(expenseURL);
            expense.addReceipt(receiptLocation);
            return updateExpense(expense);
        } else {
            printWriter.println("Error adding receipt");
            return null;
        }
    } catch(ForbiddenException e2) {
        // Errors if user does not have access to read or update the Expense resource
        printWriter.println(String.format("[%s] com.inrupt.client.solid.ForbiddenException in addReceiptToExpense:: %s", e2.getStatusCode(), e2.getMessage()));
    } catch(Exception e) {
        e.printStackTrace();
    }
    return null;
}
```

{% endtab %}

{% tab title="Kotlin" %}

```kotlin
/**
 * Note 10: Stores a non-RDF resource (image of the receipt) to a Pod and Attach to an Expense
 * Using methods defined as part of getting started, addReceiptToExpense:
 * - Calls addNonRDFFile() to store the receipt to a Pod
 * - Calls getExpense() to fetch the associated Expense RDF resource.
 * - Calls the Expense's setter `addReceipt` to add the link to the saved receipt.
 * - Calls updateExpense() to save the updated Expense.
 */
@PutMapping("**/expenses/receipts/add**")
fun addReceiptToExpense(
    @RequestParam(value = "destinationURL") destinationURL: String,
    @RequestParam(value = "file") file: MultipartFile,
    @RequestParam(value = "expenseURL") expenseURL: String
): Expense? {
    printWriter.println("In AddReceiptToExpense: Save Receipt File to Pod and Update Associated Expense.")
    val receiptLocation = addNonRDFFile(destinationURL, file)
    return if (!receiptLocation.isNullOrEmpty()) {
        val expense = getExpense(expenseURL)
        if (expense != null) {
            expense.addReceipt(receiptLocation)
            updateExpense(expense)
        } else {
            null
        }
    } else {
        printWriter.println("Error adding receipt")
        null
    }
}
```

{% endtab %}
{% endtabs %}


# Complete Code

## Expense Class

{% tabs %}
{% tab title="Java" %}

```java
package com.example.gettingstarted;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.inrupt.client.Headers;
import com.inrupt.client.solid.SolidRDFSource;
import com.inrupt.rdf.wrapping.commons.RDFFactory;
import com.inrupt.rdf.wrapping.commons.TermMappings;
import com.inrupt.rdf.wrapping.commons.ValueMappings;
import com.inrupt.rdf.wrapping.commons.WrapperIRI;
import org.apache.commons.rdf.api.Dataset;
import org.apache.commons.rdf.api.Graph;
import org.apache.commons.rdf.api.IRI;
import org.apache.commons.rdf.api.RDFTerm;
import java.math.BigDecimal;
import java.net.URI;
import java.time.Instant;
import java.util.Date;
import java.util.Objects;
import java.util.*;
/**
 * Part 1
 * Note: extends SolidRDFSource
 * To model the Expense class as an RDF resource, the Expense class extends SolidRDFSource.
 * <p>
 * The @JsonIgnoreProperties annotation is added to ignore non-class-member fields
 * when serializing Expense data as JSON.
 */
@JsonIgnoreProperties(value = { "metadata", "headers", "graph", "graphNames", "entity", "contentType" })
public class Expense extends SolidRDFSource {
    /**
     * Note 2a: Predicate Definitions
     * The following constants define the Predicates used in our triple statements.
     */
    static IRI RDF_TYPE = rdf.createIRI("http://www.w3.org/1999/02/22-rdf-syntax-ns#type");
    static IRI SCHEMA_ORG_PURCHASE_DATE = rdf.createIRI("https://schema.org/purchaseDate");
    static IRI SCHEMA_ORG_PROVIDER = rdf.createIRI("https://schema.org/provider");
    static IRI SCHEMA_ORG_DESCRIPTION = rdf.createIRI("https://schema.org/description");
    static IRI SCHEMA_ORG_TOTAL_PRICE = rdf.createIRI("https://schema.org/totalPrice");
    static IRI SCHEMA_ORG_PRICE_CURRENCY = rdf.createIRI("https://schema.org/priceCurrency");
    static IRI SCHEMA_ORG_CATEGORY = rdf.createIRI("https://schema.org/category");
    
    // Added predicate for receipts
    static IRI SCHEMA_ORG_IMAGE = rdf.createIRI("https://schema.org/image");
    /**
     * Note 2b: Value Definition
     * The following constant define the value for the predicate RDF_TYPE.
     */
    static URI MY_RDF_TYPE_VALUE = URI.create("https://schema.org/Invoice");
    /**
     * Note 3: Node class
     * The Node class is an inner class (defined below) that handles the mapping between expense data and RDF triples.
     * The subject contains the expense data.
     */
    private final Node subject;
    /**
     * Note 4: Constructors
     * Expense constructors to handle SolidResource fields:
     * - identifier: The destination URI of the resource; e.g., https://myPod.example.com/myPod/expense1
     * - dataset: The org.apache.commons.rdf.api.Dataset that corresponding to the resource.
     * - headers:  The com.inrupt.client.Headers that contains HTTP header information.
     * <p>
     * In addition, the subject field is initialized.
     */
    public Expense(final URI identifier, final Dataset dataset, final Headers headers) {
        super(identifier, dataset, headers);
        this.subject = new Node(rdf.createIRI(identifier.toString()), getGraph());
    }
    public Expense(final URI identifier) {
        this(identifier, null, null);
    }
    // Constructor updated to handle receipts
    @JsonCreator
    public Expense(@JsonProperty("identifier") final URI identifier,
                   @JsonProperty("merchantProvider") String merchantProvider,
                   @JsonProperty("expenseDate") Date expenseDate,
                   @JsonProperty("description") String description,
                   @JsonProperty("amount") BigDecimal amount,
                   @JsonProperty("currency") String currency,
                   @JsonProperty("category") String category,
                   @JsonProperty("receipts") String[] receipts) {
        this(identifier);
        this.setRDFType(MY_RDF_TYPE_VALUE);
        this.setMerchantProvider(merchantProvider);
        this.setExpenseDate(expenseDate);
        this.setDescription(description);
        this.setAmount(amount);
        this.setCurrency(currency);
        this.setCategory(category);
        this.setReceipts(receipts);
    }
    /**
     * Note 5: Various getters/setters.
     * The getters and setters reference the subject's methods.
     */
    public URI getRDFType() {
        return subject.getRDFType();
    }
    public void setRDFType(URI rdfType) {
        subject.setRDFType(rdfType);
    }
    public String getMerchantProvider() {
        return subject.getMerchantProvider();
    }
    public void setMerchantProvider(String merchantProvider) {
        subject.setMerchantProvider(merchantProvider);
    }
    public Date getExpenseDate() {
        return subject.getExpenseDate();
    }
    public void setExpenseDate(Date expenseDate) {
        subject.setExpenseDate(expenseDate);
    }
    public String getDescription() {
        return subject.getDescription();
    }
    public void setDescription(String description) {
        subject.setDescription(description);
    }
    public BigDecimal getAmount() {
        return subject.getAmount();
    }
    public void setAmount(BigDecimal amount) {
        subject.setAmount(amount);
    }
    public String getCurrency() {
        return subject.getCurrency();
    }
    public void setCurrency(String currency) {
        subject.setCurrency(currency);
    }
    public String getCategory() {
        return subject.getCategory();
    }
    public void setCategory(String category) {
        subject.setCategory(category);
    }
    // Expense class: getter and setters for receipts
    public Set<String> getReceipts() {
        return subject.getReceipts();
    }
    // Note:: The setters first uses the getter, which returns a Set, and adds the receipt to the set.
    public void addReceipt(String receipt) {
        subject.getReceipts().add(receipt);
    }
    public void setReceipts(String[] receipts) {
        subject.getReceipts().addAll(List.of(receipts));
    }
    /**
     * Note 6: Inner class ``Node`` that extends WrapperIRI
     * Node class handles the mapping of the expense data (date, provider,
     * description, category, priceCurrency, total) to RDF triples
     * <subject> <predicate> <object>.
     * <p>
     * Nomenclature Background: A set of RDF triples is called a Graph.
     */
    class Node extends WrapperIRI {
        Node(final RDFTerm original, final Graph graph) {
            super(original, graph);
        }
        URI getRDFType() {
            return anyOrNull(RDF_TYPE, ValueMappings::iriAsUri);
        }
        /**
         * Note 7: In its getters, the ``Node`` class calls WrapperBlankNodeOrIRI
         * method ``anyOrNull`` to return either 0 or 1 value mapped to the predicate.
         * You can use ValueMappings method to convert the value to a specified type.
         * <p>
         * In its setters, the ``Node`` class calls WrapperBlankNodeOrIRI
         * method ``overwriteNullable`` to return either 0 or 1 value mapped to the predicate.
         * You can use TermMappings method to store the value with the specified type information.
         */
        void setRDFType(URI type) {
            overwriteNullable(RDF_TYPE, type, TermMappings::asIri);
        }
        String getMerchantProvider() {
            return anyOrNull(SCHEMA_ORG_PROVIDER, ValueMappings::literalAsString);
        }
        void setMerchantProvider(String provider) {
            overwriteNullable(SCHEMA_ORG_PROVIDER, provider, TermMappings::asStringLiteral);
        }
        public Date getExpenseDate() {
            Instant expenseInstant = anyOrNull(SCHEMA_ORG_PURCHASE_DATE, ValueMappings::literalAsInstant);
            if (expenseInstant != null) return Date.from(expenseInstant);
            else return null;
        }
        public void setExpenseDate(Date expenseDate) {
            overwriteNullable(SCHEMA_ORG_PURCHASE_DATE, expenseDate.toInstant(), TermMappings::asTypedLiteral);
        }
        String getDescription() {
            return anyOrNull(SCHEMA_ORG_DESCRIPTION, ValueMappings::literalAsString);
        }
        void setDescription(String description) {
            overwriteNullable(SCHEMA_ORG_DESCRIPTION, description, TermMappings::asStringLiteral);
        }
        public BigDecimal getAmount() {
            String priceString = anyOrNull(SCHEMA_ORG_TOTAL_PRICE, ValueMappings::literalAsString);
            if (priceString != null) return new BigDecimal(priceString);
            else return null;
        }
        /**
         * Note 8: You can write your own TermMapping helper.
         */
        public void setAmount(BigDecimal totalPrice) {
            overwriteNullable(SCHEMA_ORG_TOTAL_PRICE, totalPrice, (final BigDecimal value, final Graph graph) -> {
                Objects.requireNonNull(value, "Value must not be null");
                Objects.requireNonNull(graph, "Graph must not be null");
                return RDFFactory.getInstance().
                        createLiteral(
                                value.toString(),
                                RDFFactory.getInstance().createIRI("http://www.w3.org/2001/XMLSchema#decimal")
                        );
            });
        }
        public String getCurrency() {
            return anyOrNull(SCHEMA_ORG_PRICE_CURRENCY, ValueMappings::literalAsString);
        }
        public void setCurrency(String currency) {
            overwriteNullable(SCHEMA_ORG_PRICE_CURRENCY, currency, TermMappings::asStringLiteral);
        }
        public String getCategory() {
            return anyOrNull(SCHEMA_ORG_CATEGORY, ValueMappings::literalAsString);
        }
        public void setCategory(String category) {
            overwriteNullable(SCHEMA_ORG_CATEGORY, category, TermMappings::asStringLiteral);
        }
        // Node class: Added getter for receipts
        public Set<String> getReceipts() {
            return objects(SCHEMA_ORG_IMAGE, TermMappings::asIri, ValueMappings::iriAsString);
        }
        
        // No setter added
    }
}
```

{% endtab %}

{% tab title="Kotlin" %}

```kotlin
package com.example.gettingstarted
import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.fasterxml.jackson.annotation.JsonProperty
import com.inrupt.client.Headers
import com.inrupt.client.solid.SolidRDFSource
import com.inrupt.rdf.wrapping.commons.RDFFactory
import com.inrupt.rdf.wrapping.commons.TermMappings
import com.inrupt.rdf.wrapping.commons.ValueMappings
import com.inrupt.rdf.wrapping.commons.WrapperIRI
import org.apache.commons.rdf.api.Dataset
import org.apache.commons.rdf.api.Graph
import org.apache.commons.rdf.api.IRI
import org.apache.commons.rdf.api.RDFTerm
import java.math.BigDecimal
import java.net.URI
import java.time.Instant
import java.util.*
/**
 * Part 1
 * Note: extends SolidRDFSource
 * To model the Expense class as an RDF resource, the Expense class extends SolidRDFSource.
 *
 *
 * The @JsonIgnoreProperties annotation is added to ignore the non-class-member fields
 * when serializing Expense data as JSON.
 */
@JsonIgnoreProperties(value = [ "metadata", "headers", "graph", "graphNames", "entity", "contentType" ])
class Expense(
    identifier: URI,
    dataset: Dataset = rdf.createDataset(),
    headers: Headers = Headers.empty(),
) : SolidRDFSource(identifier, dataset, headers) {
    companion object {
        /**
         * Note 2a: Predicate Definitions
         * The following constants define the Predicates used in our triple statements.
         */
        var RDF_TYPE: IRI = rdf.createIRI("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")
        var SCHEMA_ORG_PURCHASE_DATE: IRI = rdf.createIRI("https://schema.org/purchaseDate")
        var SCHEMA_ORG_PROVIDER: IRI = rdf.createIRI("https://schema.org/provider")
        var SCHEMA_ORG_DESCRIPTION: IRI = rdf.createIRI("https://schema.org/description")
        var SCHEMA_ORG_TOTAL_PRICE: IRI = rdf.createIRI("https://schema.org/totalPrice")
        var SCHEMA_ORG_PRICE_CURRENCY: IRI = rdf.createIRI("https://schema.org/priceCurrency")
        var SCHEMA_ORG_CATEGORY: IRI = rdf.createIRI("https://schema.org/category")
        // Added predicate for receipts
        var SCHEMA_ORG_IMAGE: IRI = rdf.createIRI("https://schema.org/image")
        /**
         * Note 2b: Value Definition
         * The following constant defines the value for the predicate RDF_TYPE.
         */
        var MY_RDF_TYPE_VALUE: URI = URI.create("https://schema.org/Invoice")
    }
    /**
     * Note 3: Node class
     * The Node class is an inner class (defined below) that handles the mapping between expense data and RDF triples.
     * The subject contains the expense data.
     */
    private val subject: Node = Node(rdf.createIRI(identifier.toString()), dataset.graph)
    // Constructor updated to handle receipts
    @JsonCreator
    constructor(
        @JsonProperty("identifier") identifier: URI,
        @JsonProperty("merchantProvider") merchantProvider: String?,
        @JsonProperty("expenseDate") expenseDate: Date,
        @JsonProperty("description") description: String?,
        @JsonProperty("amount") amount: BigDecimal?,
        @JsonProperty("currency") currency: String?,
        @JsonProperty("category") category: String?,
        @JsonProperty("receipts") receipts: Set<String>?
    ) : this(identifier, rdf.createDataset(), Headers.empty()) {
        this.rdfType = MY_RDF_TYPE_VALUE
        this.merchantProvider = merchantProvider
        this.expenseDate = expenseDate
        this.description = description
        this.amount = amount
        this.currency = currency
        this.category = category
        this.receipts = receipts
    }
    // Constructor with just the Identifier
    constructor(
        identifier: URI
    ) : this(identifier, rdf.createDataset(), Headers.empty())
    /**
     * Note 5: Various getters/setters.
     * The getters and setters reference the subject's methods.
     */
    var rdfType: URI?
        get() = subject.rdfType
        set(rdfType) {
            subject.rdfType = rdfType
        }
    var merchantProvider: String?
        get() = subject.merchantProvider
        set(merchantProvider) {
            subject.merchantProvider = merchantProvider
        }
    var expenseDate: Date?
        get() = subject.expenseDate
        set(expenseDate) {
            subject.expenseDate = expenseDate
        }
    var description: String?
        get() = subject.description
        set(description) {
            subject.description = description
        }
    var amount: BigDecimal?
        get() = subject.amount
        set(amount) {
            subject.amount = amount
        }
    var currency: String?
        get() = subject.currency
        set(currency) {
            subject.currency = currency
        }
    var category: String?
        get() = subject.category
        set(category) {
            subject.category = category
        }
    // Expense class: getter and setters for receipts
    // Note:: The setters first uses the getter, which returns a Set, and adds the receipt to the set.
    var receipts: Set<String>?
        get() = subject.receipts
        set(receipts) {
            if (receipts != null) {
                subject.receipts.addAll(receipts)
            }
        }
    fun addReceipt(receipt: String) {
        subject.receipts.add(receipt)
    }
    /**
     * Note 6: Inner class ``Node`` that extends WrapperIRI
     * Node class handles the mapping of the expense data (date, provider,
     * description, category, priceCurrency, total) to RDF triples
     * <subject> <predicate> <object>.
     *
     * Nomenclature Background: A set of RDF triples is called a Graph.
     */
    internal class Node(original: RDFTerm, graph: Graph) :
        WrapperIRI(original, graph) {
        /**
         * Note 7: In its getters, the ``Node`` class calls WrapperBlankNodeOrIRI
         * method ``anyOrNull`` to return either 0 or 1 value mapped to the predicate.
         * You can use ValueMappings method to convert the value to a specified type.
         *
         * In its setters, the ``Node`` class calls WrapperBlankNodeOrIRI
         * method ``overwriteNullable`` to return either 0 or 1 value mapped to the predicate.
         * You can use the TermMappings method to store the value with the specified type information.
         */
        var rdfType: URI?
            get() = anyOrNull(RDF_TYPE) { term: RDFTerm, graph: Graph ->
                ValueMappings.iriAsUri(term, graph)
            }
            set(type) {
                overwriteNullable(RDF_TYPE, type) { value: URI?, graph: Graph ->
                    TermMappings.asIri(value, graph)
                }
            }
        var merchantProvider: String?
            get() = anyOrNull(SCHEMA_ORG_PROVIDER) { term: RDFTerm, graph: Graph ->
                ValueMappings.literalAsString(term, graph)
            }
            set(provider) {
                overwriteNullable(SCHEMA_ORG_PROVIDER, provider) { value: String?, graph: Graph ->
                    TermMappings.asStringLiteral(value, graph)
                }
            }
        var expenseDate: Date?
            get() {
                val expenseInstant: Instant? = anyOrNull(SCHEMA_ORG_PURCHASE_DATE) { term: RDFTerm, graph: Graph ->
                    ValueMappings.literalAsInstant(term, graph)
                }
                return if (expenseInstant != null) Date.from(expenseInstant) else null
            }
            set(expenseDate) {
                val expenseInstant: Instant? = if (expenseDate != null) expenseDate.toInstant() else null
                overwriteNullable(SCHEMA_ORG_PURCHASE_DATE, expenseInstant) { value: Instant?, graph: Graph ->
                    TermMappings.asTypedLiteral(value, graph)
                }
            }
        var description: String?
            get() = anyOrNull(SCHEMA_ORG_DESCRIPTION) { term: RDFTerm?, graph: Graph ->
                ValueMappings.literalAsString(term, graph)
            }
            set(description) {
                overwriteNullable(SCHEMA_ORG_DESCRIPTION, description) { value: String?, graph: Graph ->
                    TermMappings.asStringLiteral(value, graph)
                }
            }
        /**
         * Note 8: You can write your own TermMapping helper.
         */
        var amount: BigDecimal?
            get() {
                val priceString: String? = anyOrNull(SCHEMA_ORG_TOTAL_PRICE) { term: RDFTerm?, graph: Graph ->
                    ValueMappings.literalAsString(term, graph)
                }
                return if (priceString != null) BigDecimal(priceString) else null
            }
            set(totalPrice) {
                overwriteNullable(SCHEMA_ORG_TOTAL_PRICE, totalPrice) { value, _ ->
                    RDFFactory.getInstance().createLiteral(
                        value.toString(),
                        RDFFactory.getInstance().createIRI("http://www.w3.org/2001/XMLSchema#decimal")
                    )
                }
            }
        var currency: String?
            get() = anyOrNull(SCHEMA_ORG_PRICE_CURRENCY) { term: RDFTerm?, graph: Graph ->
                ValueMappings.literalAsString(term, graph)
            }
            set(currency) {
                overwriteNullable(SCHEMA_ORG_PRICE_CURRENCY, currency) { value: String?, graph: Graph ->
                    TermMappings.asStringLiteral(value, graph)
                }
            }
        var category: String?
            get() = anyOrNull(SCHEMA_ORG_CATEGORY) { term: RDFTerm?, graph: Graph ->
                ValueMappings.literalAsString(term, graph)
            }
            set(category) {
                overwriteNullable(SCHEMA_ORG_CATEGORY, category) { value: String?, graph: Graph ->
                    TermMappings.asStringLiteral(value, graph)
                }
            }
        // Node class: Added getter for receipts
        val receipts: MutableSet<String>
            get() = objects(SCHEMA_ORG_IMAGE, TermMappings::asIri, ValueMappings::iriAsString)
        // No setter added
    }
}
```

{% endtab %}
{% endtabs %}

## ExpenseController Class

{% tabs %}
{% tab title="Java" %}

```java
package com.example.gettingstarted;
import com.inrupt.client.auth.Session;
import com.inrupt.client.openid.OpenIdSession;
import com.inrupt.client.solid.SolidSyncClient;
import com.inrupt.client.webid.WebIdProfile;
import com.inrupt.client.solid.PreconditionFailedException;
import com.inrupt.client.solid.ForbiddenException;
import com.inrupt.client.solid.NotFoundException;
import org.springframework.web.bind.annotation.*;
import org.apache.commons.rdf.api.RDFSyntax;
import com.inrupt.client.solid.SolidNonRDFSource;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URI;
import java.util.Set;
@RequestMapping("/api")
@RestController
public class ExpenseController {
    /**
     * Note 1: Authenticated Session
     * Using the client credentials, create an authenticated session.
     */
    final Session session = OpenIdSession.ofClientCredentials(
            URI.create(System.getenv("MY_SOLID_IDP")),
            System.getenv("MY_SOLID_CLIENT_ID"),
            System.getenv("MY_SOLID_CLIENT_SECRET"),
            System.getenv("MY_AUTH_FLOW"));
    /**
     * Note 2: SolidSyncClient
     * Instantiates a synchronous client for the authenticated session.
     * The client has methods to perform CRUD operations.
     */
    final SolidSyncClient client = SolidSyncClient.getClient().session(session);
    private final PrintWriter printWriter = new PrintWriter(System.out, true);
    /**
     * Note 3: SolidSyncClient.read()
     * Using the SolidSyncClient client.read() method, reads the user's WebID Profile document and returns the Pod URI(s).
     */
    @GetMapping("/pods")
    public Set<URI> getPods(@RequestParam(value = "webid", defaultValue = "") String webID) {
        printWriter.println("ExpenseController:: getPods");
        try (final var profile = client.read(URI.create(webID), WebIdProfile.class)) {
            return profile.getStorages();
        }
    }
    /**
     * Note 4: SolidSyncClient.create()
     * Using the SolidSyncClient client.create() method,
     * - Saves the Expense as an RDF resource to the location specified in the Expense.identifier field.
     */
    @PostMapping(path = "/expenses/create")
    public Expense createExpense(@RequestBody Expense newExpense) {
        printWriter.println("ExpenseController:: createExpense");
        try (var createdExpense = client.create(newExpense)) {
            printExpenseAsTurtle(createdExpense);
            return createdExpense;
        } catch(PreconditionFailedException e1) {
            // Errors if the resource already exists
            printWriter.println(String.format("[%s] com.inrupt.client.solid.PreconditionFailedException:: %s", e1.getStatusCode(), e1.getMessage()));
        } catch(ForbiddenException e2) {
            // Errors if user does not have access to create
            printWriter.println(String.format("[%s] com.inrupt.client.solid.ForbiddenException:: %s", e2.getStatusCode(), e2.getMessage()));
        } catch(Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * Note 5: SolidSyncClient.read()
     * Using the SolidSyncClient client.read() method,
     * - Reads the RDF resource into the Expense class.
     */
    @GetMapping("/expenses/get")
    public Expense getExpense(@RequestParam(value = "resourceURL", defaultValue = "") String resourceURL) {
        printWriter.println("ExpenseController:: getExpense");
        try (var resource = client.read(URI.create(resourceURL), Expense.class)) {
            return resource;
        } catch (NotFoundException e1) {
            // Errors if resource is not found
            printWriter.println(String.format("[%s] com.inrupt.client.solid.NotFoundException:: %s", e1.getStatusCode(), e1.getMessage()));
        } catch(ForbiddenException e2) {
            // Errors if user does not have access to read
            printWriter.println(String.format("[%s] com.inrupt.client.solid.ForbiddenException:: %s", e2.getStatusCode(), e2.getMessage()));
        } catch(Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * Note 6: SolidSyncClient.update()
     * Using the SolidSyncClient client.update() method,
     * - Updates the Expense resource.
     */
    @PutMapping("/expenses/update")
    public Expense updateExpense(@RequestBody Expense expense) {
        printWriter.println("ExpenseController:: updateExpense");
        try(var updatedExpense = client.update(expense)) {
            printExpenseAsTurtle(updatedExpense);
            return updatedExpense;
        } catch (NotFoundException e1) {
            // Errors if resource is not found
            printWriter.println(String.format("[%s] com.inrupt.client.solid.NotFoundException:: %s", e1.getStatusCode(), e1.getMessage()));
        } catch(ForbiddenException e2) {
            // Errors if user does not have access to read
            printWriter.println(String.format("[%s] com.inrupt.client.solid.ForbiddenException:: %s", e2.getStatusCode(), e2.getMessage()));
        } catch(Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * Note 7: SolidSyncClient.delete()
     * Using the SolidSyncClient client.delete() method,
     * - Deletes the resource located at the resourceURL.
     */
    @DeleteMapping("/expenses/delete")
    public void deleteExpense(@RequestParam(value = "resourceURL") String resourceURL) {
        printWriter.println("ExpenseController:: deleteExpense");
        try {
            client.delete(URI.create(resourceURL));
            // Alternatively, you can specify an Expense object to the delete method.
            // The delete method deletes  the Expense recorde located in the Expense.identifier field. 
            // For example: client.delete(new Expense(URI.create(resourceURL)));
        } catch (NotFoundException e1) {
            // Errors if resource is not found
            printWriter.println(String.format("[%s] com.inrupt.client.solid.NotFoundException:: %s", e1.getStatusCode(), e1.getMessage()));
        } catch(ForbiddenException e2) {
            // Errors if user does not have access to read
            printWriter.println(String.format("[%s] com.inrupt.client.solid.ForbiddenException:: %s", e2.getStatusCode(), e2.getMessage()));
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * Note 8: Prints the expense resource in Turtle.
     */
    private void printExpenseAsTurtle(Expense expense) {
        printWriter.println("ExpenseController:: printExpenseAsTurtle");
        ByteArrayOutputStream content = new ByteArrayOutputStream();
        try  {
            expense.serialize(RDFSyntax.TURTLE, content);
            printWriter.println(content.toString("UTF-8"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * Note 9: Stores a non-RDF resource to a Pod
     *
     * Using SolidNonRDFSource and the SolidSyncClient .create() method,
     * - Saves a non-RDF resource at the destinationURL.
     */
    @PutMapping("/resource/nonRDF/add")
    public String addNonRDFFile(@RequestParam(value = "destinationURL") String destinationURL,
                                @RequestParam(value = "file") MultipartFile file) {
        printWriter.println("In addNonRDFFile:: Save Non-RDF File to Pod.");
        try (final var fileStream = file.getInputStream()) {
            SolidNonRDFSource myNonRDFFile = new SolidNonRDFSource(URI.create(destinationURL), file.getContentType(), fileStream);
            return client.create(myNonRDFFile).getIdentifier().toString();
        } catch(PreconditionFailedException e1) {
            // Errors if the resource already exists
            printWriter.println(String.format("[%s] com.inrupt.client.solid.PreconditionFailedException in addNonRDFFile:: %s", e1.getStatusCode(), e1.getMessage()));
        } catch(ForbiddenException e2) {
            // Errors if user does not have access to create
            printWriter.println(String.format("[%s] com.inrupt.client.solid.ForbiddenException in addNonRDFFile:: %s", e2.getStatusCode(), e2.getMessage()));
        } catch(Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * Note 10: Stores a non-RDF resource (image of the receipt) to a Pod and Attach to an Expense
     * Using methods defined as part of getting started, addReceiptToExpense:
     * - Calls addNonRDFFile() to store the receipt to a Pod
     * - Calls getExpense() to fetch the associated Expense RDF resource.
     * - Calls the Expense's setter `addReceipt` to add the link to the saved receipt.
     * - Calls updateExpense() to save the updated Expense.
     */
    @PutMapping("/expenses/receipts/add")
    public Expense addReceiptToExpense(@RequestParam(value = "destinationURL") String destinationURL,
                                       @RequestParam(value = "file") MultipartFile file,
                                       @RequestParam(value = "expenseURL") String expenseURL) {
        printWriter.println("In addReceiptToExpense: Save Receipt File to Pod and Update Associated Expense.");
        try {
            String receiptLocation = addNonRDFFile(destinationURL, file);
            if (receiptLocation != null) {
                Expense expense = getExpense(expenseURL);
                expense.addReceipt(receiptLocation);
                return updateExpense(expense);
            } else {
                printWriter.println("Error adding receipt");
                return null;
            }
        } catch(ForbiddenException e2) {
            // Errors if user does not have access to read or update the Expense resource
            printWriter.println(String.format("[%s] com.inrupt.client.solid.ForbiddenException in addReceiptToExpense:: %s", e2.getStatusCode(), e2.getMessage()));
        } catch(Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
```

{% endtab %}

{% tab title="Kotlin" %}

```kotlin
package com.example.gettingstarted
import com.inrupt.client.openid.OpenIdSession
import com.inrupt.client.solid.SolidSyncClient
import com.inrupt.client.webid.WebIdProfile
import com.inrupt.client.solid.PreconditionFailedException
import com.inrupt.client.solid.ForbiddenException
import com.inrupt.client.solid.NotFoundException
import org.springframework.web.bind.annotation.*
import java.io.PrintWriter
import java.net.URI
import com.inrupt.client.solid.SolidNonRDFSource
import org.springframework.web.multipart.MultipartFile
import org.apache.commons.rdf.api.RDFSyntax
import java.io.ByteArrayOutputStream
import java.io.IOException
@RequestMapping("/api")
@RestController
class ExpenseController {
    /**
     * Note 1: Authenticated Session
     * Using the client credentials, create an authenticated session.
     */
    val session = OpenIdSession.ofClientCredentials(
        URI.create(System.getenv("MY_SOLID_IDP")),
        System.getenv("MY_SOLID_CLIENT_ID"),
        System.getenv("MY_SOLID_CLIENT_SECRET"),
        System.getenv("MY_AUTH_FLOW")
    )
    /**
     * Note 2: SolidSyncClient
     * Instantiates a synchronous client for the authenticated session.
     * The client has methods to perform CRUD operations.
     */
    val client = SolidSyncClient.getClient().session(session)
    private val printWriter = PrintWriter(System.out, true)
    /**
     * Note 3: SolidSyncClient.read()
     * Using the SolidSyncClient client.read() method, reads the user's WebID Profile document and returns the Pod URI(s).
     */
    @GetMapping("/pods")
    fun getPods(@RequestParam(value = "webid", defaultValue = "") webID: String): Set<URI> {
        printWriter.println("ExpenseController:: getPods")
        client.read(URI.create(webID), WebIdProfile::class.java).use { profile -> return profile.storages }
    }
    /**
     * Note 4: SolidSyncClient.create()
     * Using the SolidSyncClient client.create() method,
     * - Saves the Expense as an RDF resource to the location specified in the Expense.identifier field.
     */
    @PostMapping("/expenses/create")
    fun createExpense(@RequestBody newExpense: Expense): Expense? {
        printWriter.println("ExpenseController:: createExpense")
        try {
           val createdExpense = client.create(newExpense)
           printExpenseAsTurtle(createdExpense)
           return createdExpense
        } catch(e1: PreconditionFailedException) {
            // Errors if the resource already exists
            printWriter.println(String.format("[%s] com.inrupt.client.solid.PreconditionFailedException:: %s", e1.statusCode, e1.localizedMessage))
        } catch(e2: ForbiddenException) {
            // Errors if user does not have access to create
            printWriter.println(String.format("[%s] com.inrupt.client.solid.ForbiddenException:: %s", e2.statusCode, e2.localizedMessage))
        } catch(e: Exception) {
            e.printStackTrace()
        }
        return null
    }
    /**
     * Note 5: SolidSyncClient.read()
     * Using the SolidSyncClient client.read() method,
     * - Reads the RDF resource into the Expense class.
     */
    @GetMapping("/expenses/get")
    fun getExpense(
        @RequestParam(
            value = "resourceURL", defaultValue = ""
        ) resourceURL: String
    ): Expense? {
        printWriter.println("ExpenseController:: getExpense")
        try {
            client.read(
                URI.create(resourceURL), Expense::class.java
            ).use { resource -> return resource }
        } catch(e1: NotFoundException) {
            // Errors if the resource is not found
            printWriter.println(String.format("[%s] com.inrupt.client.solid.NotFoundException:: %s", e1.statusCode, e1.localizedMessage))
        } catch(e2: ForbiddenException) {
            // Errors if user does not have access to read
            printWriter.println(String.format("[%s] com.inrupt.client.solid.ForbiddenException:: %s", e2.statusCode, e2.localizedMessage))
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return null
    }
    /**
     * Note 6: SolidSyncClient.update()
     * Using the SolidSyncClient client.update() method,
     * - Updates the Expense resource.
     */
    @PutMapping("/expenses/update")
    fun updateExpense(@RequestBody expense: Expense): Expense? {
        printWriter.println("ExpenseController:: updateExpense")
        try {
           val updatedExpense = client.update(expense)
           printExpenseAsTurtle(updatedExpense)
           return updatedExpense
        } catch(e1: NotFoundException) {
            // Errors if the resource is not found
            printWriter.println(String.format("[%s] com.inrupt.client.solid.NotFoundException:: %s", e1.statusCode, e1.localizedMessage))
        } catch(e2: ForbiddenException) {
            // Errors if user does not have access to read
            printWriter.println(String.format("[%s] com.inrupt.client.solid.ForbiddenException:: %s", e2.statusCode, e2.localizedMessage))
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return null
    }
    /**
     * Note 7: SolidSyncClient.delete()
     * Using the SolidSyncClient client.delete() method,
     * - Deletes the resource located at the resourceURL.
     */
    @DeleteMapping("/expenses/delete")
    fun deleteExpense(@RequestParam(value = "resourceURL") resourceURL: String) {
        printWriter.println("ExpenseController:: deleteExpense")
        try {
            client.delete(URI.create(resourceURL))
            // Alternatively, you can specify an Expense object to the delete method.
            // The delete method deletes  the Expense recorde located in the Expense.identifier field.
            // For example: client.delete(Expense(URI.create(resourceURL)))
        } catch(e1: NotFoundException) {
            // Errors if the resource is not found
            printWriter.println(String.format("[%s] com.inrupt.client.solid.NotFoundException:: %s", e1.statusCode, e1.localizedMessage))
        } catch(e2: ForbiddenException) {
            // Errors if user does not have access to read
            printWriter.println(String.format("[%s] com.inrupt.client.solid.ForbiddenException:: %s", e2.statusCode, e2.localizedMessage))
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
    /**
     * Note 8: Prints the expense resource in Turtle.
     */
    fun printExpenseAsTurtle(
        expense: Expense
    ) {
        printWriter.println("ExpenseController:: printExpenseAsTurtle")
        val content = ByteArrayOutputStream()
        try  {
            expense.serialize(RDFSyntax.TURTLE, content)
            printWriter.println(content.toString("UTF-8"))
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }
    /**
     * Note 9: Stores a non-RDF resource to a Pod
     *
     * Using SolidNonRDFSource and the SolidSyncClient's .create() method,
     * - Saves a non-RDF resource at the destinationURL.
     */
    @PutMapping("/resource/nonRDF/add")
    fun addNonRDFFile(
        @RequestParam(value = "destinationURL") destinationURL: String,
        @RequestParam(value = "file") file: MultipartFile
    ): String? {
        printWriter.println("In addNonRDFFile: Save Non-RDF File to Pod.")
        try {
            file.inputStream.use { fileStream ->
               val myNonRDFFile: SolidNonRDFSource = SolidNonRDFSource(URI.create(destinationURL), file.contentType, fileStream)
               return client.create(myNonRDFFile).identifier.toString()
            }
        } catch(e1: PreconditionFailedException) {
            // Errors if the resource already exists
            printWriter.println(String.format("[%s] com.inrupt.client.solid.PreconditionFailedException in addNonRDFFile:: %s", e1.statusCode, e1.localizedMessage))
        } catch(e2: ForbiddenException) {
            // Errors if user does not have access to create
            printWriter.println(String.format("[%s] com.inrupt.client.solid.ForbiddenException in addNonRDFFile:: %s", e2.statusCode, e2.localizedMessage))
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return ""
    }
    /**
     * Note 10: Stores a non-RDF resource (image of the receipt) to a Pod and Attach to an Expense
     * Using methods defined as part of getting started, addReceiptToExpense:
     * - Calls addNonRDFFile() to store the receipt to a Pod
     * - Calls getExpense() to fetch the associated Expense RDF resource.
     * - Calls the Expense's setter `addReceipt` to add the link to the saved receipt.
     * - Calls updateExpense() to save the updated Expense.
     */
    @PutMapping("/expenses/receipts/add")
    fun addReceiptToExpense(
        @RequestParam(value = "destinationURL") destinationURL: String,
        @RequestParam(value = "file") file: MultipartFile,
        @RequestParam(value = "expenseURL") expenseURL: String
    ): Expense? {
        printWriter.println("In AddReceiptToExpense: Save Receipt File to Pod and Update Associated Expense.")
        val receiptLocation = addNonRDFFile(destinationURL, file)
        return if (!receiptLocation.isNullOrEmpty()) {
            val expense = getExpense(expenseURL)
            if (expense != null) {
                expense.addReceipt(receiptLocation)
                updateExpense(expense)
            } else {
                null
            }
        } else {
            printWriter.println("Error adding receipt")
            null
        }
    }
}
```

{% endtab %}
{% endtabs %}


# Step 6: Run (Part 2)

## Run Your Local Web Server

Open a terminal window.

### Enter Your Client Credentials

{% hint style="danger" %}
Safeguard your **`Client ID`** and **`Client Secret`** values. Do not share these with any third parties as anyone with your **`Client ID`** and **`Client Secret`** values can impersonate you and act fully on your behalf.
{% endhint %}

Export your registered client credentials (see the [Prerequisites](/sdk/java-sdk/tutorial/prerequisites)) as environment variables.

1. Identity Provider (the IDP with whom you registered your application):

```sh
read -s MY_SOLID_IDP && export MY_SOLID_IDP
```

Enter `https://login.inrupt.com`

2. Client ID:

```sh
read -s MY_SOLID_CLIENT_ID && export MY_SOLID_CLIENT_ID
```

Enter your Client ID.

3. Client Secret:

```sh
read -s MY_SOLID_CLIENT_SECRET && export MY_SOLID_CLIENT_SECRET
```

Enter your Client Secret.

4. Authentication Flow Method:

```sh
read -s MY_AUTH_FLOW && export MY_AUTH_FLOW
```

Enter `client_secret_basic`

### Run the Application

Once you have entered your client credentials, start your application. From your project ( `getting-started/` ) directory, run your Spring Boot application:

* For Java, this tutorial assumes a Spring Boot Web Maven Project.
* For Kotlin, this tutorial assumes a Spring Boot Web Gradle Project.

{% tabs %}
{% tab title="Java" %}

```sh
./mvnw spring-boot:run
```

{% endtab %}

{% tab title="Kotlin" %}

```sh
./gradlew bootRun
```

{% endtab %}
{% endtabs %}

Your Web service runs on `http://localhost:8080` .

{% hint style="info" %}
Reminder\
The application is running **as** you, the user who registered it.
{% endhint %}

## Test the Service

Open another terminal window. To test, call the new endpoints defined in the **`ExpenseController`** class:

<table><thead><tr><th width="267.5859375">Endpoint</th><th>Description</th></tr></thead><tbody><tr><td><strong><code>/api/expenses/receipts/add</code></strong></td><td>Saves a non-RDF resource, namely an image file of a receipt, to a location in the Pod and updates the <strong><code>Expense</code></strong> object with the receipt location. Returns the updated <strong><code>Expense</code></strong> object.</td></tr><tr><td><strong><code>/api/resource/nonRDF/add</code></strong></td><td>Saves a non-RDF resource to a location in the Pod. Returns the identifier (as String) for the saved resource.</td></tr></tbody></table>

### Get Pod URL

To find your Pod URL, issue the following **`curl`** command, substituting your WebID (e.g., **`https://id.inrupt.com/yourUserName`** ):

```sh
curl -X GET http://localhost:8080/api/pods\?webid\=SUBSTITUTE_YOUR_WEBID
```

Upon success, the operation should return an array with your Pod Root URL; for example:

```sh
["https://storage.inrupt.com/your-root-container/"]
```

{% hint style="info" %}
Note\
In the following operations, substitute **`your-root-container`** with the value of your root container.
{% endhint %}

### Add a Receipt to Existing Expense

To add a receipt to an existing expense created in Part 1, call the **`api/expenses/receipts/add`** endpoint with a local **`.png`** file (can be a different file type **`.jpg`** , **`.pdf`** , etc. as well), **substituting** the path to your local file and your root container in the request body:

<pre class="language-sh"><code class="lang-sh">
curl -X PUT http://localhost:8080/api/expenses/receipts/add \
          -H "Content-Type: multipart/form-data" \
<strong>          -F "destinationURL=https://storage.inrupt.com/your-root-container/expenses/20230315/receipt.png" \
</strong><strong>          -F "file=@/my/local/file/path/to/receipt.png" \
</strong><strong>          -F "expenseURL=https://storage.inrupt.com/your-root-container/expenses/20230315/expense1"
</strong>
</code></pre>

{% hint style="info" %}
Tip

* If you encounter an **`HTTP 403 Forbidden`** error, double check that you have substituted **`your-root-container`** in the command.
* If you encounter a **`PreconditionFailedException`** , check that the **`receipt.png`** does not already exist at the the specified identifier. The **`.create()`** operation errors with **`PreconditionFailedException`** if a resource already exists. See [CRUD Module](/sdk/java-sdk/crud-data) for details.
  {% endhint %}

Upon success, the operation should return the updated **`Expense`** object as JSON (as well as print out, on the server-side, the content formatted in Turtle):

{% tabs %}
{% tab title="Returned Expense Object" %}

<pre class="language-json"><code class="lang-json">
{
    "identifier": "https://storage.inrupt.com/your-root-container/expenses/20230315/expense1",
    "merchantProvider": "Example Restaurant",
    "expenseDate": "2023-03-15T00:00:00.000+00:00",
    "description": "Team Lunch",
    "amount": 100,
    "currency": "USD",
    "category": "Travel &#x26; Entertainment",
<strong>    "receipts": [
</strong><strong>        "https://storage.inrupt.com/your-root-container/expenses/20230315/receipt.png"
</strong><strong>    ],
</strong>    "rdftype": "https://schema.org/Invoice"
}
</code></pre>

{% endtab %}

{% tab title="Content Formatted as Turtle" %}

<pre class="language-turtle"><code class="lang-turtle">
&#x3C;https://storage.inrupt.com/your-root-container/expenses/20230315/expense1>
        a                              &#x3C;https://schema.org/Invoice> ;
        &#x3C;https://schema.org/purchaseDate>
                "2023-03-15T00:00:00Z"^^&#x3C;http://www.w3.org/2001/XMLSchema#dateTime> ;
        &#x3C;https://schema.org/category>  "Travel &#x26; Entertainment" ;
        &#x3C;https://schema.org/description>
                "Team Lunch" ;
<strong>        &#x3C;https://schema.org/image>     &#x3C;https://storage.inrupt.com/your-root-container/expenses/20230315/receipt.png> ;
</strong><strong>        &#x3C;https://schema.org/priceCurrency>
</strong>                "USD" ;
        &#x3C;https://schema.org/provider>  "Example Restaurant" ;
        &#x3C;https://schema.org/totalPrice>
                "100"^^&#x3C;http://www.w3.org/2001/XMLSchema#decimal> .
</code></pre>

{% endtab %}
{% endtabs %}

See also [CRUD Module](/sdk/java-sdk/crud-data).

### Save a Non-RDF File

To save a receipt (a non-RDF resource) to your Pod, issue the following **`curl`** command to the **`api/resource/nonRDF/add`** endpoint, <mark style="color:red;">**substituting**</mark> the path to your local file and your root container in the request body:

<pre class="language-sh"><code class="lang-sh">curl -X PUT http://localhost:8080/api/resource/nonRDF/add \
          -H "Content-Type: multipart/form-data" \
<strong>          -F "destinationURL=https://storage.inrupt.com/your-root-container/expenses/20230327/receipt.png" \
</strong><strong>          -F "file=@/my/local/file/path/to/newreceipt.png"
</strong></code></pre>

{% hint style="info" %}
Tip

* If you encounter an **`HTTP 403 Forbidden`** error, double check that you have substituted **`your-root-container`** in the command.
* If you encounter a **`PreconditionFailedException`** , check that the resource does not already exist at the the specified identifier. The **`.create()`** operation errors with **`PreconditionFailedException`** if a resource already exists. See [CRUD Module](/sdk/java-sdk/crud-data) for details.
  {% endhint %}

Upon success, the operation returns identifier (as string) of the resource:

```none
https://storage.inrupt.com/your-root-container/expenses/20230327/receipt.png
```

See also [CRUD Module](/sdk/java-sdk/crud-data).

### Create an Expense Record

Using the receipt saved in the Save a Non-RDF File section, create a new expense that includes the receipt info, <mark style="color:red;">**substituting**</mark> your root container in the request body:

<pre class="language-sh"><code class="lang-sh">
curl -X POST http://localhost:8080/api/expenses/create \
   -H 'Content-type:application/json'  \
   -d '{
<strong>      "identifier": "https://storage.inrupt.com/your-root-container/expenses/20230327/expense1",
</strong><strong>      "merchantProvider": "Example Supply Store",
</strong>      "description": "Chair",
      "expenseDate": "2023-03-27",
      "amount": 400,
      "currency": "USD",
      "category": "Office Equipment &#x26; Supplies",
<strong>      "receipts": [ "https://storage.inrupt.com/your-root-container/expenses/20230327/receipt.png"] }'
</strong>
</code></pre>

{% hint style="info" %}
Tip

* If you encounter an **`HTTP 403 Forbidden`** error, double check that you have substituted **`your-root-container`** in the command.
* If you encounter a **`PreconditionFailedException`** , check that the resource does not already exist at the the specified identifier. The **`.create()`** operation errors with **`PreconditionFailedException`** if a resource already exists. See [CRUD Module](/sdk/java-sdk/crud-data) for details.
  {% endhint %}

Upon success, the operation should return the updated **`Expense`** object as JSON (as well as print out, on the server-side, the content formatted in Turtle):

{% tabs %}
{% tab title="Returned Expense Object" %}

```json
{
    "identifier": "https://storage.inrupt.com/your-root-container/expenses/20230327/expense1",
    "merchantProvider": "Example Supply Store",
    "expenseDate": "2023-03-27T00:00:00.000+00:00",
    "description": "Chair",
    "amount": 400,
    "currency": "USD",
    "category": "Office Equipment & Supplies",
    "receipts": [
        "https://storage.inrupt.com/your-root-container/expenses/20230327/receipt.png"
    ],
    "rdftype": "https://schema.org/Invoice"
}
```

{% endtab %}

{% tab title="Content Formatted as Turtle" %}

```turtle
<https://storage.inrupt.com/your-root-container/expenses/20230327/expense1>
        a                              <https://schema.org/Invoice> ;
        <https://schema.org/purchaseDate>
                "2023-03-27T00:00:00Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> ;
        <https://schema.org/category>  "Office Equipment & Supplies" ;
        <https://schema.org/description>
                "Chair" ;
        <https://schema.org/image>     <https://storage.inrupt.com/your-root-container/expenses/20230327/receipt.png> ;
        <https://schema.org/priceCurrency>
                "USD" ;
        <https://schema.org/provider>  "Example Supply Store" ;
        <https://schema.org/totalPrice>
                "400"^^<http://www.w3.org/2001/XMLSchema#decimal> .
```

{% endtab %}
{% endtabs %}

See also [CRUD Module](/sdk/java-sdk/crud-data).


# Authentication

## OpenID Sessions for Multi-User Web Application

Inrupt’s Java Client Libraries can work with 3rd party libraries/frameworks that support [OpenID Connect](https://openid.net/connect/) and [OAuth2](https://datatracker.ietf.org/doc/html/rfc6749) (for example, [Spring Security](https://docs.spring.io/spring-security/reference/servlet/oauth2/login/index.html), [Quarkus](https://quarkus.io/guides/security-openid-connect-client-reference)). To support the [OIDC login flow](https://openid.net/developers/how-connect-works/), these frameworks typically require you to configure:

* a **`client_id`** and
* an OpenID provider (e.g., **`https://login.inrupt.com`** for PodSpaces).

To login/logout your users, refer to your framework’s documentation on OpenID Connect support.

If your OpenID Provider supports the [Solid-OIDC specification](https://solid.github.io/solid-oidc/), the `client_id` can be a URI that [dereferences to a Client Identifier document](/sdk/java-sdk/authentication/solid-oidc-client-identifiers).

#### Authenticated Session

Once a user has successfully logged in, you can access the ID Token from your framework, and create an authenticated [Session](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/auth/Session.html) using the [OpenIdSession](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/openid/OpenIdSession.html) class. For example:

```java
import com.inrupt.client.auth.Session;
import com.inrupt.client.openid.OpenIdSession;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
//...


public Expense fetchAsUser(OidcUser authedUser, URL expenseURL) {
    Session session = OpenIdSession.ofIdToken(authedUser.getIdToken().getTokenValue());
    //...
}
```

{% hint style="info" %}
In multi-user contexts, multiple sessions in a single application must be managed to ensure that one user’s session is not used by another user. See [Session Management](/sdk/java-sdk/authentication/session-management) for more information.
{% endhint %}

To clear cached credential data from the session, use [Session.reset()](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/auth/Session.html#reset\(\)).

## OpenID Sessions for Statically Registered Single-User App

For applications that run <mark style="color:red;">**on behalf of a single-user only**</mark> (such as a single-user command-line app), you can statically register application if static registration is supported by your Solid Identity Provider. For example, if using the Solid Identity Provider for Inrupt’s [PodSpaces](/podspaces/podspaces), you can statically register your application via its [Application Registration](https://login.inrupt.com/registration.html) page.

Static registration results in a Client ID and Client Secret pair, which can be used for [Client Credentials](https://www.rfc-editor.org/rfc/rfc6749#section-4.4) flow.

{% hint style="danger" %}
Safeguard your **`Client ID`** and **`Client Secret`** values. Do not share these with any third parties as anyone with your **`Client ID`** and **`Client Secret`** values can impersonate you and act fully on your behalf.
{% endhint %}

```java
import com.inrupt.client.auth.Session;
import com.inrupt.client.openid.OpenIdSession;
import java.net.URI;

public class MyPersonalApplication {

    // For PodSpaces, the IdentityProvider is https://login.inrupt.com

    public void run(String myIdentityProvider, String myClientID, String myClientSecret) {
       try{

          URI issuer = URI.create(myIdentityProvider);
          Session session = OpenIdSession.ofClientCredentials(
             issuer,
             myClientID,
             myClientSecret,
             "client_secret_basic");

          // ... Perform operations as the user who registered the app

       } catch (Exception e) {
          //...
       }
   }
}
```

To clear cached credential data from the session, use [Session.reset()](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/auth/Session.html#reset\(\)).

## Sessions for Access Grants

To use Access Requests and Grants, applications uses both Access Grants and an OpenID-based session to build an [AccessGrantSession](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantSession.html). For example:

```java
AccessGrant grant = // ....;
Session myOpenIDSession = OpenIdSession.ofIdToken(idToken);

Session myAccessGrantSession = AccessGrantSession.ofAccessGrant(myOpenIDSession, grant);
```

To clear cached credential data from the session, use [Session.reset()](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/auth/Session.html#reset\(\)).

For information on access grants, see [Access Requests and Grants](/sdk/java-sdk/access-requests-and-grants).


# Session Management

Inrupt’s Java Client Libraries provide a [Session](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/auth/Session.html) interface to handle session objects. In multi-user contexts, multiple sessions in a single application must be managed to ensure that one user’s session is not used by another user.

The following content provides some recommendations for managing sessions in multi-user applications.

### Session Scope

#### Avoid Application-Scoped Sessions

For multi-user applications, the general guidance is to <mark style="color:red;">**avoid**</mark> making a session scoped to the entire application. That is, when using dependency injection framework such as [Spring](https://spring.io/) or [JakartaEE](https://jakarta.ee/):

* Do <mark style="color:red;">**NOT**</mark> use **`@ApplicationScope`**, **`@ApplicationScoped`**, or equivalent scope.
* Do <mark style="color:red;">**NOT**</mark> use **`@Singleton`** or equivalent scope.

#### Use Request-Scoped Sessions

For applications where a session is used by different components, instantiating an independent session inside each component introduces unnecessary overhead. Instead, in cases where these applications also use dependency injection framework such as [Spring](https://spring.io/) or [JakartaEE](https://jakarta.ee/), consider using request scopes:

* **`@RequestScope`** in Spring,
* **`@RequestScoped`** in JakartaEE, or
* the equivalent annotation in your framework.

{% hint style="danger" %}
For the session object:

* Do <mark style="color:red;">**NOT**</mark> use **`@ApplicationScope`**, **`@ApplicationScoped`**, or equivalent.
* Do <mark style="color:red;">**NOT**</mark> use **`@Singleton`** or equivalent.
  {% endhint %}

<pre class="language-java"><code class="lang-java">import com.inrupt.client.auth.Session;
import com.inrupt.client.openid.OpenIdSession;
<strong>import jakarta.enterprise.context.RequestScoped;
</strong>import jakarta.inject.Inject;
// ...

<strong>@RequestScoped
</strong>public class SessionManager {
    private Session session;

    @Inject
    JsonWebToken jwt

    Session getSession() {
        if (session == null) {
            session = OpenIdSession.ofIdToken(jwt.getRawToken());
        }
        return session;
    }
}
</code></pre>

With request scoped session, the Java runtime automatically removes references to that session at the end of a request.

### Application Scoped Clients

{% hint style="warning" %}
Disambiguation

The following refers to the client object (i.e. [SolidClient](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidClient.html), [SolidSyncClient](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidSyncClient.html)) and not the session object ([Session](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/auth/Session.html)).

Do <mark style="color:red;">**NOT**</mark> use application/singleton scope (or equivalents) with sessions.
{% endhint %}

For the client object (i.e. [SolidClient](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidClient.html), [SolidSyncClient](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidSyncClient.html)),

When possible, use application/singleton scope for clients.

<pre class="language-java"><code class="lang-java">import com.inrupt.client.solid.SolidSyncClient;
<strong>import jakarta.enterprise.context.ApplicationScoped;
</strong>// ...

<strong>@ApplicationScoped
</strong>public class ClientManager {
    private SolidSyncClient client = SolidSyncClient.getClient();

    @Produces
    SolidSyncClient getClient() {
        return client;
    }
}
</code></pre>

Then, a Web component (e.g., **`DataEndpoint`** in the following code block) of this application can use the application-scoped client and the request-scoped session in the following manner:

```java
@ApplicationScoped
@Path("/data")
public class DataEndpoint {

    @Inject
    SolidSyncClient solidClient;     // ClientManager class is ApplicationScoped. See above.

    @Inject
    SessionManager sessionMgr;       // SessionManager class is RequestScoped. See above.

    @GET
    public DataObject fetch() {
        var client = solidClient.session(sessionMgr.getSession());
        var uri = URI.create(...);
        try (var data = client.read(uri, DataObject.class)) {
            return data;
        }
    }
}
```


# Solid-OIDC Client IDs

[Solid-OIDC Client Identifiers (Client IDs)](https://solid.github.io/solid-oidc/#clientids) are URIs that dereference to a JSON-LD document, namely the [Client ID document](https://solid.github.io/solid-oidc/#clientids-document).

The Client ID document is a [JSON-LD document](https://solid.github.io/solid-oidc/#clientids-document) with:

* A **`@context`** value of **`https://www.w3.org/ns/solid/oidc-context.jsonld`**.
* Fields conformant to an [OIDC client registration](https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata).

For example, the following sample JSON-LD document may be found by dereferencing the Client ID **`https://my-app.example.com/myappid.jsonld`**:

```java
{
  "@context": "https://www.w3.org/ns/solid/oidc-context.jsonld",
  "client_id": "https://my-app.example.com/myappid.jsonld",
  "redirect_uris": ["https://my-app.example.com/callbackAfterLogin"],
  "client_name": "My Sample App",
  "client_uri": "https://my-app.example.com/",
  "logo_uri": "https://my-app.example.com/logo.png",
  "tos_uri": "https://my-app.example.com/terms.html",
  "policy_uri": "https://my-app.example.com/policy.html",
  "contacts": ["someone@example.com"],
  "scope" : "openid offline_access webid",
  "grant_types" : ["refresh_token","authorization_code"],
  "post_logout_redirect_uris": [
    "https://my-app.example.com/"
  ]
}
```

<table><thead><tr><th width="153.74609375">Field</th><th>Description</th></tr></thead><tbody><tr><td><strong><code>@context</code></strong></td><td>The context for the JSON-LD document. The expected <strong><code>@context</code></strong> value is <strong><code>https://www.w3.org/ns/solid/oidc-context.jsonld</code></strong>.</td></tr><tr><td><strong><code>client_id</code></strong></td><td>A string containing the application's Client Identifier.</td></tr><tr><td><strong><code>redirect_uris</code></strong></td><td><p>An array containing URIs where the Solid Identity Provider may redirect the user to complete the login process.</p><div data-gb-custom-block data-tag="hint" data-style="info" class="hint hint-info"><p><strong>Tip</strong><br>To test with a locally running application during development, you can specify the localhost url (i.e., <strong><code>https://localhost:&#x3C;port></code></strong>) in both:<br>• the <strong><code>redirect_uris</code></strong> in the Client Identifier, and<br>• the <strong><code>redirectUrl</code></strong> in the application's <strong><code>login()</code></strong> call.</p></div></td></tr><tr><td><strong><code>scope</code></strong></td><td><p>A string containing a space-delimited list of OAuth2.0 scopes your application is allowed to request. OAuth2.0 scopes include:</p><p>Custom values may also be specified.</p></td></tr><tr><td>Scope</td><td>Notes</td></tr><tr><td><strong><code>openid</code></strong></td><td><strong><code>openid</code></strong> is <mark style="color:red;"><strong>mandatory</strong></mark>.</td></tr><tr><td><strong><code>offline_access</code></strong></td><td>Include <strong><code>offline_access</code></strong> to be issued refresh tokens.<br><br>For the definition of <strong><code>offline_access</code></strong> scope, see <a href="https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess">OpenID Connect: Offline Access</a>.</td></tr><tr><td><strong><code>webid</code></strong></td><td><strong><code>webid</code></strong> is <mark style="color:red;"><strong>mandatory</strong></mark></td></tr><tr><td>Scope</td><td>Notes</td></tr><tr><td><strong><code>openid</code></strong></td><td><strong><code>openid</code></strong> is <mark style="color:red;"><strong>mandatory</strong></mark>.</td></tr><tr><td><strong><code>offline_access</code></strong></td><td>Include <strong><code>offline_access</code></strong> to be issued refresh tokens.<br><br>For the definition of <strong><code>offline_access</code></strong> scope, see <a href="https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess">OpenID Connect: Offline Access</a>.</td></tr><tr><td><strong><code>webid</code></strong></td><td><strong><code>webid</code></strong> is <mark style="color:red;"><strong>mandatory</strong></mark></td></tr><tr><td><strong><code>grant_types</code></strong></td><td><p>An array of OAuth 2.0 grant types that the client can use at the authorization server's token endpoint.</p><p>For additional values, see the grant_types definition in <a href="https://datatracker.ietf.org/doc/html/rfc7591#section-2">https://datatracker.ietf.org/doc/html/rfc7591#section-2</a>.</p></td></tr><tr><td>Grant Type</td><td>Description</td></tr><tr><td><strong><code>"authorization_code"</code></strong></td><td>The default authentication flow, based on redirections between the application and the Solid Identity Provider.</td></tr><tr><td><strong><code>"refresh_token"</code></strong></td><td>The flow where a refresh token is used to "refresh" an expired session.<br><br>Used for apps that have declared the offline_access scope (i.e., discouraged for in-browser apps).</td></tr><tr><td>Grant Type</td><td>Description</td></tr><tr><td><strong><code>"authorization_code"</code></strong></td><td>The default authentication flow, based on redirections between the application and the Solid Identity Provider.</td></tr><tr><td><strong><code>"refresh_token"</code></strong></td><td>The flow where a refresh token is used to "refresh" an expired session.<br><br>Used for apps that have declared the offline_access scope (i.e., discouraged for in-browser apps).</td></tr><tr><td><strong><code>client_name</code></strong></td><td>Optional. A string containing a user-friendly name for the application.</td></tr><tr><td><strong><code>client_uri</code></strong></td><td>Optional. A string containing the application's homepage URI.</td></tr><tr><td><strong><code>logo_uri</code></strong></td><td>Optional. A string containing the URI where the application's logo is available.</td></tr><tr><td><strong><code>tos_uri</code></strong></td><td>Optional. A string containing the URI where the application's terms of service are available.</td></tr><tr><td><strong><code>policy_uri</code></strong></td><td>Optional. A string containing the URI where the application's privacy policy is available.</td></tr><tr><td><strong><code>contacts</code></strong></td><td>Optional. An array of contact information for the application.</td></tr></tbody></table>

<table><thead><tr><th width="209.12890625">Grant Type</th><th>Description</th></tr></thead><tbody><tr><td><strong><code>"authorization_code"</code></strong></td><td>The default authentication flow, based on redirections between the application and the Solid Identity Provider.</td></tr><tr><td><strong><code>"refresh_token"</code></strong></td><td>The flow where a refresh token is used to "refresh" an expired session.<br><br>Used for apps that have declared the offline_access scope (i.e., discouraged for in-browser apps).</td></tr></tbody></table>

<table><thead><tr><th width="162.46875">Scope</th><th>Notes</th></tr></thead><tbody><tr><td><strong><code>openid</code></strong></td><td><strong><code>openid</code></strong> is <mark style="color:red;"><strong>mandatory</strong></mark>.</td></tr><tr><td><strong><code>offline_access</code></strong></td><td>Include <strong><code>offline_access</code></strong> to be issued refresh tokens.<br><br>For the definition of <strong><code>offline_access</code></strong> scope, see <a href="https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess">OpenID Connect: Offline Access</a>.</td></tr><tr><td><strong><code>webid</code></strong></td><td><strong><code>webid</code></strong> is <mark style="color:red;"><strong>mandatory</strong></mark></td></tr></tbody></table>

{% hint style="info" %}
**Tip**\
For additional fields to include in the document as well as more information on the aforementioned fields, see [RFC7591](https://datatracker.ietf.org/doc/html/rfc7591#section-2).
{% endhint %}


# CRUD Module

Both [SolidSyncClient](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidSyncClient.html) and [SolidClient](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidClient.html) provide methods for reading and writing resources to your Solid Pod; that is, performing create/read/update/delete (CRUD) operations. The resources can be RDF resources as well as [non-RDF resources](/sdk/java-sdk/crud-rdf-data/modeling-rdf-data) (such as **`.jpg`**, **`.pdf`**, **`.txt`**, **`.json`** files).

## `SolidClient`

[`SolidClient`](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidClient.html) is an asynchronous client for interacting with Solid resources.

```java
SolidClient client = SolidClient.getClient();
```

For an authenticated client, pass the authenticated [Session object](/sdk/java-sdk/authentication) to the client:

```java
client.session(mySession);
```

See also:

* [Authentication](/sdk/java-sdk/authentication)
* [Session Management](/sdk/java-sdk/authentication/session-management)

### `.create()`

[`SolidClient.create()`](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidClient.html#create\(T\)) creates a new resource ([SolidRDFSource](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidRDFSource.html), [SolidContainer](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidContainer.html), and [SolidNonRDFSource](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidNonRDFSource.html)) at the specified location in your Pod.

For example, assume an **`Expense`** class that extends [SolidRDFSource](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidRDFSource.html). To save an **`Expense`** object to a Pod, pass the object to the method:

```java
client.create(newExpense).toCompletableFuture().join();
```

{% hint style="warning" %}

* If any container in the location path does not exist, the method creates the missing containers as well as the resource.
* If the resource already exists at the location, the operation errors with **`PreconditionFailedException`**. Use `.update()` instead.
  {% endhint %}

### `.read()`

[`SolidClient.read()`](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidClient.html#read\(java.net.URI,java.lang.Class\)) reads a resource from your Pod and map to a specified class.

For example, assume an **`Expense`** class that extends [SolidRDFSource](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidRDFSource.html). To read the **`Expense`** resource from a Pod, pass the resource’s identifier (i.e., its URI) and the class:

```java
Expense myExpense = client.read(
   URI.create("https://..."),
   Expense.class).toCompletableFuture().join();
```

### `.update()`

[`SolidClient.update()`](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidClient.html#update\(T\)) updates a resource in your Pod.

* If the resource does not exist at the location, creates a new resource.
* If the resource already exists at the location, overwrites the resource.

For example, assume an **`Expense`** class that extends [SolidRDFSource](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidRDFSource.html). To update (or create) an **`Expense`** resource in a Pod, pass the object to the method:

```java
// ... Modify the fetched expense
// myExpense.setXXX(...);
// myExpense.setYYY(...);

// Update the RDF resource at the location
// specified in the myExpense's identifier field.

Expense response = client.update(myExpense).toCompletableFuture().join();
```

### `.delete()`

[`SolidClient.delete()`](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidClient.html#delete\(T\)) deletes a resource from your Pod.

* You can specify the URL of the resource to delete:

  ```java
  client.delete(URI.create("https://...")).toCompletableFuture().join();
  ```

or

* You can pass the object to delete:

  For example, assume an **`Expense`** class that extends [SolidRDFSource](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidRDFSource.html).

  ```java
  client.delete(new Expense(URI.create("https://..."))).toCompletableFuture().join();
  ```

{% hint style="info" %}
To delete a SolidContainer, the SolidContainer must be empty.
{% endhint %}

## SolidSyncClient

[SolidSyncClient](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidSyncClient.html) is a synchronous client for interacting with Solid resources.

```java
SolidSyncClient client = SolidSyncClient.getClient();
```

For an authenticated client, pass the authenticated [Session object](/sdk/java-sdk/authentication) to the client:

```java
client.session(mySession);
```

### `.create()`

[`SolidSyncClient.create()`](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidSyncClient.html#create\(T\)) creates a new resource ([SolidRDFSource](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidRDFSource.html), [SolidContainer](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidContainer.html), and [SolidNonRDFSource](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidNonRDFSource.html)) at the specified location in your Pod.

For example, assume an **`Expense`** class that extends [SolidRDFSource](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidRDFSource.html). To save an **`Expense`** object to a Pod, pass the object to the method:

```java
client.create(newExpense);
```

{% hint style="info" %}

* If any container in the location path does not exist, the method creates the missing containers as well as the resource.
* If the resource already exists at the location, the operation errors with `PreconditionFailedException`. Use .update() instead.
  {% endhint %}

### `.read()`

[`SolidSyncClient.read()`](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidSyncClient.html#read\(java.net.URI,java.lang.Class\)) reads a resource from your Pod and map to a specified class.

or example, assume an **`Expense`** class that extends [SolidRDFSource](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidRDFSource.html). To read the **`Expense`** resource from a Pod, pass the resource’s identifier (i.e., its URI) and the class:

```java
Expense myExpense = client.read(
   URI.create("https://..."),
   Expense.class);
```

### `.update()`

[`SolidSyncClient.update()`](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidSyncClient.html#update\(T\)) updates a resource in your Pod.

* If the resource does not exist at the location, it creates a new resource.
* If the resource already exists at the location, it overwrites the resource.

For example, assume a **`Expense`** class that extends [SolidRDFSource](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidRDFSource.html). To update (or create) an **`Expense`** resource in a Pod, pass the object to the method:

```java
// ... Modify the fetched expense
// myExpense.setXXX(...);
// myExpense.setYYY(...);

// Update the RDF resource at the location
// specified in the myExpense's identifier field.

Expense response = client.update(myExpense);
```

### `.delete()`

[`SolidSyncClient.delete()`](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidSyncClient.html#delete\(T\)) deletes a resource from your Pod.

* You can specify the URL of the resource to delete:

  ```java
  client.delete(URI.create("https://..."));
  ```

or

* You can specify the object to delete:

  For example, assume a **`Expense`** class that extends [SolidRDFSource](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidRDFSource.html).

  ```java
  client.delete(new Expense(URI.create("https://...")));
  ```

{% hint style="info" %}
To delete a SolidContainer, the SolidContainer must be empty.
{% endhint %}

## Headers

You can use the resource class’ **`getHeaders()`** method to get the headers:

* [SolidRDFSource.getHeaders()](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidRDFSource.html#method.summary)
* [SolidContainer.getHeaders()](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidContainer.html#method.summary)
* [SolidNonRDFSource.getHeaders()](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidNonRDFSource.html#method.summary)

See also [#headers](#headers "mention").

Alternatively, you can also use the resource class’ **`getMetadata()`** methods:

* [SolidRDFSource.getMetadata()](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidRDFSource.html#getMetadata\(\))
* [SolidContainer.getMetadata()](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidContainer.html#method.summary)
* [SolidNonRDFSource.getMetadata()](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidNonRDFSource.html#getMetadata\(\))

The **`getMetadata()`** methods return Solid-specific data (parsed into Java types) from a resource’s response headers. To access the complete set of response headers, use the **`getHeaders()`** method.


# WebID Profile and Pod URLs

A [WebID](/reference/glossary#webid) is a unique URL that identifies an agent in the Solid ecosystem. Dereferencing the WebID yields a **publicly** readable [WebID Profile](/reference/glossary#webid-profile) document. A WebID Profile is an [RDF Resource](/reference/glossary#rdf-resource) that contains data about the user, such as the user’s Pod URLs.

To facilitate reading WebID Profile, Inrupt’s Java Client Libraries provide the [WebIdProfile](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/webid/WebIdProfile.html) class. For example, the following code reads the Pod URLs associated with the WebID.

```java
import com.inrupt.client.webid.WebIdProfile;

// ...

public Set<URI> getPods(String webID) {
    try (final var profile = client.read(URI.create(webID), WebIdProfile.class)) {
         return profile.getStorages();
    }
}
```

{% hint style="info" %}
**Note**

Although an RDF Resource, the WebID Profile is not necessarily hosted on a Solid Pod and may not necessarily be a Solid Resource per the [Solid Protocol](https://solidproject.org/TR/protocol). That is, the WebID Profile may be mapped to the [RDFSource](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/RDFSource.html) class (which is extended by [WebIdProfile](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/webid/WebIdProfile.html)) but not necessarily to the [SolidRDFSource](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidRDFSource.html).

As such, Solid applications cannot rely on the Solid Protocol to read and write the WebID profile. See also <https://solid.github.io/webid-profile/#introduction>.
{% endhint %}


# Headers

To specify headers on all client requests, you can set the headers for the SolidClient/SolidSyncClient. To read the response headers, you can use the **`getHeaders()`** method for the resource:

For example:

```java
import com.inrupt.client.Headers;
import com.inrupt.client.solid.SolidClient;

// ...
// ...

final SolidClient mySolidClient = SolidClient.getClientBuilder()
                .headers(Headers.of(Map.of("x-request-id", List.of("7492595229158059")))).build()
                .session(session);

client.read(URI.create(resourceURL), MyExtendedRDF.class)
    .thenAccept(responseResource -> {
       List<String> responseHeaderValues = responseResource.getHeaders().allValues("x-request-id");
       // ...
});
```

To specify headers per request, you can set the headers at the request operation level. To read the response headers, you can use the **`getHeaders()`** method for the resource:

For example:

```java
import com.inrupt.client.Headers;
import com.inrupt.client.solid.SolidClient;

// ...
// ...

final SolidClient mySolidClient = SolidClient.getClient().session(session);

Headers myHeaders = Headers.of(Map.of("x-request-id", List.of("7492595229158059")));

mySolidClient.read(URI.create(resourceURL), myHeaders, Expense.class)
   .thenAccept(responseExpense -> {
       List<String> responseHeaderValues = responseExpense.getHeaders().allValues("x-request-id");
       // ...
});
```

See:

* [Headers](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/Headers.html)
* [SolidRDFSource.getHeaders()](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidRDFSource.html#method.summary)
* [SolidContainer.getHeaders()](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidContainer.html#method.summary)
* [SolidNonRDFSource.getHeaders()](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidNonRDFSource.html#method.summary)

### Additional Information

Alternatively, you can also use the resource class’ **`getMetadata()`** methods:

* [SolidRDFSource.getMetadata()](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidRDFSource.html#getMetadata\(\))
* [SolidContainer.getMetadata()](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidContainer.html#method.summary)
* [SolidNonRDFSource.getMetadata()](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidNonRDFSource.html#getMetadata\(\))

The **`getMetadata()`** methods return Solid-specific data (parsed into Java types) from a resource’s response headers. To access the complete set of response headers, use the **`getHeaders()`** methods.


# Low-level HTTP Requests

In addition to the high-level APIs **`.create()`**, **`.read()`**, **`.update()`**, **`.delete()`**, to perform low-level HTTP requests, the Java Client Libraries include:

* [Request.Builder](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/Request.Builder.html) to build HTTP requests.
* [Request.BodyPublishers](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/Request.BodyPublishers.html) to handle request body payloads.
* **`.send()`** method in [SolidSyncClient](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidSyncClient.html) and [SolidClient](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidClient.html) to send the request.
* [Response.BodyHandlers](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/Response.BodyHandlers.html) to handle response body payloads.

For example, the following uses the Java Client Library to create a **`PUT`** request to save a file to a Pod and send the request:

```java
import com.inrupt.client.Request;
import com.inrupt.client.Response;
import java.io.InputStream;
import java.io.IOException;
//...

// MultipartFile file = ...

try (final var fileStream = file.getInputStream()) {
    Request request = Request.newBuilder()
       .uri(URI.create("https://storage.example.com/some/resource"))
       .header("Content-Type", file.getContentType())
       .PUT(Request.BodyPublishers.ofInputStream(fileStream))
       .build();
    Response<Void> response = client.send(
       request,
       Response.BodyHandlers.discarding());

} catch (IOException e1) {
    e1.printStackTrace();
}
```


# CRUD (RDF Data)

## `SolidRDFSource`

The [SolidRDFSource](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidRDFSource.html) class maps to an RDF resource stored or to be stored in a Solid Pod.

A summary of parameters to the [SolidRDFSource](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidRDFSource.html) class constructors are as follows:

<table><thead><tr><th width="132.30255126953125">Field</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td><strong><code>identifier</code></strong></td><td><a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/URI.html">java.net.URI</a></td><td>The URI (Uniform Resource Identifier) of the resource.</td></tr><tr><td><strong><code>dataset</code></strong></td><td><a href="https://commons.apache.org/proper/commons-rdf/apidocs/org/apache/commons/rdf/api/Dataset.html">org.apache.commons.rdf.api.Dataset</a></td><td>The <a href="https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-dataset">RDF dataset</a> (i.e., the set(s) of triples) contained in the resource.</td></tr><tr><td><strong><code>headers</code></strong></td><td><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/Headers.html">com.inrupt.client.Headers</a></td><td>Collection of HTTP headers.</td></tr></tbody></table>

To instantiate, specify the identifier (i.e., the URI) for the resource:

```java
// Locally instantiate a new resource
SolidRDFSource newResource = new SolidRDFSource(
      URI.create("https://pod.example.com/resource/path"));  // identifier
```

Optionally, you can also initialize the resource data during instantiation by including an [RDF dataset](https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-dataset):

```java
// Locally instantiate a new resource with initial RDF Dataset
SolidRDFSource newResourcePopulated = new SolidRDFSource(
      URI.create("https://pod.example.com/resource/path"),  // identifier
      initialDataset);                                                  // dataset
```

You can extended the [SolidRDFSource](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidRDFSource.html) class to model a POJO (Plain Old Java Object) class as an RDF resources. See [Modeling an RDF Resource](/sdk/java-sdk/crud-rdf-data/modeling-rdf-data) for information on modeling POJOs as RDF resources.

### **Class Methods**

You can:

* Use the [SolidRDFSource](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidRDFSource.html) class [methods](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidRDFSource.html#method.summary) to directly interact with local [SolidRDFSource](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidRDFSource.html) object’s dataset or
* Or, if you have extended the class to model a POJO (Plain Old Java Object) class as an RDF resources, use that class methods/members.

  ```java
  public class Expense extends SolidRDFSource {
     // ...

  }
  ```

### **Read/Write to Pod**

To read RDF resources from your Pod or write RDF resources to your Pod (i.e., CRUD operations), the library provides [SolidSyncClient](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidSyncClient.html) and [SolidClient](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidClient.html) classes. See [CRUD Module](/sdk/java-sdk/crud-data) for details.

{% hint style="info" %}
When saving a new resource to a Pod (e.g., **`https://pod.example.com/container1/container2/resource`**), if any Container in the resource path does not exist (e.g., **`container1/`** and **`container2/`**), the [SolidSyncClient.create()](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidSyncClient.html#create\(T\)) and [SolidClient.create()](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidClient.html#create\(T\)) methods creates the missing containers as well.
{% endhint %}

For the CRUD operation response headers, you can use [SolidRDFSource.getHeaders()](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidRDFSource.html#method.summary). See [Headers](/sdk/java-sdk/crud-data/appendix-headers).

## Container

A [Container](/reference/glossary#container) is an [RDF resource](/reference/glossary#rdf-resource) that can contain other RDF (including other Containers) and [non-RDF resources](/reference/glossary#non-rdf-resource). A Container is analogous to a folder in a file system.

{% hint style="warning" %}
Container URIs always end with a slash **`/`**.
{% endhint %}

### `SolidContainer`

The [SolidContainer](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidContainer.html) class maps to a Container stored or to be stored in a Solid Pod. [\[1\]](#solid) The [SolidContainer](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidContainer.html) class extends [SolidRDFSource](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidRDFSource.html).

To instantiate, specify the destination URI for the Container. Container URIs always end with a slash **`/`**.

```java
// Locally instantiate a new Container.
// Container URIs ends with a slash "/"
SolidContainer newResource = new SolidContainer(
      URI.create("https://pod.example.com/container/path/"));
```

### **Class Methods**

You can use [SolidContainer](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidContainer.html) class [methods](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidContainer.html#method.summary) to interact with a Container directly. For example, to retrieve all contained resources within a Container, you can use [getResources](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidContainer.html#getResources\(\)).

### **Read/Write to Pod**

To read Containers from your Pod or write Containers to your Pod (i.e., CRUD operations), the library provides [SolidSyncClient](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidSyncClient.html) and [SolidClient](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidClient.html) classes.

{% hint style="info" %}
**Tip**

* Although you can instantiate and save a [SolidContainer](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidContainer.html) by itself, when creating a resource to a Pod (e.g., **`https://pod.example.com/container1/container2/resource`**), if any Container in the location path does not exist (e.g., **`container1/`** and **`container2/`**), the [SolidSyncClient.create()](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidSyncClient.html#create\(T\)) and [SolidClient.create()](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidClient.html#create\(T\)) methods creates the missing containers as well as the resource.
* To delete a Container, the Container must be empty.
  {% endhint %}

For the CRUD operation response headers, you can use [SolidContainer.getHeaders()](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidContainer.html#method.summary). See [Headers](/sdk/java-sdk/crud-data/appendix-headers).

## Non-RDF Resource

A [non-RDF Resource](/reference/glossary#non-rdf-resource) is any non-RDF binary or text file, such as **`.pdf`**, **`.jpeg`**, etc.

### `SolidNonRDFSource`

The [SolidNonRDFSource](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidNonRDFSource.html) class maps to a non-RDF resources stored or to be stored in a Solid Pod.

A summary of parameters to the [SolidNonRDFSource](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidNonRDFSource.html) class constructors are as follows:

<table><thead><tr><th width="170.88214111328125">Field</th><th width="207.2470703125">Type</th><th>Description</th></tr></thead><tbody><tr><td><strong><code>identifier</code></strong></td><td><a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/URI.html">java.net.URI</a></td><td>The <a href="/pages/7PKZaxnnUDGdJ8gPDOqL#uri">URI</a> (Uniform Resource Identifier) of the resource.</td></tr><tr><td><strong><code>contentType</code></strong></td><td><a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/String.html">java.lang.String</a></td><td>The MIME type for the file.</td></tr><tr><td><strong><code>entity</code></strong></td><td><a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/InputStream.html">java.io.InputStream</a></td><td>Input stream of the file content.</td></tr></tbody></table>

To instantiate, specify the identifier (i.e., the URI), the content type (i.e., MIME type), and the input stream for the resource. For example, the following instantiates a SolidNonRDFSource for a **`.jpg`** file:

```java
MultipartFile file = //... Some .jpg file

// Locally instantiate a new SolidNonRDFSource.
SolidNonRDFSource newNonRDFSource = new SolidNonRDFSource(
   URI.create("https://pod.example.com/container1/somePic.jpg"),  // identifier
   file.getContentType(),                 // MIME type
   file.getInputStream());                // InputStream
```

### **Read/Write to Pod**

To read non-RDF resources from your Pod or write non-RDF resources to your Pod (i.e., CRUD operations), the library provides [SolidSyncClient](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidSyncClient.html) and [SolidClient](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidClient.html) classes. See [CRUD Module](/sdk/java-sdk/crud-data) for details.

{% hint style="info" %}
**Tip**

When saving a new resource to a Pod (e.g., **`https://pod.example.com/container1/container2/somePic.jpg`**), if any Container in the resource path does not exist (e.g., **`container1/`** and **`container2/`**), the [SolidSyncClient.create()](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidSyncClient.html#create\(T\)) and [SolidClient.create()](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidClient.html#create\(T\)) methods creates the missing containers as well.
{% endhint %}

For the CRUD operation response headers, you can use [SolidNonRDFSource.getHeaders()](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidNonRDFSource.html#method.summary). See [Headers](/sdk/java-sdk/crud-data/appendix-headers).


# Modeling an RDF Resource

The following content highlights the key modifications to a Java class to convert it into an [RDF resource](/reference/glossary#rdf-resource) class. As a starting point, consider a non-RDF class **`Expense`**. Such a class may be implemented similarly to the following sample code:

```java
// Non-RDF Class

public class Expense {
    private UUID _id;
    private Date date;
    private String description;
    // ... Additional fields

    public Expense() {

    }

    public Expense(date, description, //...) {

       this.date = date;
       this.description = descriptions;
       //...
    }

    public Date getDate() {
         return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    // ... Additional getters/setters and other content

}
```

### 1. Extend the SolidRDFSource

The [SolidRDFSource](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidRDFSource.html) class maps to an RDF resource. A summary of parameters to the [SolidRDFSource](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidRDFSource.html) class constructors are as follows:

<table><thead><tr><th width="138.55078125">Field</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td><strong><code>identifier</code></strong></td><td><a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/URI.html">java.net.URI</a></td><td>The URI (Uniform Resource Identifier) of the resource.</td></tr><tr><td><strong><code>dataset</code></strong></td><td><a href="https://commons.apache.org/proper/commons-rdf/apidocs/org/apache/commons/rdf/api/Dataset.html">org.apache.commons.rdf.api.Dataset</a></td><td>The RDF dataset (i.e., the sets of triples) contained in the resource.</td></tr><tr><td><strong><code>headers</code></strong></td><td><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/Headers.html">com.inrupt.client.Headers</a></td><td>Collection of HTTP headers.</td></tr></tbody></table>

To model the **`Expense`** class as an RDF resource, the class:

* Extends the [SolidRDFSource](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidRDFSource.html) and
* Adds constructors for the [SolidRDFSource](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidRDFSource.html) fields (**`identifier`**, **`dataset`**, **`headers`**).

```java
public class Expense extends SolidRDFSource {

   private final Node subject;
   // ...

   public Expense(final URI identifier, final Dataset dataset, final Headers headers) {
       super(identifier, dataset, headers);
       // ...
   }

   public Expense(final URI identifier) {
       this(identifier, null, null);
   }

   public Expense(final URI identifier,
                  String merchantProvider,
                  Date expenseDate,
                  String description,
                  BigDecimal amount,
                  String currency,
                  String category) {
       this(identifier, null, null);
       //...
   }
}
```

{% hint style="warning" %}
The SolidRDFSource constructor that accepts Metadata as a parameter (instead of Headers) has been deprecated.
{% endhint %}

### 2. Inner Class that Extends WrapperIRI

A sample RDF file that for a sample Expense data is shown below in [Turtle format](/reference/glossary#turtle):

```turtle
<https://pod.example.com/container/expenses/202303/teamlunch>
     <http://www.w3.org/1999/02/22-rdf-syntax-ns#type>   <https://schema.org/Invoice> ;
     <https://schema.org/purchaseDate>    "2023-03-07T00:00:00Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> ;
     <https://schema.org/provider>        "Example Restaurant" ;
     <https://schema.org/description>     "Team Lunch" ;
     <https://schema.org/category>        "Travel and Entertainment" ;
     <https://schema.org/priceCurrency>   "USD" ;
     <https://schema.org/totalPrice>      "120"^^<http://www.w3.org/2001/XMLSchema#decimal> ;
     <https://schema.org/image>           <https://pod.example.com/container/expenses/202303/receipt1.jpg> .
```

where:

<table data-header-hidden><thead><tr><th width="121.48828125"></th><th width="103.29296875"></th><th></th></tr></thead><tbody><tr><td><code>subject</code></td><td>URL</td><td><code>&#x3C;https://pod.example.com/container/expenses/202303/teamlunch></code></td></tr><tr><td><code>predicates</code></td><td>URL</td><td><ul><li><code>&#x3C;http://www.w3.org/1999/02/22-rdf-syntax-ns#type></code></li><li><code>&#x3C;https://schema.org/purchaseDate></code></li><li><code>&#x3C;https://schema.org/provider></code></li><li>etc.</li></ul></td></tr><tr><td><code>objects</code></td><td>Literals or URLS</td><td><ul><li><code>"2023-03-07T00:00:00Z"^^&#x3C;http://www.w3.org/2001/XMLSchema#dateTime></code></li><li><code>"Example Restaurant"</code>,</li><li><code>&#x3C;https://pod.example.com/expenses/receipt1.jpg></code></li><li>etc.</li></ul></td></tr></tbody></table>

To handle the mapping of the expense data (date, provider, description, category, priceCurrency, total) to RDF triples (**`<subject> <predicate> <object>`**):

* The **`Expense`** class defines the predicate IRIs. That is, instead of having a Class field named **`date`**, for the RDF Resource, the value of the field is mapped to the IRI **`"https://schema.org/purchaseDate"`**.
* The **`Expense`** class defines an inner class **`Node`** that extends the [WrapperIRI](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/rdf/wrapping/commons/WrapperIRI.html) class.

```java
public class Expense extends SolidRDFSource {

   // ...
    static IRI SCHEMA_DATE = rdf.createIRI("https://schema.org/purchaseDate");
    static IRI SCHEMA_PROVIDER = rdf.createIRI("https://schema.org/provider");
    static IRI SCHEMA_DESCRIPTION = rdf.createIRI("https://schema.org/description");
    static IRI SCHEMA_TOTAL_PRICE = rdf.createIRI("https://schema.org/totalPrice");
    static IRI SCHEMA_CURRENCY = rdf.createIRI("https://schema.org/priceCurrency");
    static IRI SCHEMA_CATEGORY = rdf.createIRI("https://schema.org/category");
    static IRI SCHEMA_IMAGE = rdf.createIRI("https://schema.org/image");

    // ...

    class Node extends WrapperIRI {

       Node(final RDFTerm original, final Graph graph) {
           super(original, graph);
       }

       URI getRDFType() {
           return anyOrNull(RDF_TYPE, ValueMappings::iriAsUri);
       }

       void setRDFType(String type) {
           overwriteNullable(RDF_TYPE, type, TermMappings::asIri);
       }

       String getMerchantProvider() {
           return anyOrNull(SCHEMA_PROVIDER, ValueMappings::literalAsString);
       }


      String getMerchantProvider() {
          return anyOrNull(SCHEMA_PROVIDER, ValueMappings::literalAsString);
      }

      void setMerchantProvider(String provider) {
          overwriteNullable(SCHEMA_PROVIDER, provider, TermMappings::asStringLiteral);
      }

      // ...

    }
}
```

### Add the Inner Class Field to the RDF Resource Class

To use the defined mappings in the inner class:

* Update **`Expense.java`** to include a new field **`subject`** set to the inner class **`Node`**.
* Update the **`Expense.java`** getters and setters to use the **`subject`**‘s getters and setters.

```java
public class Expense extends SolidRDFSource {

   // ...


    private final Node subject;

    // ...

    public URI getRDFType() {
        return subject.getRDFType();
    }

    public void setRdfType(String rdfType) {
        subject.setRDFType(rdfType);
    }

    public String getMerchantProvider() {
        return subject.getMerchantProvider();
    }

    public void setMerchantProvider(String merchantProvider) {
        subject.setMerchantProvider(merchantProvider);
    }

    public Date getExpenseDate() {
        return subject.getExpenseDate();
    }

    public void setExpenseDate(Date expenseDate) {
        subject.setExpenseDate(expenseDate);
    }

    //...

}
```


# Access Requests and Grants

Inrupt’s Enterprise Solid Server (ESS) provides support for Access Request and Grants. With Access Requests and Grants:

* An [agent](/reference/glossary#agent) can request access to [Resources](/reference/glossary#resource) hosted on a [Pod](/reference/glossary#pods). This Access Request includes the specific [access mode](/reference/glossary#access-modes) (e.g., read, write, append) being requested, the Resources to access, the Purpose for which the data will be used, and other optional fields.
* The owner of the requested Resources (i.e., individuals with Control access to the requested Resources) can review the Access Request and either approve the Access Request, resulting in an Access Grant, or deny the Access Request, resulting in an Access Denial.
* If the requesting agent has an Access Grant, the requesting agent can exchange the Access Grant for an access token in order to access the Resources.

## Enable Use of Access Grants

ESS uses [Access Control Policy (ACP)](https://docs.inrupt.com/security/authorization/acp) to define policies that determine access to Pod resources. To be able to use Access Grants for a Resource, the Resource must have a policy that enables the use of Access Grants.

{% hint style="warning" %}
The policy only *enables* the use of Access Grants on the Resource for the access modes specified in the policy. To determine the access for an agent who is using an Access Grant, ESS uses the *intersection* of:

* The allowed access specified by the resource’s ACP, and
* The granted access specified in the Access Grant for that Resource.

For example:

* A Resource has a policy that enables the use of Access Grants for **`Read`** access.
* A requesting agent has received an Access Grant for that resource that allows **`Read`** and **`Write`** access.

Then:

* The requesting agent can use the Access Grant to **`Read`** the resource **only**.
* The requesting agent **cannot** use the Access Grant to **`Write`** the resource, even though the Access Grant specifies both **`Read`** and **`Write`** access.
  {% endhint %}

ESS enables the use of Access Grants by default. Specifically, when ESS creates a **new** Pod, ESS creates default policies that enable the use of Access Grants for that Pod.

## **`inrupt-client-accessgrant`**

To handle Access Requests and Grants, Inrupt’s Java Client Library provides the **`inrupt-client-accessgrant`** module. See [Installation](/sdk/java-sdk/installation).

The **`inrupt-client-accessgrant`** provides:

<table data-header-hidden><thead><tr><th width="193.1484375"></th><th></th></tr></thead><tbody><tr><td><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantClient.html">AccessGrantClient</a></td><td><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantClient.html">AccessGrantClient</a> can interact with the <a href="https://docs.inrupt.com/ess/latest/services/service-access-grant/">ESS Access Grant Service</a>; specifically, <a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantClient.html">AccessGrantClient</a> can be used to create/verify/query/fetch Access Requests and Grants.</td></tr><tr><td><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantSession.html">AccessGrantSession</a></td><td><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantSession.html">AccessGrantSession</a> allows for the use of Access Grants to interact with Resources; specifically, using an <a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantSession.html">AccessGrantSession</a>, <a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidClient.html">SolidClient</a>/<a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidSyncClient.html">SolidSyncClient</a> can access Resources using the Access Grants.</td></tr></tbody></table>


# Create Access Requests/Grants

## `AccessGrantClient`

The **`inrupt-client-accessgrant`** module provides an [AccessGrantClient](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantClient.html) that interacts with the [ESS Access Grant Service](https://docs.inrupt.com/ess/latest/services/service-access-grant/) to create/verify/query/fetch Access Requests and Grants.

To interact with the service, the [AccessGrantClient](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantClient.html) has the following methods:

<table><thead><tr><th width="290.54296875">Method</th><th>Description</th></tr></thead><tbody><tr><td><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantClient.html#requestAccess(com.inrupt.client.accessgrant.AccessRequest.RequestParameters)">AccessGrantClient.requestAccess</a></td><td>Creates Access Requests.</td></tr><tr><td><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantClient.html#grantAccess(com.inrupt.client.accessgrant.AccessRequest)">AccessGrantClient.grantAccess</a></td><td>Creates Access Grants.</td></tr><tr><td><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantClient.html#revoke(com.inrupt.client.accessgrant.AccessCredential)">AccessGrantClient.revoke</a></td><td>Updates the status of the Access Requests/Grantss to revoked.</td></tr><tr><td><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantClient.html#query(com.inrupt.client.accessgrant.AccessCredentialQuery)">AccessGrantClient.query</a></td><td>Queries for Access Requests and Grants.</td></tr><tr><td><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantClient.html#denyAccess(com.inrupt.client.accessgrant.AccessRequest)">AccessGrantClient.denyAccess</a></td><td>Creates Access Request denials.</td></tr><tr><td><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantClient.html#verify(com.inrupt.client.accessgrant.AccessCredential)">AccessGrantClient.verify</a></td><td>Performs <a href="https://docs.inrupt.com/ess/latest/services/service-access-grant/service-access-grant-verifier/">validation checks</a> on Access Requests/Grantss, such as signature validation, date validation, etc.</td></tr></tbody></table>

To instantiate an [AccessGrantClient](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantClient.html), you need to pass in the URI of the ESS Access Grant Service. For example, the Access Grant Service for Inrupt’s [PodSpaces](/podspaces/podspaces) runs at **`https://vc.inrupt.com`**.

### Create an Access Request

An application can use [AccessGrantClient.requestAccess](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantClient.html#requestAccess\(com.inrupt.client.accessgrant.AccessRequest.RequestParameters\)) to create an Access Request.

For example, a user visits an ExamplePrinter’s website which provides photo printing services. When the ExamplePrinter’s web application asks for the photos to print, the user enters the URLs of the photos that are located in the user’s Pod. To continue, the ExamplePrinter’s backend server uses [AccessGrantClient.requestAccess](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantClient.html#requestAccess\(com.inrupt.client.accessgrant.AccessRequest.RequestParameters\)) to create Access Requests to read the photos.

#### 1. Instantiate the Requestor’s AccessGrantClient

To instantiate an [AccessGrantClient](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantClient.html), call the constructor with the following parameters:

<table><thead><tr><th width="221.8984375">Parameters</th><th>Descriptions</th></tr></thead><tbody><tr><td>Access Grant Service URI</td><td>The root URL of the ESS Access Grant Service.</td></tr><tr><td>Authenticated Session</td><td>The authenticated session of the requestor.</td></tr></tbody></table>

For example, the following instantiates an [AccessGrantClient](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantClient.html) for ExamplePrinter using the Inrupt PodSpaces Access Grant Service URI and ExamplePrinter’s authenticated session:

```java
public class ExamplePrinterRequestingClass {

   private final URI PS_ACCESS_GRANT_URI = URI.create("https://vc.inrupt.com");
   private Session session;  // Session for ExamplePrinter.
   // ... Logic to initialize the ExamplePrinter's session has been omitted for brevity.

   private final AccessGrantClient accessgrantClient = new AccessGrantClient(PS_ACCESS_GRANT_URI)
         .session(session);

   // ...
   // ...

}
```

{% hint style="info" %}
In these examples, logic to initialize an authenticated session for ExamplePrinter has been omitted for brevity.
{% endhint %}

#### 2. Create the Access Request

To create an Access Request, call **`AccessRequest.requestAccess`** with the request details:

<table><thead><tr><th width="253.63671875">Parameters</th><th>Descriptions</th></tr></thead><tbody><tr><td>Resource Owner</td><td>The WebID of the agent who controls access to the requested resource(s).</td></tr><tr><td>Requested resource(s)</td><td>Resource(s) to which the access is being requested.</td></tr><tr><td>Requested access mode(s).</td><td><p>Requested access modes. Available modes are:</p><ul><li><code>"Read"</code>,</li><li><code>"Write"</code>, and</li><li><code>"Append"</code>.</li></ul></td></tr><tr><td>Optional. Purpose(s) for the request.</td><td>URI(s) indicating the stated purpose(s) for the request.</td></tr><tr><td>Optional (but Recommended) Expiration Date.</td><td><p>Expiration date of the Access Request and subsequent Access Grant, if approved.</p><p>ESS Access Grant Service may be <a href="https://docs.inrupt.com/ess/latest/services/service-access-grant/#cmdoption-agconfig-arg-INRUPT_VC_MAX_DURATION"><code>configured</code></a> to issue Access Requests and Grants with earlier expiration date.</p></td></tr></tbody></table>

The **`AccessRequest.requestAccess`** accepts:

* request details as [AccessRequest.RequestParameters](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessRequest.RequestParameters.html), which is a collection of the request parameters; or
* request details as individual parameters.

{% tabs %}
{% tab title="Request Parameters" %}
[AccessGrantClient.requestAccess(AccessRequest.RequestParameters)](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantClient.html#requestAccess\(com.inrupt.client.accessgrant.AccessRequest.RequestParameters%20requestParams\)) accepts the Access Request details as [AccessRequest.RequestParameters](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessRequest.RequestParameters.html). To build the [AccessRequest.RequestParameters](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessRequest.RequestParameters.html) object, you can use [AccessRequest.RequestParameters.Builder](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessRequest.RequestParameters.Builder.html) and its methods.

For example, the following code uses:

* [AccessRequest.RequestParameters.Builder](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessRequest.RequestParameters.Builder.html) to specify the request details (i.e., the resource owner, the resources, the modes, the purposes, and the expiration); and
* ExamplePrinter’s instantiated AccessGrantClient to create the request (i.e., the requestor is ExamplePrinter).

<pre class="language-java"><code class="lang-java">public AccessRequest createReadRequest(List&#x3C;String> resourceURLs, String resourceOwner) {
    URI resourceOwnerURI = URI.create(resourceOwner);
    Set&#x3C;URI> resourcesURIs = resourceURLs.stream().map(URI::create).collect(Collectors.toSet());

    Set&#x3C;String> modes = Set.of("Read"); // Available modes are "Read", "Write", and "Append".

    Set&#x3C;URI> purposes = Set.of(
                URI.create("https://purpose.example.com/PhotoPrinting"),
                URI.create("https://purpose.example.com/ServiceProvision"));

    Instant currentInstant = Instant.now();
    Instant expiration = currentInstant.plus(30, ChronoUnit.MINUTES);

<strong>    AccessRequest.RequestParameters requestParams = AccessRequest.RequestParameters.newBuilder()
</strong><strong>            .recipient(resourceOwnerURI)
</strong><strong>            .resources(resourcesURIs)
</strong><strong>            .modes(modes)
</strong><strong>            .purposes(purposes)
</strong><strong>            .expiration(expiration)
</strong><strong>            .build();
</strong>
    AccessRequest accessRequest = accessgrantClient.requestAccess(requestParams).toCompletableFuture().join();

    return accessRequest;
}
</code></pre>

{% endtab %}

{% tab title="Individual Request Parameters" %}
[AccessGrantClient.requestAccess(URI recipient, Set\<URI> resources, Set\<String> modes, Set\<URI> purposes, Instant expiration)](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantClient.html#requestAccess\(java.net.URI,java.util.Set,java.util.Set,java.util.Set,java.time.Instant\)) can accept the request details as individual parameters:

For example, the following code uses ExamplePrinter’s instantiated AccessGrantClient to create a Read request:

<pre class="language-java"><code class="lang-java">public AccessRequest createReadRequest(List&#x3C;String> resourceURLs, String resourceOwner) {
    URI resourceOwnerURI = URI.create(resourceOwner);
    Set&#x3C;URI> resourcesURIs = resourceURLs.stream().map(URI::create).collect(Collectors.toSet());

    Set&#x3C;String> modes = Set.of("Read"); // Available modes are "Read", "Write", and "Append".

    Set&#x3C;URI> purposes = Set.of(
                URI.create("https://purpose.example.com/PhotoPrinting"),
                URI.create("https://purpose.example.com/ServiceProvision"));

    Instant currentInstant = Instant.now();
    Instant expiration = currentInstant.plus(30, ChronoUnit.MINUTES);

<strong>    AccessRequest accessRequest = accessgrantClient.requestAccess(
</strong><strong>        resourceOwnerURI,
</strong><strong>        resourcesURIs,
</strong><strong>        modes,
</strong><strong>        purposes,
</strong><strong>        expiration).toCompletableFuture().join();
</strong>
    return accessRequest;
}
</code></pre>

{% endtab %}
{% endtabs %}

### Create an Access Grant

Resource owners can use their access management application to view Access Requests made to them and decide whether to grant the requested access or not. If the resource owner decides to grant an Access Request, the access management application can call [AccessGrantClient.grantAccess](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantClient.html#grantAccess\(com.inrupt.client.accessgrant.AccessRequest\)) to create an Access Grant. Optionally, the application can also call [AccessGrantClient.verify](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantClient.html#verify\(com.inrupt.client.accessgrant.AccessCredential\)) to verify the Access Request before displaying it to the user.

For example, the user (the resource owner) who visited ExamplePrinter’s website to print pictures can login to a trusted access management application. The access management application can display the Access Request made to the user by the ExamplePrinter. If the user decides to grant the requested access, the application can create an Access Grant. The access manage application has a backend server that uses various [AccessGrantClient](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantClient.html) methods, namely:

#### 1. Instantiate the Grantor’s AccessGrantClient

To instantiate an [AccessGrantClient](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantClient.html) for the grantor/resource owner, call the constructor with the following parameters:

<table><thead><tr><th width="235.71484375">Parameters</th><th>Descriptions</th></tr></thead><tbody><tr><td>Access Grant Service URL</td><td>The root URL of the ESS Access Grant Service</td></tr><tr><td>Authenticated Session</td><td>The authenticated session of the user (the resource owner).</td></tr></tbody></table>

For example, the following instantiates an [AccessGrantClient](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantClient.html) using Inrupt PodSpaces Access Grant Service URI and the resource owner’s session:

<pre class="language-java"><code class="lang-java">public class AccessManagementAppRequestHandler {

   private final URI PS_ACCESS_GRANT_URI = URI.create("https://vc.inrupt.com");
   private Session userSession;  // Session for a resource owner.
   // ... Logic to initialize a session for a resource owner has been omitted for brevity.

<strong>   AccessGrantClient agClientForUser = new AccessGrantClient(PS_ACCESS_GRANT_URI)
</strong><strong>      .session(userSession);
</strong>
   // ...
   // ...

}
</code></pre>

#### 2. Get the Access Requests Made to the User

{% hint style="info" %}
The user can only retrieve Access Requests where the user is either the requestor (creator of the Access Request) or the resource owner (recipient of the Access Request).
{% endhint %}

If the Access Request’s id is known, the application can directly retrieve the Access Request using [AccessGrantClient.fetch](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantClient.html#fetch\(java.net.URI,java.lang.Class\)) with the Access Request’s id.

The **`fetch`** operation can return expired or future Access Requests.

```java
// String requestID = "https://vc.{ESS DOMAIN}/vc/xxxxxx...";
AccessRequest accessRequest = agClientForUser.fetch(URI.create(requestID), AccessRequest.class)
      .toCompletableFuture()
      .join();
```

#### 3. Verify the Requested Access

To [verify the Access Request](https://docs.inrupt.com/ess/latest/services/service-access-grant/service-access-grant-verifier/), use [AccessGrantClient.verify](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantClient.html#verify\(com.inrupt.client.accessgrant.AccessCredential\)), passing it the Access Request.

For example, the following example uses [AccessGrantClient.verify](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantClient.html#verify\(com.inrupt.client.accessgrant.AccessCredential\)) to verify an Access Request, checking for errors and warning.

```java
// AccessRequest accessRequest = ...;

AccessCredentialVerification verification = agClientForUser.verify(accessRequest).toCompletableFuture().join();

if (verification.getChecks().isEmpty() || !verification.getErrors().isEmpty()) {
   // Handle invalid Access Request
}

if (!verification.getWarnings().isEmpty()) {
   // Handle warnings
}
```

#### 4. Create the Access Grant

For a valid Access Request, if the resource owner decides to grant the requested access, the application can call [AccessGrantClient.grantAccess](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantClient.html#grantAccess\(com.inrupt.client.accessgrant.AccessRequest\)), passing in the specific Access Request to grant.

For example:

```java
AccessGrant accessGrant = agClientForUser.grantAccess(accessRequest).toCompletableFuture().join();
```


# Use Access Grants to Access Resources

## `AccessGrantSession`

The **`inrupt-client-accessgrant`** module provides an [AccessGrantSession](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantSession.html) that builds an authenticated [Session](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/auth/Session.html) object using both:

* an OpenID-based session and
* one or more access grants.

Then, a [SolidClient](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidClient.html)/[SolidSyncClient](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidSyncClient.html) can use this session to access resources using the approved Access Grant(s).

Continuing the example from [Create Access Requests/Grants](/sdk/java-sdk/access-requests-and-grants/create-access-requests-grants), ExamplePrinter backend server, with the appropriate access grants, can access the resources for printing.

For convenience, ExamplePrinter’s backend server’s code to instantiate [AccessGrantClient](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantClient.html) for ExamplePrinter is repeated here:

```java
public class ExamplePrinterRequestingClass {

   private final URI PS_ACCESS_GRANT_URI = URI.create("https://vc.inrupt.com");
   private Session session;  // Session for ExamplePrinter.
   // ... Logic to initialize the ExamplePrinter's session has been omitted for brevity.

   private final AccessGrantClient accessgrantClient = new AccessGrantClient(PS_ACCESS_GRANT_URI)
         .session(session);

   // ...
   // ...

}
```

#### 1. Get the Access Grant(s) to Use

{% hint style="info" %}
The user can only access those access grants where the user is the creator of the Access Grant (i.e., the grantor) or the recipient of the Access Grant (i.e., the grantee). That is, the ESS’ Access Grant Service only returns those access grants where the user is the creator or the recipient.
{% endhint %}

{% tabs %}
{% tab title="Fetch a Specific Access Grant" %}
If the Access Grant’s id is known, the application can directly retrieve the Access Grant using [AccessGrantClient.fetch](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantClient.html#fetch\(java.net.URI,java.lang.Class\)) with the Access Grant’s id.

{% hint style="info" %}
The **`fetch`** operation can return expired or future access grants.
{% endhint %}

```java
// String grantID = "https://vc.{ESS DOMAIN}/vc/xxxxxx...";
AccessGrant accessGrant = accessgrantClient.fetch(URI.create(grantID), AccessGrant.class)
      .toCompletableFuture()
      .join();
```

{% endtab %}

{% tab title="Query for Access Grants" %}
The application can use [AccessGrantClient.query](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantClient.html#query\(com.inrupt.client.accessgrant.AccessCredentialQuery\)) to query for active (i.e., current and not expired) access grants. To use [AccessGrantClient.query](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantClient.html#query\(com.inrupt.client.accessgrant.AccessCredentialQuery\)) for access grants, you can pass in a [CredentialFilter](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/CredentialFilter.html) object `CredentialFilter<AccessGrant>` that specifies the query filter values (i.e., a combination of the resource, creator, recipient, purpose, and type).

You can use [CredentialFilter.Builder](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/CredentialFilter.Builder.html) and its [methods](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/CredentialFilter.Builder.html#method.summary) to build a [CredentialFilter](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/CredentialFilter.html) for access grants:

<table><thead><tr><th width="133.3046875">Method</th><th>Descriptions</th></tr></thead><tbody><tr><td><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/CredentialFilter.Builder.html#status(com.inrupt.client.accessgrant.CredentialFilter.CredentialStatus)">.status</a></td><td><p>Optional. Include a credential status in the query object.</p><p>The following values are supported for access grants:</p><ul><li><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/CredentialFilter.CredentialStatus.html#ACTIVE">ACTIVE</a> when used with <a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrant.html">AccessGrant</a> queries, this returns all active access grants: those that have not expired and have not been revoked.</li><li><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/CredentialFilter.CredentialStatus.html#EXPIRED">EXPIRED</a> when used with <a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrant.html">AccessGrant</a> queries, this returns all access grants that have expired.</li><li><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/CredentialFilter.CredentialStatus.html#REVOKED">REVOKED</a> when used with <a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrant.html">AccessGrant</a> queries, this returns all access grants that have been revoked by the resource owner.</li></ul></td></tr><tr><td><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/CredentialFilter.Builder.html#fromAgent(java.net.URI)">.fromAgent</a></td><td>Optional. Include the creator of the Access Grant in the query filter. This is the resource owner.</td></tr><tr><td><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/CredentialFilter.Builder.html#toAgent(java.net.URI)">.toAgent</a></td><td>Optional. Include the recipient of the Access Grant in the query filter. This is the agent that is granted access. In the example, the value is the ExamplePrinter’s WebID <code>https://id.example.com/examplePrinter</code>.</td></tr><tr><td><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/CredentialFilter.Builder.html#resource(java.net.URI)">.resource</a></td><td><p>Optional. Include the resource in the query object.</p><p>Use this filter to return access grants bound to a specific resource.</p></td></tr><tr><td><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/CredentialFilter.Builder.html#purpose(java.net.URI)">.purpose</a></td><td><p>Optional. Include a purpose in the query object.</p><p>Use this filter to return access grants bound to a specific purpose.</p></td></tr><tr><td><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/CredentialFilter.Builder.html#issuedWithin(com.inrupt.client.accessgrant.CredentialFilter.CredentialDuration)">.issuedWithin</a></td><td><p>Optional. Include a time constraint on the issuance date in the query object. All matched credentials will have been issued within the provided duration value.</p><p>Certain time constraints are available for use with this method, including:</p><ul><li><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/CredentialFilter.CredentialDuration.html#P1D">P1D</a> One day</li><li><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/CredentialFilter.CredentialDuration.html#P7D">P7D</a> Seven days</li><li><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/CredentialFilter.CredentialDuration.html#P1M">P1M</a> One month</li><li><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/CredentialFilter.CredentialDuration.html#P3M">P3M</a> Three months</li></ul></td></tr><tr><td><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/CredentialFilter.Builder.html#revokedWithin(com.inrupt.client.accessgrant.CredentialFilter.CredentialDuration)">.revokedWithin</a></td><td><p>Optional. Include a time constraint on the revocation date in the query object. All matched credentials will have been revoked within the provided duration value.</p><p>Certain time constraints are available for use with this method, including:</p><ul><li><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/CredentialFilter.CredentialDuration.html#P1D">P1D</a> One day</li><li><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/CredentialFilter.CredentialDuration.html#P7D">P7D</a> Seven days</li><li><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/CredentialFilter.CredentialDuration.html#P1M">P1M</a> One month</li><li><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/CredentialFilter.CredentialDuration.html#P3M">P3M</a> Three months</li></ul></td></tr><tr><td><a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/CredentialFilter.Builder.html#build(java.lang.Class)">.build</a></td><td><p>Builds the <a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/CredentialFilter.html">CredentialFilter</a> object.</p><p>To build a query object for access grants (i.e., <code>CredentialFilter&#x3C;AccessGrant></code>), specify the class <code>AccessGrant.class</code> to the <a href="https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/CredentialFilter.Builder.html#build(java.lang.Class)">.build</a> method.</p></td></tr></tbody></table>

The following example queries for active access grants, given to ExamplePrinter, that provide access for a specific resource for the purpose of photo printing. Specifically,

1. The example uses the [CredentialFilter.Builder](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/CredentialFilter.Builder.html) and its [methods](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/CredentialFilter.Builder.html#method.summary) to:
   * Specify the resource `.resource(...)`, the purpose `.purpose(...)`, and the status `.status(CredentialStatus.ACTIVE)`, and
   * Build `.build(AccessGrant.class)` a `CredentialFilter<AccessGrant>`.
2. Calls [AccessGrantClient.query](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantClient.html#query\(com.inrupt.client.accessgrant.AccessCredentialQuery\)) with the built `CredentialFilter<AccessGrant>` object.

   ```java
   CredentialFilter<AccessGrant> accessGrantFilter = CredentialFilter
       .newBuilder()
       .status(CredentialFilter.CredentialStatus.ACTIVE)
       .resource(URI.create("https://storage.example.com/some/resource"))
       .purpose(URI.create("https://purpose.example.com/PhotoPrinting"))
       .build(AccessGrant.class);

   CredentialResult<AccessGrant> result = accessgrantClient
       .query(accessGrantFilter)
       .toCompletableFuture().join();
   ```
3. Navigate through the response [CredentialResult](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/CredentialResult.html) object.

```java
List<AccessGrant> items = result.getItems();

if (result.nextPage().isPresent()) {
    CredentialResult<AccessGrant> page2 = agClientForUser
        .query(result.nextPage().get())
        .toCompletableFuture().join();
}
```

From the list, the agent can select the Access Grant to use.
{% endtab %}
{% endtabs %}

#### 2. Create an AccessGrantSession

To instantiate an [AccessGrantSession](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantSession.html), call the constructor with the following parmeters:

* OpenID-based session
* Access Grants to Use

For example, the following code instantiates an [AccessGrantSession](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantSession.html) using both the ExamplePrinter’s OpenID Session and the Access Grant:

```java
Session agSession = AccessGrantSession.ofAccessGrant(session, accessGrant);
```

#### 3. Create a Solid Client

To access the resource with access grants, create a [SolidSyncClient](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidSyncClient.html) or [SolidClient](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidClient.html) using the [AccessGrantSession](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/accessgrant/AccessGrantSession.html):

```java
SolidSyncClient client = SolidSyncClient.getClient().session(agSession);
```

#### 4. Access the Resource

Use the [SolidSyncClient.send()](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidSyncClient.html#send\(com.inrupt.client.Request,com.inrupt.client.Response.BodyHandler\)) method to access the non-RDF resources and [SolidSyncClient.read()](https://api.docs.inrupt.com/docs/developer-tools/api/java/inrupt-client/latest/com/inrupt/client/solid/SolidSyncClient.html#read\(java.net.URI,java.lang.Class\)) method to read RDF resources.

For example, the following code uses the client associated with ExamplePrinter and the access grants to read the image file (a Non-RDF resource) at the resource URL:

```java
SolidNonRDFSource resource = client.read(URI.create("https://storage.example.com/some/resource"), SolidNonRDFSource.class).toCompletableFuture().join();
```

{% hint style="info" %}
If you receive an `HTTP 403 Forbidden` error, check that you have [enabled the use of access grants for the resource](/sdk/java-sdk/access-requests-and-grants#enable-use-of-access-grants).
{% endhint %}


# Release Notes

| Library                     | Release Notes                                                         |
| --------------------------- | --------------------------------------------------------------------- |
| `@inrupt/solid-client-java` | [Release Notes](https://github.com/inrupt/solid-client-java/releases) |
| `@inrupt/rdf-wrapping-java` | [Release Notes](https://github.com/inrupt/rdf-wrapping-java/releases) |


# Security Checklist

The following provides some general guidelines with respect to securing your ESS deployment. The checklist is not meant to be an exhaustive list.

### Limit Network Exposure

| Limit external access to specific networks/ports.                                                                                                                               |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Separate internal and external traffic. For example, by running inside a VPC, you can ensure that all communication within the VPC is securely separated from external traffic. |
| <p>If setting up a VPN endpoint, avoid manually adding public<br>Internet routes/authorizations to the VPN endpoint .</p>                                                       |

### Use Encryption

| <p>Use TLS for network encryption.</p><ul><li>Encrypt in-transit inbound traffic to ESS.</li><li>For external facing services, use TLS certificates from an official Certificate Authority (CA). Do not use self-signed certificates.</li></ul> |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Encrypt data at rest, including audit logs.                                                                                                                                                                                                     |

See [Encryption](/security/encryption)

### Manage and Safeguard Sensitive Data/Credentials

| Many strategies for safeguarding sensitive data/credentials exist for Kubernetes. Investigate the best available options for your environment.                                       |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Secure highly-sensitive (i.e., passwords, tokens, etc.) environment variables. Do not set these environment variables on the containers as they are stored and passed in plain text. |
| Take care about what and to whom you grant access.                                                                                                                                   |


# Authentication

An authentication system determines the identity of a user or agent and the level of trust associated with this identity.

For authentication, ESS supports [OpenID Connect](https://openid.net/connect/) (OIDC) standards, which build on the [OAuth 2.0](https://datatracker.ietf.org/doc/html/rfc6749) authorization framework.

* [OAuth 2.0](https://datatracker.ietf.org/doc/html/rfc6749) defines a framework for authorization, in which a client obtains an access token to obtain access to resources.
* [OpenID Connect](https://openid.net/connect/) defines a standard mechanism by which a web application leads a user through a login flow. The login flow results in a signed ID token, which is a [JSON Web Token](https://datatracker.ietf.org/doc/html/rfc7519) (JWT) that asserts the identity of the user.

## Identity Provider Integration

ESS integrates with your existing OIDC-compliant Identity Provider. There is no proprietary identity broker or intermediary required — ESS establishes a trust relationship directly with your IdP, allowing you to use the identity infrastructure you already have.

Supported Identity Providers include any OIDC-compliant provider, such as:

* **Keycloak**
* **Amazon Cognito**
* **Microsoft Azure AD**
* **Okta**
* **Ping Identity**
* **ESS Solid OIDC Broker** (see [Advanced Configuration: Solid OIDC Broker](#advanced-configuration-solid-oidc-broker))

### Trust Relationship

To integrate an IdP with ESS, configure the IdP's issuer URL as a trusted issuer in the [Platform Management Service](https://docs.inrupt.com/ess/latest/services/service-platform-management/token-exchange). ESS validates tokens from trusted issuers using standard OIDC discovery — it fetches the IdP's `.well-known/openid-configuration` to obtain signing keys and verify token signatures.

### Authentication Flow

```
Client → External IdP → Platform Management Service (token exchange) → ESS Access Token
```

1. The client authenticates with the external Identity Provider using standard OIDC flows.
2. The IdP issues an ID token to the client.
3. The client exchanges the ID token for an ESS Access Token via the [Platform Management Service's token exchange endpoint](https://docs.inrupt.com/ess/latest/services/service-platform-management/token-exchange).
4. The client uses the ESS Access Token for all subsequent requests to ESS services.

### ESS Access Token

The ESS Access Token is a signed [JSON Web Token](https://datatracker.ietf.org/doc/html/rfc7519) (JWT) issued by the Platform Management Service. It is the credential used to access all ESS services.

* **Format**: JWT
* **Default TTL**: 5 minutes
* **Usage**: Include as a `Bearer` token in the `Authorization` header of requests to ESS services

ESS verifies the token signature and that the token has not expired. An invalid or expired token cannot be used to access resources.

For details on obtaining an ESS Access Token, see [Token Exchange](https://docs.inrupt.com/ess/latest/services/service-platform-management/token-exchange).

### Client IDs in Allow Lists and Access Policies

ESS supports the use of Client IDs in client allow list configurations and access policies to restrict which clients can be used. These restrictions are enforced at the Platform Management Service level.

For details, see [Authorization and Clients](/security/authorization#authorization-and-clients).

## Advanced Configuration: Solid OIDC Broker

When ESS must interoperate with other [Solid](https://solidproject.org/) servers or issue Solid-OIDC-compliant tokens, ESS includes a [Solid OIDC Broker Service](https://docs.inrupt.com/ess/latest/services/service-oidc/). In this configuration, the Broker is the Identity Provider that ESS trusts, and it implements the [Solid-OIDC](https://solid.github.io/solid-oidc/) specification.

{% hint style="info" %}
The Solid OIDC Broker is an advanced configuration, needed only when ESS must interoperate with other Solid servers or issue Solid-OIDC-compliant tokens. Standard enterprise deployments use the [Identity Provider integration](#identity-provider-integration) described above.
{% endhint %}

### WebID

In the Solid ecosystem, users are identified by a [WebID](/reference/glossary#webid). A WebID is a URL (e.g., **`https://id.<ESS Domain>/user1234`**) that can be dereferenced to an RDF profile document.

ESS includes a [WebID Service](https://docs.inrupt.com/ess/latest/services/service-webid/). WebIDs issued by ESS have the form:

```none
https://id.<ESS DOMAIN>/<username>
```

### Client Identifier (Client ID)

In [Solid-OIDC](https://solid.github.io/solid-oidc/), an application identifies itself using a [client identifier (Client ID)](/reference/glossary#client-id).

A Client ID can be:

* a URL that dereferences to a [Client ID Document](https://solid.github.io/solid-oidc/#clientids-document).
* a value that has been registered using either [OIDC dynamic or static registration](https://solid.github.io/solid-oidc/#clientids-oidc).

#### **Solid-OIDC Client ID Document**

ESS supports [Client Identifiers (Client IDs)](https://solid.github.io/solid-oidc/#clientids) that are of type URL and dereference to a JSON-LD document, the [Client ID Document](https://solid.github.io/solid-oidc/#clientids-document).

#### **Client Registration**

For applications that do not use identifiers that dereference to a Client ID Document, they can [register](https://datatracker.ietf.org/doc/html/rfc7591.html) with ESS' [Solid OIDC Broker service (the Broker)](https://docs.inrupt.com/ess/latest/services/service-oidc/).

To register, a client provides various [metadata about itself](https://datatracker.ietf.org/doc/html/rfc7591#section-2) as part of its registration request (see [RFC7591: 3.1 Client Registration Request](https://www.rfc-editor.org/rfc/rfc7591#section-3.1)).

Upon successful registration, the Broker responds with a unique **`client_id`**. The response may include additional fields. For details, see [RFC7591: 3.2 Client Registration Responses](https://www.rfc-editor.org/rfc/rfc7591#section-3.2).

#### **Dynamic Registration**

To dynamically register an application, an application POSTs to [the Broker's](https://docs.inrupt.com/ess/latest/services/service-oidc/) client **`registration_endpoint`** with the client's metadata.

{% hint style="info" %}
Tip\
To determine if the Broker supports dynamic client registration, check its **`/.well-known/openid-configuration`** for the **`registration_endpoint`** field.
{% endhint %}

Inrupt's JavaScript client libraries provide **`login`** APIs that handle dynamic registration of applications.

#### **Static Registration**

ESS supports [static registration of client applications](https://docs.inrupt.com/ess/latest/services/service-oidc/service-application-registration) associated with a user (i.e., WebID). Static registration results in client credentials (i.e., **`client_id`** and **`client_secret`**). ESS' application registration returns **`client_id`** of type UUID.

Single-user scripts and bots can use these client credentials to authenticate (on behalf of the user) without requiring browser-based user interactions with the Identity Provider.

For details, see [Application Registration](https://docs.inrupt.com/ess/latest/services/service-oidc/service-application-registration).

### Broker Tokens

As part of the Solid-OIDC login flow, ESS' [Solid OIDC Broker Service](https://docs.inrupt.com/ess/latest/services/service-oidc/) issues ID tokens and access tokens. The Broker includes the WebID and the Client ID as claims in these tokens.

#### **ID Tokens**

An ID token asserts the identity of the user and is represented as a [JSON Web Token](https://datatracker.ietf.org/doc/html/rfc7519) (JWT).

The [OpenID specification](https://openid.net/specs/openid-connect-core-1_0.html#IDToken) defines an extensible data structure for ID Tokens. This data structure is serialized as a [JSON Web Token](https://datatracker.ietf.org/doc/html/rfc7519).

See also [Broker Token Claims](https://docs.inrupt.com/ess/latest/services/service-oidc/#openidp-claims).

ESS ID tokens have a default lifespan of 5 minutes (see [**`SMALLRYE_JWT_NEW_TOKEN_LIFESPAN`**](https://docs.inrupt.com/ess/latest/services/service-oidc#smallrye_jwt_new_token_lifespan)).

#### **Signed Access Tokens**

The Broker issues signed access tokens that provide access to resources. Access tokens are represented as [JSON Web Tokens](https://datatracker.ietf.org/doc/html/rfc7519) (JWT).

ESS verifies the token signature and that the token has not expired. An invalid token cannot be used to gain access to resources.

See also [Broker Token Claims](https://docs.inrupt.com/ess/latest/services/service-oidc/#openidp-claims).

ESS access tokens have a default lifespan of 5 minutes (see [**`SMALLRYE_JWT_NEW_TOKEN_LIFESPAN`**](https://docs.inrupt.com/ess/latest/services/service-oidc#smallrye_jwt_new_token_lifespan)).

#### **Demonstration of Proof-of-Possession (DPoP) Token**

As an additional layer of protection against token stealing and various replay attacks, Solid clients can send an additional HTTP header (specifically a [DPoP proof](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-dpop-00)).

A [DPoP proof](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-dpop-00) can be used to verify that a client is in legitimate possession of an access token while also scoping the request to a particular Pod resource. This helps prevent against token exfiltration attacks.

ESS uses version 00 of DPoP.


# Authorization/Access Control

An authorization system determines whether an agent has access to perform a given action on a particular resource.

### ACP

ESS uses [Access Control Policy (ACP)](/security/authorization/acp) to define the policies that determine access to Pod’s resources.

If

*< allOf | anyOf > (*[*Matchers*](/security/authorization/acp#matcher-statements)*) evaluates to true, **AND***

*< allOf | anyOf | noneOf > (*[*Matchers*](/security/authorization/acp#matcher-statements)*) evaluates to true, **AND***

***...***

***Then***

*<**allow** (*[*AccessModes*](/security/authorization/acp#access-modes)*) | <mark style="color:red;">**deny**</mark> (*[*AccessModes*](/security/authorization/acp#access-modes)*) | **allow** (*[*AccessModes*](/security/authorization/acp#access-modes)*) **AND*** *<mark style="color:red;">**deny**</mark> (*[*AccessModes*](/security/authorization/acp#access-modes)*) >*

For more information, see [Access Control Policy (ACP)](/security/authorization/acp)

### Access Control Mechanisms

ESS supports:

* [Identity-Based Access](/security/authorization/identity-based-access-policies) , where access to Pod resources is based on agents’ identity, and optionally, the identity of their clients.\
  To use identity based access, the resource must have ACPs that specify the Agents’ WebIDs (and, optionally, Client IDs).
* [Access Grants](/security/authorization/access-requests-grants), where access to Pod resources can be requested and granted. Access Grants work independently — once a resource owner approves an Access Grant, the recipient can access the resource directly.

### Authorization Services

To support authorization, ESS provides the following services:

* [Authorization Service](https://docs.inrupt.com/ess/latest/services/service-authorization/)
* [Access Grant Service](https://docs.inrupt.com/ess/latest/services/service-access-grant/)

### Authorization and Clients

ESS supports the the use of [Client IDs](/reference/glossary#client-identifier) in client allow lists and access policies.

#### Client Allow Lists

Operators can use Client IDs in the following allow lists:

* [`INRUPT_AUTHORIZATION_DEFAULT_ACR_CLIENT_ID_ALLOW_LIST`](https://docs.inrupt.com/ess/latest/services/service-authorization/#cmdoption-authzconfig-arg-INRUPT_AUTHORIZATION_DEFAULT_ACR_CLIENT_ID_ALLOW_LIST)
  * Specifies the [Client Matcher](https://docs.inrupt.com/guides/access-control-policies#matcher-statements) statements for a new Pod’s [initial access policies](https://docs.inrupt.com/ess/latest/services/service-pod-management/service-pod-provision) . To configure this option, see [Set Initial Pod Clients Allow List](https://docs.inrupt.com/ess/latest/installation/customize-configurations/customization-security/modify-pod-client-list) for an example.
* [`INRUPT_AUTHORIZATION_CLIENT_ID_ALLOW_LIST`](https://docs.inrupt.com/ess/latest/services/service-authorization/#cmdoption-authzconfig-arg-INRUPT_AUTHORIZATION_CLIENT_ID_ALLOW_LIST) .
  * Determines which applications can modify the Access Control Resource (i.e., which applications can modify the Access Control Policies for Pod resources). To configure this option, see [Set Authorization Client Allow List](https://docs.inrupt.com/ess/latest/installation/customize-configurations/customization-security/modify-authz-client-list) for an example.
  * May also be used to initialize a new Pod’s [access policies’ client matcher statements](https://docs.inrupt.com/security/authorization#client-matchers) if [`INRUPT_AUTHORIZATION_DEFAULT_ACR_CLIENT_ID_ALLOW_LIST`](https://docs.inrupt.com/ess/latest/services/service-authorization/#cmdoption-authzconfig-arg-INRUPT_AUTHORIZATION_DEFAULT_ACR_CLIENT_ID_ALLOW_LIST) is unset.
* [`INRUPT_VC_CLIENT_ID_ALLOW_LIST_SOLIDACCESSREQUEST`](https://docs.inrupt.com/ess/latest/services/service-access-grant/#cmdoption-agconfig-arg-INRUPT_VC_CLIENT_ID_ALLOW_LIST_SOLIDACCESSREQUEST)
  * Determines which applications can access the [/issue Endpoint](https://docs.inrupt.com/ess/latest/services/service-access-grant/issue-endpoint) and the [/status Endpoint](https://docs.inrupt.com/ess/latest/services/service-access-grant/service-access-grant-status) for access requests.
* [`INRUPT_VC_CLIENT_ID_ALLOW_LIST_SOLIDACCESSGRANT`](https://docs.inrupt.com/ess/latest/services/service-access-grant/#cmdoption-agconfig-arg-INRUPT_VC_CLIENT_ID_ALLOW_LIST_SOLIDACCESSGRANT)
  * Determines which applications can access the [/issue Endpoint](https://docs.inrupt.com/ess/latest/services/service-access-grant/issue-endpoint) and the [/status Endpoint](https://docs.inrupt.com/ess/latest/services/service-access-grant/service-access-grant-status) for access grants.

#### Client Matchers

Client IDs can be used in [Client Matcher](https://docs.inrupt.com/security/authorization#client-matchers) statements in [Access Control Policy (ACP)](/security/authorization/acp) policies.

For example, if [client allow list configuration for the initial policy](#authz-client-allow-list) is set, ESS creates [default ACP policies](https://docs.inrupt.com/ess/latest/services/service-pod-management/service-pod-provision#initial-acp-policies) of the form:

`If allOf(AgentMatcher and ClientMatcher) evaluates to true, Then allow (Read and Write).`

### Note

* ESS does not support the use of dynamically registered Client ID values in client allow lists and access policies.
* Inrupt does not provide support for ESS servers running [Web Access Control (WAC)](https://solid.github.io/web-access-control-spec/) in Production.


# Access Control Policy (ACP)

ESS uses [Access Control Policy (ACP)](/reference/glossary#access-control-policies) to manage access to Pod resources. With ACP, Pod owners can define Policies that determine access for their Pod’s resources.

### Policies

Policies determine access for Pod resources. A policy consists of:

* Matcher statements that specify conditions that must be satisfied for the Policy to take effect.
* Access mode statements that specify which access modes are allowed and/or denied to the agent(s) satisfying the Matcher statements.

{% code overflow="wrap" %}

```
If
< allOf | anyOf > ([Matcher(s)](acp.md#acp-matcher)) evaluates to true, AND
< allOf | anyOf | noneOf > ([Matcher(s)](acp.md#acp-matcher)) evaluates to true, AND
…

Then< allow ( [AccessMode(s)](acp.md#acp-access-modes) ) | deny ( [AccessMode(s)](acp.md#acp-access-modes) ) | allow ( [AccessMode(s)](acp.md#acp-access-modes) ) AND deny ( [AccessMode(s)](acp.md#acp-access-modes) ) >

```

{% endcode %}

{% hint style="warning" %}
Important

The **`noneOf()`** expression excludes matches from the **`allOf`** and **`anyOf`** expressions; i.e., you can use the **`noneOf`** expression to refine the **`allOf`** and **`anyOf`** matches.

Because the **`noneOf()`** expression acts as a secondary/supplementary filter to the **`allOf`** and **`anyOf`** expressions, a policy statement with only a **`noneOf(<matchers>)`** condition cannot be satisfied.
{% endhint %}

### Matcher Statements

{% code overflow="wrap" %}

```
< allOf | anyOf > ([Matcher(s)](acp.md#acp-matcher)) evaluate to true, AND
< allOf | anyOf | noneOf > ([Matcher(s)](acp.md#acp-matcher)) evaluates to true, AND
…
```

{% endcode %}

#### Matchers

Matchers specify the conditions under which the Access Policy applies.

ESS supports matching:

<table><thead><tr><th width="114.37109375"></th><th></th></tr></thead><tbody><tr><td>Agents</td><td><ul><li>To match agents by specific <a href="https://docs.inrupt.com/reference/glossary#webid">WebID(s)</a>.</li><li>To match any authenticated agent.</li><li>To match any agent.</li></ul></td></tr><tr><td>Clients</td><td><ul><li>To match by specific <a href="/pages/vzCNW5vU72LdQziKR9gW#client-identifier-client-id">Client ID(s)</a>.</li><li>To match any client application.</li></ul><p>See also <a href="https://docs.inrupt.com/security/authentication#client-identifier-client-id">Authorization and Clients</a></p><div data-gb-custom-block data-tag="hint" data-style="info" class="hint hint-info"><p>Note<br>To use Client Matchers, the Policy must also specify an Agent Matcher.</p></div></td></tr><tr><td>Verifiable Credentials</td><td><ul><li>To match by <a href="https://docs.inrupt.com/reference/glossary#verifiable-credential">Verifiable Credential(s)</a> type; e.g., match VC type<strong><code>http://www.w3.org/ns/solid/vc#SolidAccessGrant</code></strong>.</li></ul><div data-gb-custom-block data-tag="hint" data-style="info" class="hint hint-info"><p><strong>Tip</strong></p><p>To enable the use of ESS issued <a href="https://docs.inrupt.com/ess/latest/services/service-access-grant/access-grant-vc-jsonld-context">access grants (which are serialized as VCs)</a> , a policy with a VC Matcher is required. For details, see <a href="/pages/nzZgNhwDek5fJNZxuYts">access grants</a> .</p></div></td></tr></tbody></table>

#### allOf, anyOf, noneOf Operators

A policy specifies its matchers in **`allOf()`** , **`anyOf()`** , and **`noneOf()`** operator expressions.

| **`allOf(<matchers>)`**  | Evaluates to true if all of its listed matchers evaluate to true.  |
| ------------------------ | ------------------------------------------------------------------ |
| **`anyOf(<matchers>)`**  | Evaluates to true if any of its listed matchers evaluate to true.  |
| **`noneOf(<matchers>)`** | Evaluates to true if none of its listed matchers evaluate to true. |

{% hint style="warning" %}
Important\
The **`noneOf()`** expression excludes matches from the **`allOf`** and **`anyOf`** expressions; i.e., you can use the **`noneOf`** expression to refine the **`allOf`** and **`anyOf`** matches.

Because the **`noneOf()`** expression acts as a secondary/supplementary filter to the **`allOf`** and **`anyOf`** expressions, a policy statement with only a **`noneOf(<matchers>)`** condition cannot be satisfied.
{% endhint %}

### Access Mode Statements

{% code overflow="wrap" %}

```
< allow ( [AccessMode(s)](acp.md#acp-access-modes) ) | deny ( [AccessMode(s)](acp.md#acp-access-modes) ) |allow ( [AccessMode(s)](acp.md#acp-access-modes) ) AND deny ( [AccessMode(s)](acp.md#acp-access-modes) ) >
```

{% endcode %}

#### Access Modes

Access Modes describe the permissions that can be granted or denied. The available modes are:

| Access Mode  | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`Read`**   | <p>Permission to view/retrieve a resource as well as to subscribe to <a href="https://docs.inrupt.com/ess/latest/services/service-notification/service-websocket">notifications</a> for the resource.<br>See also <a href="#crud-access-modes">CRUD Operations and Access Modes</a>.</p>                                                                                                                                                                                                                                                                                                                                            |
| **`Write`**  | <p>Permission to create a resource, update the content of a resource, and delete a resource.<br><br>Tip:<br>\* To create a resource, you must have <strong><code>Write</code></strong> access on both the resource and the resource’s container.<br>\* To delete a resource, you must have <strong><code>Write</code></strong> access on both the resource and the resource’s container.<br>See also <a href="#crud-access-modes">CRUD Operations and Access Modes</a>.</p>                                                                                                                                                         |
| **`Append`** | <p>Permission to add content to a resource.<br>If a resource is a <a href="/pages/7PKZaxnnUDGdJ8gPDOqL#container">container</a> (analogous to a folder in a file system), the <strong><code>Append</code></strong> permission on the resource allows agents to add new resources (container, RDF resource, non-RDF resource) to the container.<br>If a resource is an <a href="/pages/Ejga65cCzJE3HowMncgm">RDF resource</a>, the <strong><code>Append</code></strong> permission on a resource allows agents to add statements to the resource.<br>See also <a href="#crud-access-modes">CRUD Operations and Access Modes</a>.</p> |

#### allow, deny Expressions

A policy statement specifies its access modes in `allow(Access Modes)` or `deny(Access Modes)` expressions:

* The **`allow`** expression specifies the access modes to be granted.
* The **`deny`** expression specifies the access modes to be denied.

An agent is granted an access mode for a resource if:

* The agent satisfies a policy that allows the access mode for the resource, **and**
* The agent does not satisfy any policy that denies that access mode for the resource.

For example:

* If a resource only has a single policy that allows **`Read`** and **`Write`** for an agent, the agent is granted **`Read`** and **`Write`** for the resource.
* If a resource has:
  * A policy that allows **`Read`** and **`Write`** for an agent, and
  * A policy that denies **`Write`** for the same agent,

    Then, the agent is granted **`Read`** access for the resource.

If **no** “allow access” policy is satisfied for a resource, then that resource is inaccessible to the agent. That is, an unsatisfied “deny access” policy does not confer access. For example,

* If a resource has defined only a single policy that denies **`Read`** and the policy is unsatisfied by an agent, that agent still does not have any access to that resource.

#### CRUD Operations and Access Modes

This section summarizes the relationship between Create/Read/Update/Delete (CRUD) operations and the required access modes.

{% tabs %}
{% tab title="Create" %}
To create a resource, the user requires either an **`Append`** or **`Write`** access.

{% hint style="info" %}
Note The creation operation creates the resource (be it container, RDF resource, non-RDF resource) and updates the content of the **parent** container with the new resource’s metadata.
{% endhint %}

| Resource                                                                                | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| --------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Container](/reference/glossary#container)                                              | Either **`Append`** or **`Write`** access on the **parent** container (under which the new container is to be created) allows agents to create a new container. For example, to create `https://storage..../parentcontainer/newContainer/`, either an **`Append`** or a **`Write`** access on `https://storage..../parentcontainer/` allows agents to create `https://storage..../parentcontainer/newContainer/`.                                                      |
| [RDF resource](/reference/rdf)                                                          | Either **`Append`** or **`Write`** access on the **parent** container (under which the new resource is to be created) allows agents to create a RDF resource. For example, to create `https://storage..../parentcontainer/newResource/`, either an **`Append`** or a **`Write`** access on `https://storage..../parentcontainer/` allows agents to create `https://storage..../parentcontainer/newResource`.                                                           |
| [Non-RDF resource](https://docs.inrupt.com/sdk/java-sdk/crud-rdf-data#non-rdf-resource) | Either **`Append`** or **`Write`** access on the **parent** container (under which the new resource is to be created) allows agents to create a new [non-RDF resource](https://docs.inrupt.com/sdk/java-sdk/crud-rdf-data#non-rdf-resource). For example, to create `https://storage..../parentcontainer/foo.jpg`, **`Append`** or **`Write`** access on `https://storage..../parentcontainer/` allows agents to create `https://storage..../parentcontainer/foo.jpg`. |
| {% endtab %}                                                                            |                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |

{% tab title="Read" %}
For read operations , the user requires `Read` access.

| Resource                                                                                | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| --------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Container](/reference/glossary#container)                                              | **`Read`** access on the target container (analogous to a folder in a file system) allows agents to read/retrieve the container as a resource (not the resource(s) under the container). That is, Read access only affects read operation on the container itself. Reading a container (which stores metadata about the resources contained within the container) allows a client to discover what resources are contained inside the container and their resource type (i.e., analogous to an **`ls`** on a folder in a file system). |
| [RDF resource](/reference/rdf)                                                          | **`Read`** access on the RDF resource allows agents to read/retrieve the resource (regardless of the access on the parent container). For example, if a resource has as its URL `https://storage..../container/ResourceToRead`, to read/retrieve this resource: The user must have **`Read`** access on `https://storage..../container/ResourceToRead`. The user’s access on `https://storage..../container/` is immaterial.                                                                                                           |
| [Non-RDF resource](https://docs.inrupt.com/sdk/java-sdk/crud-rdf-data#non-rdf-resource) | **`Read`** access on a non-RDF resource allows agents to read/retrieve the resource (regardless of Read access on the container). For example, if a non-RDF resource has as its URL `https://storage..../container/foo.jpg`, to read/retrieve this resource: The user must have **`Read`** access on `https://storage..../container/foo.jpg`. The user’s access on `https://storage..../container/` is immaterial.                                                                                                                     |
| {% endtab %}                                                                            |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |

{% tab title="Update" %}
For update operations, the user requires **`Append`** or **`Write`** access, depending on the specific update operation.

| Resource                                                                                | Description                                                                                                                                                                                                                                                                                                                                                                                                                               |
| --------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Container](/reference/glossary#container)                                              | To add resources to the Container, see the Create tab. To delete resources from the Container, see the Delete tab.                                                                                                                                                                                                                                                                                                                        |
| [RDF resource](/reference/rdf)                                                          | Either **`Append`** or **`Write`** access on an RDF resource allows agents to add content (statements) to the resource. **`Write`** access on an RDF resource allows agents to delete content (statements) from the resource. **`Write`** access on an RDF resource allows agents to modify existing content (statements) in the resource.                                                                                                |
| [Non-RDF resource](https://docs.inrupt.com/sdk/java-sdk/crud-rdf-data#non-rdf-resource) | **`Write`** access on the target resource allows agents to overwrite/replace the resource (regardless of the access on the parent container). For example, if the non-RDF resource has as its URL **`https://storage..../container/foo.jpg`**, to overwrite this resource: The user must have **`Write`** access on **`https://storage..../container/foo.jpg`**. The user’s access on **`https://storage..../container/`** is immaterial. |
| {% endtab %}                                                                            |                                                                                                                                                                                                                                                                                                                                                                                                                                           |

{% tab title="Delete" %}
For delete operations, the user requires `Write` access.

| Resource                                                         | Description                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| ---------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Container](/reference/glossary#container)                       | **`Write`** access on both the parent container and the target container allows agents to delete the target container. For example, to delete `https://storage..../parentcontainer/containerToDelete/`, **`Write`** access on both `https://storage..../parentcontainer/` and `https://storage..../parentcontainer/containerToDelete/` allows agents to delete `https://storage..../parentcontainer/containerToDelete/`.                     |
| [RDF resource](/reference/rdf)                                   | To delete an RDF resource, **`Write`** access on both the parent container and the target resource allows agents to delete the target resource. For example, to delete `https://storage..../parentcontainer/resourceToDelete/`, **`Write`** access on both `https://storage..../parentcontainer/` and `https://storage..../parentcontainer/resourceToDelete` allows agents to delete `https://storage..../parentcontainer/resourceToDelete`. |
| [Non-RDF resource](/sdk/java-sdk/crud-rdf-data#non-rdf-resource) | To delete a non-RDF resource, **`Write`** access on both the parent container and the target resource allows agents to delete the target resource. For example, to delete `https://storage..../parentcontainer/foo.jpg`, **`Write`** access on both `https://storage..../parentcontainer/` and `https://storage..../parentcontainer/foo.jpg` allows agents to delete `https://storage..../parentcontainer/foo.jpg`.                          |
| {% endtab %}                                                     |                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| {% endtabs %}                                                    |                                                                                                                                                                                                                                                                                                                                                                                                                                              |

### Access Control Resource

Each Pod resource has an associated Access Control Resource (ACR) that contains the policies that determine access to the Pod resource.

<figure><img src="/files/PbUOCrnXnsrBbRUKzcZf" alt=""><figcaption></figcaption></figure>

The lifecycle of the ACR is bound to the lifecycle of the Pod resource; that is:

* When creating a resource, ESS creates a corresponding ACR.
* When deleting a resource, ESS deletes the corresponding ACR.

If a resource has no Policies that apply to it, the resource is inaccessible. However, the Pod owner can add new policies to provide access to the resource.

### Member Policies

If a resource is a Container, you can also specify Member Policies in the Container’s ACR. Member Policies will be inherited by the Container’s children/descendants.

### Access to ACRs

ESS’ [Authorization Service](https://docs.inrupt.com/ess/latest/services/service-authorization/) hosts the ACRs. The [Authorization Service](https://docs.inrupt.com/ess/latest/services/service-authorization/) ‘s [`INRUPT_AUTHORIZATION_CLIENT_ID_ALLOW_LIST`](https://docs.inrupt.com/ess/latest/services/service-authorization/#cmdoption-authzconfig-arg-INRUPT_AUTHORIZATION_CLIENT_ID_ALLOW_LIST) determines which clients can write policies to ACRs.

{% hint style="info" %}
Note\
Having read/write/append access to policies for a resource (i.e., write to the resource’s ACR) is distinct from having access to read/write/append the resource itself.
{% endhint %}

In version 2.0, ESS also uses the values in [`INRUPT_AUTHORIZATION_CLIENT_ID_ALLOW_LIST`](https://docs.inrupt.com/ess/latest/services/service-authorization/#cmdoption-authzconfig-arg-INRUPT_AUTHORIZATION_CLIENT_ID_ALLOW_LIST) as part of the initial ACP policies that determine the read/write/append access to the Pod and its resource.

Starting in 2.1, ESS uses the values in [`INRUPT_AUTHORIZATION_DEFAULT_ACR_CLIENT_ID_ALLOW_LIST`](https://docs.inrupt.com/ess/latest/services/service-authorization/#cmdoption-authzconfig-arg-INRUPT_AUTHORIZATION_DEFAULT_ACR_CLIENT_ID_ALLOW_LIST) if set. If unset, ESS uses the values in [`INRUPT_AUTHORIZATION_CLIENT_ID_ALLOW_LIST`](https://docs.inrupt.com/ess/latest/services/service-authorization/#cmdoption-authzconfig-arg-INRUPT_AUTHORIZATION_CLIENT_ID_ALLOW_LIST) (same as it did in version 2.0).

For details, see [Initial ACP Policies](#initial-policies) .

### Initial ACP Policies

When a Pod is created, like any other Pod resource, an [Access Control Resource](#acp-acr) is also created for the Pod Root. The ACR is initialized with the default [ACP policies](#acp-policies) for the Pod Owner:

* **Initial Pod Owner policies** give the Pod Owner read and write access to the Pod. These policies also specify a client matcher as well if the **Authorization service’s** configuration for the initial client allow list is set:
  * [`INRUPT_AUTHORIZATION_DEFAULT_ACR_CLIENT_ID_ALLOW_LIST`](https://docs.inrupt.com/ess/latest/services/service-authorization/#cmdoption-authzconfig-arg-INRUPT_AUTHORIZATION_DEFAULT_ACR_CLIENT_ID_ALLOW_LIST) or if that is unset,
  * [`INRUPT_AUTHORIZATION_CLIENT_ID_ALLOW_LIST`](https://docs.inrupt.com/ess/latest/services/service-authorization/#cmdoption-authzconfig-arg-INRUPT_AUTHORIZATION_CLIENT_ID_ALLOW_LIST) .

{% tabs %}
{% tab title="Allow List is Set" %}
{% hint style="info" %}
**Note**

Starting in 2.1, ESS uses the values in its **Authorization service’s** [`INRUPT_AUTHORIZATION_DEFAULT_ACR_CLIENT_ID_ALLOW_LIST`](https://docs.inrupt.com/ess/latest/services/service-authorization/#cmdoption-authzconfig-arg-INRUPT_AUTHORIZATION_DEFAULT_ACR_CLIENT_ID_ALLOW_LIST) (at the time of Pod creation) to create the client matcher for the initial ACP policies. If the configuration is unset, ESS uses the values in its **Authorization service’s** [`INRUPT_AUTHORIZATION_CLIENT_ID_ALLOW_LIST`](https://docs.inrupt.com/ess/latest/services/service-authorization/#cmdoption-authzconfig-arg-INRUPT_AUTHORIZATION_CLIENT_ID_ALLOW_LIST) (at the time of Pod creation).
{% endhint %}

Using the value of the Pod owner’s WebID and an initial client allow list, ESS creates the initial policies of the form:

```
If allOf(AgentMatcher and ClientMatcher) evaluates to true, Then allow (Read and Write).
```

Specifically, ESS creates:

Policy 1 for the Pod Root:If the agent matches the Pod owner’s [WebID](https://docs.inrupt.com/reference/glossary#webid) , and if the client application’s Client ID has a match in the initial client allow list, allow Read and Write access.

Policy 2 for the Pod Root’s Initial Member Policies:If the agent matches the Pod owner’s [WebID](https://docs.inrupt.com/reference/glossary#webid) , and if the client application’s Client ID has a match in the initial client allow list, allow Read and Write access.

For more information on a Container’s Member Policies, see [Member Policies](#member-policies) .
{% endtab %}

{% tab title="Allow List is Not Set" %}
{% hint style="info" %}
**Note**\
Starting in 2.1, ESS uses the values in **Authorization service’s** [`INRUPT_AUTHORIZATION_DEFAULT_ACR_CLIENT_ID_ALLOW_LIST`](https://docs.inrupt.com/ess/latest/services/service-authorization/#cmdoption-authzconfig-arg-INRUPT_AUTHORIZATION_DEFAULT_ACR_CLIENT_ID_ALLOW_LIST) (at the time of Pod creation) to create the client matcher for the initial policies. If the configuration is unset, ESS uses the values in its **Authorization service’s** [`INRUPT_AUTHORIZATION_CLIENT_ID_ALLOW_LIST`](https://docs.inrupt.com/ess/latest/services/service-authorization/#cmdoption-authzconfig-arg-INRUPT_AUTHORIZATION_CLIENT_ID_ALLOW_LIST) (at the time of Pod creation).
{% endhint %}

If the initial client allow list is empty (when creating the policy), ESS uses the value of the Pod owner’s WebID to create initial policies of the form:

```
If allOf(AgentMatcher) evaluates to true, Then allow (Read and Write).
```

Specifically, ESS creates:

Policy 1 for the Pod Root:If the agent matches the Pod owner’s [WebID](https://docs.inrupt.com/reference/glossary#webid) , allow Read and Write access.

Policy 2 for the Pod Root’s Initial Member Policies:If the agent matches the Pod owner’s [WebID](https://docs.inrupt.com/reference/glossary#webid) , allow Read and Write access.

For more information on a Container’s Member Policies, see [Member Policies](#member-policies) .
{% endtab %}
{% endtabs %}

{% hint style="danger" %}
**Disambiguation**\
Both [Authorization Service](https://docs.inrupt.com/ess/latest/services/service-authorization/) and [Pod Storage Service](https://docs.inrupt.com/ess/latest/services/service-pod-management/service-pod-storage) have a `INRUPT_AUTHORIZATION_CLIENT_ID_ALLOW_LIST` setting.

**Only** the [Authorization Service](https://docs.inrupt.com/ess/latest/services/service-authorization/) setting affects which clients are allowed. The [Pod Storage Service](https://docs.inrupt.com/ess/latest/services/service-pod-management/service-pod-storage) is for [Discovery](https://docs.inrupt.com/ess/latest/services/service-pod-management/service-pod-provision) purposes only.
{% endhint %}

{% hint style="info" %}
Note\
A Pod’s initial Policies are set when the Pod is provisioned. As such, updates to the various `INRUPT_AUTHORIZATION_DEFAULT_ACR_*` options do not affect existing Pods.

That is, once a Pod’s initial policies have been created, changes to the various `INRUPT_AUTHORIZATION_DEFAULT_ACR_*` options are not reflected in that Pod’s policies.
{% endhint %}

* ESS’ ACP is based on an earlier version of the [Access Control Policy (ACP) Specification](https://solid.github.io/authorization-panel/acp-specification/) .
* Inrupt does not provide support for ESS servers running [Web Access Control (WAC)](https://solid.github.io/web-access-control-spec/) in Production.

### Examples

#### Create Policy to Match Agents and Clients

The following example sets up an **`app-friends-policy`** that allow Read and Write access to any Agent that satisfies the **`match-app-friends`** Matcher conditions; namely, Agents whose WebID matches one of the specified WebIDs and is using an application whose Client Identifier matches the specified Client IDs. When verifying against a policy that specifies a Client Application Matcher, the user must be logged in. A Policy that specifies a Client Application Matcher but no Agent Matcher does not match any agent.

{% code fullWidth="false" %}

```javascript
import { handleIncomingRedirect, login, fetch, getDefaultSession } from '@inrupt/solid-client-authn-browser';
import { acp_ess_2, asUrl } from "@inrupt/solid-client";


// ... Various logic, including login logic, omitted for brevity.
// ...


async function setupPolicyToMatchAgentsAndClients(resourceURL) {

  const agentsToMatch = [ "https://id.example.com/chattycarl", "https://id.example.com/busybee" ];
  const clientIDsToMatch = [ "https://myapp.example.net/appid" ];

  try {
    // 1. Fetch the SolidDataset with its Access Control Resource (ACR).
    let resourceWithAcr = await acp_ess_2.getSolidDatasetWithAcr(
      resourceURL,            // Resource whose ACR to set up
      { fetch: fetch }       // fetch from the authenticated session
    );

    // 2. Initialize a new Matcher.
    let appFriendsMatcher = acp_ess_2.createResourceMatcherFor(
      resourceWithAcr,
      "match-app-friends"
    );

    // 3. For the Matcher, specify the Agent(s) to match.
    agentsToMatch.forEach(agent => {
      appFriendsMatcher = acp_ess_2.addAgent(appFriendsMatcher, agent);
    })

    // 4. For the Matcher, specify the Client ID(s) to match.
    clientIDsToMatch.forEach(clientID => {
      appFriendsMatcher = acp_ess_2.addClient(appFriendsMatcher, clientID);
    })

    // 5. Add the Matcher definition to the Resource's ACR.
    resourceWithAcr = acp_ess_2.setResourceMatcher(
      resourceWithAcr,
      appFriendsMatcher
    );

    // 6. Create a Policy for the Matcher.
    let appFriendsPolicy = acp_ess_2.createResourcePolicyFor(
      resourceWithAcr,
      "app-friends-policy",
    );

    // 7. Add the appFriendsMatcher to the Policy as an allOf() expression.
    // Since using allOf() with a single Matcher, could also use anyOf() expression

    appFriendsPolicy = acp_ess_2.addAllOfMatcherUrl(
      appFriendsPolicy,
      appFriendsMatcher
    );

    // 8. Specify the access modes (e.g., allow Read and Write).
    appFriendsPolicy = acp_ess_2.setAllowModes(appFriendsPolicy,
      { read: true, write: true }
    );

    // 9. Apply the Policy to the resource.
    resourceWithAcr = acp_ess_2.addPolicyUrl(
      resourceWithAcr,
      asUrl(appFriendsPolicy)
    );

    // 10. Add the Policy definition to the resource's ACR. 
    resourceWithAcr = acp_ess_2.setResourcePolicy(
      resourceWithAcr,
      appFriendsPolicy
    );

    // 11. Save the modified ACR for the resource.
    const updatedResourceWithAcr = await acp_ess_2.saveAcrFor(
      resourceWithAcr,
      { fetch: fetch }          // fetch from the authenticated session
    );

  } catch (error) {
    console.error(error.message);
  }
}
```

{% endcode %}

**Details**

In particular, the example uses:

1. [acp\_ess\_2.getSolidDatasetWithAcr](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#getsoliddatasetwithacr) to retrieve the SolidDataset with its ACR.

   ```javascript
   let resourceWithAcr = await acp_ess_2.getSolidDatasetWithAcr(
     resourceURL,            // Resource whose ACR to set up
     { fetch: fetch }       // fetch from the authenticated session
   );
   ```

   To specify policies for files with other structures (such as .pdf or .jpeg files), use [acp\_ess\_2.getFileWithAcr](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#getfilewithacr) instead.
2. [acp\_ess\_2.createResourceMatcherFor](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#createresourcematcherfor) to initialize the Matcher that will be used by the policy.

   ```javascript
   let appFriendsMatcher = acp_ess_2.createResourceMatcherFor(
     resourceWithAcr,
     "match-app-friends"
   );
   ```

   When saved, the Matcher URL will be `{ACR URL}#match-app-friends`.
3. [acp\_ess\_2.addAgent](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#addagent) to specify the [WebID](about:blank/reference/glossary/#term-WebID) of the agent(s) to match:

   ```javascript
   agentsToMatch.forEach(agent => {
     appFriendsMatcher = acp_ess_2.addAgent(appFriendsMatcher, agent);
   })
   ```
4. [acp\_ess\_2.addClient](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#addclient) to specify the [Client ID](about:blank/authenticate-client/#authenticate-client-identifier-document) of the application(s) to match.

   ```javascript
   clientIDsToMatch.forEach(clientID => {
     appFriendsMatcher = acp_ess_2.addClient(appFriendsMatcher, clientID);
   })
   ```
5. [acp\_ess\_2.setResourceMatcher](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#setresourcematcher) to store the new matcher definition to the ACR:

   ```javascript
   resourceWithAcr = acp_ess_2.setResourceMatcher(
     resourceWithAcr,
     appFriendsMatcher
   );
   ```
6. [acp\_ess\_2.createResourcePolicyFor](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#createresourcepolicyfor) to initialize the policy:

   ```javascript
   let appFriendsPolicy = acp_ess_2.createResourcePolicyFor(
     resourceWithAcr,
     "app-friends-policy",
   );
   ```

   When saved, the policy URL will be `{ACR URL}#app-friends-policy`.
7. [acp\_ess\_2.addAllOfMatcherUrl](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#addallofmatcherurl) to add the matcher to the policy.

   ```javascript
   // Since using allOf() with a single Matcher, could also use anyOf() expression

   appFriendsPolicy = acp_ess_2.addAllOfMatcherUrl(
     appFriendsPolicy,
     appFriendsMatcher
   );
   ```
8. [acp\_ess\_2.setAllowModes](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#setallowmodes) to specify that the policy allows `Read` and `Write` modes:

   ```javascript
   appFriendsPolicy = acp_ess_2.setAllowModes(appFriendsPolicy,
     { read: true, write: true }
   );
   ```
9. [acp\_ess\_2.addPolicyUrl](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#addpolicyurl) to apply the new policy to the resource:

   ```javascript
   resourceWithAcr = acp_ess_2.addPolicyUrl(
     resourceWithAcr,
     asUrl(appFriendsPolicy)
   );
   ```
10. [acp\_ess\_2.setResourcePolicy](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#setresourcepolicy) to store the new policy definition to the ACR:

    ```javascript
    resourceWithAcr = acp_ess_2.setResourcePolicy(
      resourceWithAcr,
      appFriendsPolicy
    );
    ```
11. [acp\_ess\_2.saveAcrFor](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#saveacrfor) to save the modified ACR.

    ```javascript
    const updatedResourceWithAcr = await acp_ess_2.saveAcrFor(
      resourceWithAcr,
      { fetch: fetch }          // fetch from the authenticated session
    );
    ```

#### Make a Resource Public: Create Public Policy for a Resource

The following example uses the ACP-specific APIs to set up a **`public-policy`** that allows Read access to the public (i.e., everyone) for a resource.

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

// ... Various logic, including login logic, omitted for brevity.
// ...

async function setupPublicReadPolicyForResource(resourceURL) {
  try {
    // 1. Fetch the SolidDataset with its Access Control Resource (ACR).
    let resourceWithAcr = await acp_ess_2.getSolidDatasetWithAcr(
      resourceURL,              // Resource for which to set up the policies
      { fetch: fetch }          // fetch from the authenticated session
    );

    // 2. Create a Matcher for the Resource.
    let resourcePublicMatcher = acp_ess_2.createResourceMatcherFor(
      resourceWithAcr,
      "match-public"  // Matcher URL will be {ACR URL}#match-public
    );

    // 3. Specify that the matcher matches the Public (i.e., everyone).
    resourcePublicMatcher = acp_ess_2.setPublic(resourcePublicMatcher);

    // 4. Add Matcher to the Resource's ACR.
    resourceWithAcr = acp_ess_2.setResourceMatcher(
      resourceWithAcr,
      resourcePublicMatcher,
    );

    // 5. Create the Policy for the Resource.
    let resourcePolicy = acp_ess_2.createResourcePolicyFor(
      resourceWithAcr,
      "public-policy",  // Policy URL will be {ACR URL}#public-policy
    );

    // 6. Add the Public Matcher to the Policy as an allOf() expression.
    resourcePolicy = acp_ess_2.addAllOfMatcherUrl(
      resourcePolicy,
      resourcePublicMatcher
    );

    // 7. Specify the access modes for the Policy.
    resourcePolicy = acp_ess_2.setAllowModes(
      resourcePolicy,
      { read: true, append: false, write: false },
    );

    // 8. Apply the Policy to the Resource.
    resourceWithAcr = acp_ess_2.addPolicyUrl(
       resourceWithAcr,
       asUrl(resourcePolicy)
     );

    // 9. Add the Policy definition to the Resource's ACR. 
    resourceWithAcr = acp_ess_2.setResourcePolicy(
       resourceWithAcr,
       resourcePolicy,
    );

    // 10. Save the ACR for the Resource.
    const updatedResourceWithAcr = await acp_ess_2.saveAcrFor(
      resourceWithAcr,
      { fetch: fetch }          // fetch from the authenticated session
    );
  } catch (error) {
    console.error(error.message);
  }
}
```

**Details**

In particular, the example uses:

1. [acp\_ess\_2.getSolidDatasetWithAcr](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#getsoliddatasetwithacr) to retrieve the SolidDataset (the SolidDataset can be a Container) with its ACR.

   ```javascript
   let resourceWithAcr = await acp_ess_2.getSolidDatasetWithAcr(
     resourceURL,              // Resource for which to set up the policies
     { fetch: fetch }          // fetch from the authenticated session
   );
   ```

   To specify policies for files with other structures (such as .pdf or .jpeg files), use [acp\_ess\_2.getFileWithAcr](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#getfilewithacr) instead.
2. [acp\_ess\_2.createResourceMatcherFor](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#createresourcematcherfor) to initialize the Matcher that will be used by the policy.

   ```javascript
   let resourcePublicMatcher = acp_ess_2.createResourceMatcherFor(
     resourceWithAcr,
     "match-public"  // Matcher URL will be {ACR URL}#match-public
   );
   ```

   When saved, the Matcher URL will be `{ACR URL}#match-public`.
3. [acp\_ess\_2.setPublic](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#setpublic) to specify that the matcher is a Public matcher; i.e., matches everyone.

   ```javascript
   resourcePublicMatcher = acp_ess_2.setPublic(resourcePublicMatcher);
   ```
4. [acp\_ess\_2.setResourceMatcher](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#setresourcematcher) to store the matcher definition to the ACR:

   ```javascript
   resourceWithAcr = acp_ess_2.setResourceMatcher(
     resourceWithAcr,
     resourcePublicMatcher,
   );
   ```
5. [acp\_ess\_2.createResourcePolicyFor](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#createresourcepolicyfor) to initialize the policy for the Resource:

   ```javascript
   let resourcePolicy = acp_ess_2.createResourcePolicyFor(
     resourceWithAcr,
     "public-policy",  // Policy URL will be {ACR URL}#public-policy
   );
   ```

   When saved, the policy URL will be `{ACR URL}#public-policy`.
6. [acp\_ess\_2.addAllOfMatcherUrl](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#addallofmatcherurl) to add the matcher to the policy.

   ```javascript
   resourcePolicy = acp_ess_2.addAllOfMatcherUrl(
     resourcePolicy,
     resourcePublicMatcher
   );
   ```
7. [acp\_ess\_2.setAllowModes](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#setallowmodes) to specify the access modes for the policy:

   ```javascript
   resourcePolicy = acp_ess_2.setAllowModes(
     resourcePolicy,
     { read: true, append: false, write: false },
   );
   ```
8. [acp\_ess\_2.addPolicyUrl](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#addpolicyurl) to apply the new policy to the resource:

   ```javascript
   resourceWithAcr = acp_ess_2.addPolicyUrl(
      resourceWithAcr,
      asUrl(resourcePolicy)
    );
   ```
9. [acp\_ess\_2.setResourcePolicy](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#setresourcepolicy) to store the new policy definition to the ACR:

   ```javascript
   resourceWithAcr = acp_ess_2.setResourcePolicy(
      resourceWithAcr,
      resourcePolicy,
   );
   ```
10. [acp\_ess\_2.saveAcrFor](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#saveacrfor) to save the modified ACR.

    ```javascript
    const updatedResourceWithAcr = await acp_ess_2.saveAcrFor(
      resourceWithAcr,
      { fetch: fetch }          // fetch from the authenticated session
    );
    ```

#### View Policies and Matchers for a Resource

The following example uses the ACP-specific APIs to view the ACP policies for a resource.

```javascript
import { handleIncomingRedirect, login, fetch, getDefaultSession } from '@inrupt/solid-client-authn-browser';
import { acp_ess_2, solidDatasetAsTurtle } from "@inrupt/solid-client";

// ... Various logic, including login logic, omitted for brevity.

async function viewResourceACR(resourceURL) {

  try {
    // 1. Fetch the SolidDataset with its Access Control Resource (ACR).
    const resourceWithAcr = await acp_ess_2.getSolidDatasetWithAcr(
      resourceURL,
      { fetch: fetch }            // fetch from the authenticated session
    );

    // 2a. Get the Access Control Resource (ACR)
    const myACR = await getSolidDataset(
      acp_ess_2.getLinkedAcrUrl(resourceWithAcr),
      { fetch: fetch }
    );
    
    // 2b. Output (formatted as Turtle) its policies and matchers details.
    console.log(solidDatasetAsTurtle(myACR));

    // 3a. Get all policies from the ACR to process policies.
    const myResourcePolicies = acp_ess_2.getResourcePolicyAll(resourceWithAcr);

    // Loop through each policy for processing.
    myResourcePolicies.forEach(policy => {
      //... 
    });

    // 3b. Get a specific policy from the ACR.
    const specificPolicy = acp_ess_2.getResourcePolicy(
      resourceWithAcr,
      "specify-the-name-of-policy-to-get"
    );

    // 4a. Get all matchers from the ACR to process matchers.
    const myResourceMatchers = acp_ess_2.getResourceMatcherAll(resourceWithAcr)

    // Loop through each matcher for processing.
    myResourceMatchers.forEach(matcher => {
      // ... 
    });

    // 4b. Get a specific matcher from the ACR.
    const specificMatcher = acp_ess_2.getResourceMatcher(
      resourceWithAcr,
      "specify-the-name-of-matcher-to-get"
    );


  } catch (error) {
      console.error(error.message);
  }
}
```

**Details**

In particular, the example uses:

1. [acp\_ess\_2.getSolidDatasetWithAcr](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#getsoliddatasetwithacr) to retrieve the SolidDataset (the SolidDataset can be a Container) with its ACR.

   ```javascript
   const resourceWithAcr = await acp_ess_2.getSolidDatasetWithAcr(
     resourceURL,
     { fetch: fetch }            // fetch from the authenticated session
   );
   ```

   To specify policies for files with other structures (such as .pdf or .jpeg files), use [acp\_ess\_2.getFileWithAcr](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#getfilewithacr) instead.
2. [getSolidDataset](https://inrupt.github.io/solid-client-js/modules/resource_solidDataset.html#getsoliddataset) with [getLinkedAcrUrl](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#getlinkedacrurl) to retrieve the ACR.

   ```javascript
   const myACR = await getSolidDataset(
     acp_ess_2.getLinkedAcrUrl(resourceWithAcr),
     { fetch: fetch }
   );
   ```

   Once you retrieve the ACR as a SolidDataset, you can use [solidDatasetAsTurtle](https://inrupt.github.io/solid-client-js/modules/formats.html#soliddatasetasturtle) to format ACR as [Turtle](https://www.w3.org/TR/turtle/).

   ```javascript
   console.log(solidDatasetAsTurtle(myACR));
   ```
3. [acp\_ess\_2.getResourcePolicyAll](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#getresourcepolicyall) to get the policies from the resource’s ACR.

   ```javascript
   const myResourcePolicies = acp_ess_2.getResourcePolicyAll(resourceWithAcr);

   // Loop through each policy for processing.
   myResourcePolicies.forEach(policy => {
     //... 
   });
   ```

   To view a specific policy, you can use [acp\_ess\_2.getResourcePolicy](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#getresourcepolicy):

   ```javascript
   const specificPolicy = acp_ess_2.getResourcePolicy(
     resourceWithAcr,
     "specify-the-name-of-policy-to-get"
   );
   ```
4. [acp\_ess\_2.getResourceMatcherAll](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#getresourcematcherall) to get all matchers from the resource’s ACR.

   ```javascript
   const myResourceMatchers = acp_ess_2.getResourceMatcherAll(resourceWithAcr)

   // Loop through each matcher for processing.
   myResourceMatchers.forEach(matcher => {
     // ... 
   });
   ```

   To view a specific matcher, you can use [acp\_ess\_2.getResourceMatcher](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#getresourcematcher):

   ```javascript
   const specificMatcher = acp_ess_2.getResourceMatcher(
     resourceWithAcr,
     "specify-the-name-of-matcher-to-get"
   );
   ```

#### Delete Existing Policy for a Resource

The following example deletes an existing Policy for a resource.

{% hint style="info" %}
**Tip**

To view existing Policies for a resource, see View Policies and Matchers for a Resource.
{% endhint %}

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

// ... Various logic, including login logic, omitted for brevity.

async function deletePolicyForResource(resourceURL, policyName) {

  try {

    // 1. Fetch the SolidDataset with its Access Control Resource (ACR).
    let resourceWithAcr = await acp_ess_2.getSolidDatasetWithAcr(
      resourceURL,           // Resource whose policy you want to delete
      { fetch: fetch }       // fetch from the authenticated session
    );

    // 2. Remove the Policy definition from the ACR
    resourceWithAcr = acp_ess_2.removeResourcePolicy(resourceWithAcr, policyName);

    // 3. Save the ACR for the Resource.
    const updatedResourceWithAcr = await acp_ess_2.saveAcrFor(
      resourceWithAcr,
      { fetch: fetch }          // fetch from the authenticated session
    );

  } catch (error) {
    console.error(error.message);
  }
}
```

**Details**

In particular, the example uses:

1. [acp\_ess\_2.getSolidDatasetWithAcr](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#getsoliddatasetwithacr) to retrieve the SolidDataset (the SolidDataset can be a Container) with its ACR.

   ```javascript
   let resourceWithAcr = await acp_ess_2.getSolidDatasetWithAcr(
     resourceURL,           // Resource whose policy you want to delete
     { fetch: fetch }       // fetch from the authenticated session
   );
   ```

   To specify policies for files with other structures (such as .pdf or .jpeg files), use [acp\_ess\_2.getFileWithAcr](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#getfilewithacr) instead.
2. [acp\_ess\_2.removeResourcePolicy](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#removeresourcepolicy) to delete the Policy definition from the ACR:

   ```javascript
   resourceWithAcr = acp_ess_2.removeResourcePolicy(resourceWithAcr, policyName);
   ```

   [acp\_ess\_2.removeResourcePolicy](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#removeresourcepolicy) can also accept the Policy URL or the Policy itself instead of the Policy name.
3. [acp\_ess\_2.saveAcrFor](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#saveacrfor) to save the modified ACR.

   ```javascript
   const updatedResourceWithAcr = await acp_ess_2.saveAcrFor(
     resourceWithAcr,
     { fetch: fetch }          // fetch from the authenticated session
   );
   ```

#### Modify Existing Matcher for a Resource

The following example continues from an earlier example. Specifically, the example modifies the **`match-app-friends`** created in [Create Policy to Match Agents and Clients](#create-policy-to-match-agents-and-clients) to remove one of the Agents from the match list.

Tip

To view existing Matchers for a resource, see [View Policies and Matchers for a Resource](#view-policies-and-matchers-for-a-resource).

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

// ... Various logic, including login logic, omitted for brevity.

async function removeAgentFromMatcher(resourceURL) {

  try {

    // 1. Fetch the SolidDataset with its Access Control Resource (ACR).
    let resourceWithAcr = await acp_ess_2.getSolidDatasetWithAcr(
        resourceURL,           // Resource whose Matcher you want to modify
        { fetch: fetch }       // fetch from the authenticated session
    );

    // 2. Get the Matcher to modify.
    let matcherToModify = acp_ess_2.getResourceMatcher(
        resourceWithAcr,
        "match-app-friends" // Name of the Matcher created in an earlier example.
    );

    // 3. Modify the Matcher; e.g., remove an Agent from the Matcher.

    const agentToRemove="https://id.example.com/chattycarl";
    matcherToModify = acp_ess_2.removeAgent(matcherToModify, agentToRemove);

    // 4. Store the modified Matcher definition to the resource's ACR.
    resourceWithAcr = acp_ess_2.setResourceMatcher(
        resourceWithAcr,
        matcherToModify
    );

    // 5. Save the modified ACR for the resource.
    const updatedResourceWithAcr = await acp_ess_2.saveAcrFor(
        resourceWithAcr,
        { fetch: fetch }          // fetch from the authenticated session
    );

  } catch (error) {
    console.error(error.message);
  }
}
```

#### **Details**

In particular, the example uses:

1. [acp\_ess\_2.getSolidDatasetWithAcr](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#getsoliddatasetwithacr) to retrieve the SolidDataset (the SolidDataset can be a Container) with its ACR.

   ```javascript
   let resourceWithAcr = await acp_ess_2.getSolidDatasetWithAcr(
       resourceURL,           // Resource whose Matcher you want to modify
       { fetch: fetch }       // fetch from the authenticated session
   );
   ```

   To specify policies for files with other structures (such as .pdf or .jpeg files), use [acp\_ess\_2.getFileWithAcr](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#getfilewithacr) instead.
2. [acp\_ess\_2.getResourceMatcher](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#getresourcematcher) to get the Matcher from the resource’s ACR.

   ```javascript
   let matcherToModify = acp_ess_2.getResourceMatcher(
       resourceWithAcr,
       "match-app-friends" // Name of the Matcher created in an earlier example.
   );
   ```

   The `match-app-friends` was created in an earlier example, [Create Policy to Match Agents and Clients](#create-policy-to-match-agents-and-clients).

   Tip

   To view existing Matchers for a resource, see [View Policies and Matchers for a Resource](https://docs.inrupt.com/security/authorization/acp#view-policies-and-matchers-for-a-resource).
3. [acp\_ess\_2.removeAgent](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#removeagent) to remove an Agent’s WebID from the list of the Matcher’s WebIDs to match.

   ```javascript
   policyToModify = acp_ess_2.setAllowModes(policyToModify,
     { write: false }
   );
   ```
4. [acp\_ess\_2.setResourceMatcher](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#setresourcematcher) to update the Matcher definition in the ACR:

   ```javascript
   resourceWithAcr = acp_ess_2.setResourcePolicy(
     resourceWithAcr,
     policyToModify
   );
   ```
5. [acp\_ess\_2.saveAcrFor](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#saveacrfor) to save the modified ACR.

   ```javascript
   const updatedResourceWithAcr = await acp_ess_2.saveAcrFor(
     resourceWithAcr,
     { fetch: fetch }          // fetch from the authenticated session
   );
   ```

#### Modify Existing Policy for a Resource

The following example continues from an earlier example. Specifically, the example modifies the **`app-friends-policy`** created in [Create Policy to Match Agents and Client](#create-policy-to-match-agents-and-clients).

{% hint style="info" %}
Tip

To view existing Policies for a resource, see [View Policies and Matchers for a Resource](#view-policies-and-matchers-for-a-resource).
{% endhint %}

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

// ... Various logic, including login logic, omitted for brevity.

async function modifyAppFriendsPolicy(resourceURL) {

  try {

    // 1. Fetch the SolidDataset with its Access Control Resource (ACR).
    let resourceWithAcr = await acp_ess_2.getSolidDatasetWithAcr(
      resourceURL,           // Resource whose Policy you want to modify
      { fetch: fetch }       // fetch from the authenticated session
    );

    // 2. Get the Policy to modify. 
    let policyToModify = acp_ess_2.getResourcePolicy(
      resourceWithAcr,
      "app-friends-policy" // Name of the Policy created in an earlier example.
    );

    // 3. Change the Write access mode to false (from true). Other access modes remain unchanged.
    policyToModify = acp_ess_2.setAllowModes(policyToModify,
      { write: false }
    );

    // 4. Store the modified Policy definition to the resource's ACR. 
    resourceWithAcr = acp_ess_2.setResourcePolicy(
      resourceWithAcr,
      policyToModify
    );

    // 5. Save the modified ACR for the resource.
    const updatedResourceWithAcr = await acp_ess_2.saveAcrFor(
      resourceWithAcr,
      { fetch: fetch }          // fetch from the authenticated session
    );

  } catch (error) {
    console.error(error.message);
  }
}
```

**Details**

In particular, the example uses:

1. [acp\_ess\_2.getSolidDatasetWithAcr](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#getsoliddatasetwithacr) to retrieve the SolidDataset (the SolidDataset can be a Container) with its ACR.

   ```javascript
   let resourceWithAcr = await acp_ess_2.getSolidDatasetWithAcr(
     resourceURL,           // Resource whose Policy you want to modify
     { fetch: fetch }       // fetch from the authenticated session
   );
   ```

   To specify policies for files with other structures (such as .pdf or .jpeg files), use [acp\_ess\_2.getFileWithAcr](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#getfilewithacr) instead.
2. [acp\_ess\_2.getResourcePolicy](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#getresourcepolicy) to get the Policy from the resource’s ACR. The `app-friends-policy` was created in an earlier example, [Create Policy to Match Agents and Clients](#create-policy-to-match-agents-and-clients).

   ```javascript
   let policyToModify = acp_ess_2.getResourcePolicy(
     resourceWithAcr,
     "app-friends-policy" // Name of the Policy created in an earlier example.
   );
   ```

   Tip

   To view existing Policies for a resource, see [View Policies and Matchers for a Resource](#view-policies-and-matchers-for-a-resource).
3. [acp\_ess\_2.setAllowModes](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#setallowmodes) to update the Write access mode for the Policy. The other Access Modes for this Policy remain unchanged.

   ```javascript
   policyToModify = acp_ess_2.setAllowModes(policyToModify,
     { write: false }
   );
   ```

   For additional Policy functions, see the [API documentation](https://inrupt.github.io/solid-client-js/).
4. [acp\_ess\_2.setResourcePolicy](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#setresourcepolicy) to update the Policy definition in the ACR:

   ```javascript
   resourceWithAcr = acp_ess_2.setResourcePolicy(
     resourceWithAcr,
     policyToModify
   );
   ```
5. [acp\_ess\_2.saveAcrFor](https://inrupt.github.io/solid-client-js/modules/acp_ess_2.html#saveacrfor) to save the modified ACR.

   ```javascript
   const updatedResourceWithAcr = await acp_ess_2.saveAcrFor(
     resourceWithAcr,
     { fetch: fetch }          // fetch from the authenticated session
   );
   ```


# Identity-Based Access Policies

With identity-based access policies, you can:

* Define access for specific agents using their [WebIDs](/reference/glossary#webid); e.g., WebID<sub>agentX</sub> and WebID<sub>agentY</sub> have **`Read`** access to a Pod resource.
* Define access for all agents using a Public agent identifier **`http://www.w3.org/ns/solid/acp#PublicAgent`**.
* Define access for all authenticated (or all unauthenticated) agents using an Authenticated agent identifier.

Additionally, you can include [Client IDs](/reference/glossary#client-identifier) (in the [Client Matcher](/security/authorization/acp#matchers)) to the agents’ access policy definitions. This feature allows you to decide not only **who** has access to your data but also **which applications** the agent can use to access your data. To include the Client ID in the agents’ access policy definition:

* Use the Client ID of specific clients to include them in the agents’ access definition.
* Use the Public Client ID **`http://www.w3.org/ns/solid/acp#PublicClient`** to include all clients in the agents’ access definition.

### ACP

ESS uses [Access Control Policy (ACP)](/security/authorization/acp) to define the policies that determine access to Pod’s resources. For identity-based access, the resource must have an ACP that specifies:

* [Agent Matcher](/security/authorization/acp#matchers) identifying the agents, and optionally, the [Client Matcher](/security/authorization/acp#matchers) identifying the clients.
* The access mode(s) (**`Read`**, **`Write`**, **`Append`**) to allow/deny.

For more information on ACP, see Access Control Policy (ACP).

### Identity-Based Access Services

To support identity-based access, ESS provides the following services:

* [Authorization Service](https://docs.inrupt.com/ess/latest/services/service-authorization)


# Access Requests and Grants

Inrupt's Enterprise Solid Server (ESS) supports an authorization mechanism based on Access Requests and Grants. With Access Requests and Grants:

1. An [agent](/reference/glossary#agent) sends an Access Request to the [resource owner](/reference/glossary#resource-owner) . In ESS, the Access Request is serialized as a [VC](/reference/glossary#verifiable-credential). This request includes the specific [access mode](/reference/glossary#access-modes) (e.g. **`Read`** , **`Write`** , **`Append`** ), the resources to access, the purpose the data will be used, etc.
2. The resource owner decides to deny or grant the Access Request:

* For an approved request, ESS creates an Access Grant with an approved status.
* For a denied request, ESS creates an Access Grant with a denied status.

  \
  In ESS, the Access Grant is serialized as a VC, and the resource owner can revoke the Access Grant in the future.

3. Once the resource owner approves the Access Grant, the requesting agent can retrieve the resource with a direct HTTP request using their [ESS Access Token](/security/authentication#ess-access-token). The Access Grant is a receipt — ESS checks the grant server-side when the agent makes the request, so no additional token exchange is needed.

Note:

* An Access Request for a [Container](/reference/glossary#container), by default, also applies to the Container’s descendants, unless explicitly specified otherwise in the request (See [inherit: false](https://docs.inrupt.com/ess/latest/services/service-access-grant/) ).
* An Access Grant for a Container, by default, also applies to the Container’s descendants, unless explicitly specified otherwise in the grant (See [inherit: false](https://docs.inrupt.com/ess/latest/services/service-access-grant/) ).

### Access Grant Effective Period

An active (i.e., not revoked) access grant is effective:

* Starting from its **`issuanceDate`** to its **`expirationDate`** , and
* While its **`credentialSubject.id`** (the grantor) remains a resource owner .

### Services to Support Access Requests and Grants

To support access requests and grants, ESS provides the following services:

* [Access Grant Service](https://docs.inrupt.com/ess/latest/services/service-access-grant/). The Access Grant service is responsible for issuing, verifying, and revoking Verifiable Credentials.
* [Authorization Service](https://docs.inrupt.com/ess/latest/services/service-authorization/) to manage the ACPs.


# Recommendations for Applications

Applications handling Access Requests/Grants should:

* Validate the Access Requests/Grants’ URL.
* Validate the Resource URLs.
* Use authenticated fetches to fetch the Purpose URLs.
* Escape the values when displaying Purpose URLs and definition.
* NOT display the Purpose URLs as links
* Verify that the requestor is trusted before fetching the profile and extended profile.
* NOT display WebID as links.
  * If dereferencing profile/extended profile:
    * Escape label values if displaying labels.
    * Validate that the image property is a valid URL if displaying the image.
* NOT prompt users on their IDP based on the WebID of the Resource’s Owner.


# Encryption

### At Rest Encryption

It is recommended that you encrypt data at rest. The layers of encryption available are listed below. Use multiple layers for higher levels of protection.

**Hardware (Full Disk) Encryption**

The system hardware itself or the operating system disk management may offer encryption of everything stored on physical media.

**Container or Volume Encryption**

The operating system or the volume management system may offer encryption of everything stored within containers/volumes, allowing for a more granular key control than hardware level.

For example, if using Amazon Elastic Block Store (EBS), encrypt the EBS.

**Database Encryption**

The database may offer encryption of everything it stores, allowing for a more granular key control than container or volume.

When using cloud-managed database services, refer to the key management guidelines provided by the offering.

**File/Folder or Field-level Encryption**

The operating system or database may offer encryption at the individual folder, file, or even field-level. This provides a highly granular key control, as decryption can be required for every field based on unique keys.

**Application Encryption**

Applications may be written so that encryption happens before data reaches the aforementioned layers, with keys managed entirely outside the service or system that is storing the data.

**Messaging System (Kafka) Encryption**

ESS’ services communicate with each other by sending messages through Kafka. Many Kafka deployments already offer data encryption at rest. In addition to this protection, ESS can be configured to encrypt all messages sent to Kafka.

{% hint style="info" %}
Note\
By default, Inrupt enables data encryption for all data that pass through the Kafka messaging system.

\
You **MUST** set the data encryption key values to a strong password.
{% endhint %}

### Transport Layer Security (TLS)

ESS supports TLS 1.2 and 1.3. By configuring your services to use TLS for ingress, you can ensure your data in transit is encrypted. If possible, configure remote services to use TLS encryption.

{% hint style="warning" %}
Important\
In production, ESS should run with certificates from an official Certificate Authority (CA) for all external facing services; e.g., Storage services. Self-signed certificates can be used for internal services. For an example of how you can customize your deployment to use your production certificates, see [Use Official Certificate Authority](https://docs.inrupt.com/ess/latest/installation/customize-configurations/customization-security/use-production-lets-encrypt).
{% endhint %}


# Auditing

ESS auditing provides comprehensive activity monitoring to manage risks. ESS services support the auditing of their activities.

{% hint style="info" %}
For information on the ESS Auditing Service, see [https://docs.inrupt.com/ess/latest/services/service-auditing/](https://docs.inrupt.com/ess/latest/services/service-auditing/ "mention")
{% endhint %}

## Audit Events

The following events are audited:

| Services                                                                                | Event Name                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           | Notes                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Most Services                                                                           | <ul><li><strong><code>service-started</code></strong></li><li><strong><code>service-shutdown</code></strong></li></ul>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | Service Startup/Shutdown.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| Purgeable services (i.e. Access Grant, Authorization, Broker, Pod Provision, and WebID) | <ul><li><strong><code>purge-init</code></strong></li><li><strong><code>purge-completed</code></strong></li></ul>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     | Purge start/complete.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| Access Grant Service                                                                    | <ul><li><strong><code>access-grant-created</code></strong></li><li><strong><code>access-grant-queried</code></strong></li><li><strong><code>access-grant-read</code></strong></li><li><strong><code>access-grant-revoked</code></strong></li><li><strong><code>access-grant-verified</code></strong></li><li><strong><code>access-request-created</code></strong></li><li><strong><code>access-request-read</code></strong></li><li><strong><code>access-request-revoked</code></strong></li><li><strong><code>access-request-verified</code></strong></li><li><strong><code>access-denial-created</code></strong></li><li><strong><code>access-denial-read</code></strong></li><li><strong><code>access-denial-revoked</code></strong></li><li><strong><code>access-denial-verified</code></strong></li><li><strong><code>request-authorized</code></strong></li><li><strong><code>revocation-status-read</code></strong></li></ul> | <p>Access Request/Grant/Denial lifecycle events.<br><br><strong><code>request-authorized</code></strong> events contain additional information (such as the Access Grant service endpoint, the WebID, client id, etc.) for <strong>authenticated</strong> access requests/grants/denial events. For these events, you can find the corresponding <strong><code>request-authorized</code></strong> event using the instrument field. However, other than the service endpoint, the same information may be found in the <strong><code>access-\*</code></strong> event messages themselves.<br><br>As part of the Access Grant to UMA access token exchange (which is an <strong>unauthenticated</strong> event), <strong><code>revocation-status-read</code></strong> events occur. These <strong><code>revocation-status-read</code></strong> events, which are also <em>unauthenticated</em>, do not have an associated <strong><code>request-authorized</code></strong> event.</p> |
| Authorization Service                                                                   | <ul><li><strong><code>acr-created</code></strong></li><li><strong><code>acr-updated</code></strong></li><li><strong><code>acr-deleted</code></strong></li><li><strong><code>provisioned-pod-access-control</code></strong></li><li><strong><code>deprovisioned-pod-access-control</code></strong></li></ul>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          | ACR Lifecycle events.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| Pod Storage Service                                                                     | <ul><li><strong><code>resource-created</code></strong></li><li><strong><code>resource-updated</code></strong></li><li><strong><code>resource-deleted</code></strong></li></ul>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | Resource Lifecycle events.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
|                                                                                         | <ul><li><strong><code>resource-read</code></strong></li></ul>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        | <p>Successful resource read events (<strong><code>GET</code></strong> and <strong><code>HEAD</code></strong> operations).</p><p>To enable, see <a href="https://docs.inrupt.com/ess/latest/services/service-pod-management/service-pod-storage.md#inrupt_storage_audit_resource_read_enabled"><strong><code>INRUPT\_STORAGE\_AUDIT\_RESOURCE\_READ\_ENABLED</code></strong></a> configuration for <a href="https://docs.inrupt.com/ess/latest/services/service-pod-management/service-pod-storage.md">Pod Storage Service.</a></p>                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| Pod Provision Service                                                                   | <ul><li><strong><code>pod-provisioned</code></strong></li></ul>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      | Pod Provisioned event.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| Purger application                                                                      | <ul><li><strong><code>purge-started</code></strong></li><li><strong><code>purge-completed</code></strong></li><li><strong><code>purge-failed</code></strong></li></ul>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | Purge start/end (successful or not).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| Query Service                                                                           | <ul><li><strong><code>query-succeeded</code></strong></li></ul>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      | Query events.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| Query Indexer                                                                           | <ul><li><strong><code>ingest-succeeded</code></strong></li><li><strong><code>ingest-failed</code></strong></li></ul>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 | Query Indexer events.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| Solid OIDC Broker Service                                                               | <ul><li><strong><code>openid-backend-idp-login</code></strong></li><li><strong><code>openid-token-requested</code></strong></li><li><strong><code>openid-authorization-initialized</code></strong></li></ul>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         | Authentication/Authorization flow.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| UMA Service                                                                             | <ul><li><strong><code>uma-token-created</code></strong></li></ul>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    | UMA Grant Flow.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| WebID Service                                                                           | <ul><li><strong><code>webid-created</code></strong></li><li><strong><code>webid-updated</code></strong></li><li><strong><code>webid-deleted</code></strong></li></ul>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                | WebID Profile events.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |

The following services do not produce audit events:

* Notification Gateway service
* WebSocket Notification service
* Start service

{% hint style="info" %}
ESS uses asynchronous messaging for auditing.
{% endhint %}

#### Audit Event Message Internal Format

Internally, ESS’ audit event messages are in RDF and serialized as [ActivityStreams 2.0](https://www.w3.org/TR/activitystreams-core/) JSON-LD documents:

{% hint style="info" %}
Although the following document shows all possible fields for an event message, the specific events determine which fields appear.

Pod-related event messages include:

* the actor information ([WebID](/reference/glossary#webid))
* the application information ([Client ID](/reference/glossary#client-identifier))
* the Pod information (Pod root URL and Pod data subject)
  {% endhint %}

```json
{
   "@context":[
      "https://www.w3.org/ns/activitystreams",
      "https://schema.inrupt.com/audit/v1.jsonld"
   ],
   "id":"<UUID of the event>",
   "type": [ "Activity", <type2>,... ],
   "name":"<event name>",
   "summary": "<event description>",
   "generator": <JSON document identifying the software producing the event>,
   "actor": [ <JSON document identifying the actor associated with the event>, ... ],
   "object": [ <JSON document identifying the object associated with the event>, ... ],
   "instrument": [ <JSON document identifying the client/application associated with the event>,
                   <JSON document with associated OpenTelemetry data>,
                   <JSON document with associated application-defined metadata>,
                   <JSON document identifying the Pod root and data subject>, ... ],
   "result": [ <JSON document containing associated results for the event, if any> ],
   "published": "<datetime>",
   "identifier":"<identifier to use for correlated events>"
}
```

<table data-header-hidden><thead><tr><th width="142"></th><th></th></tr></thead><tbody><tr><td><strong><code>@context</code></strong></td><td>Specifies the JSON-LD contexts.</td></tr><tr><td><strong><code>id</code></strong></td><td>Universally Unique IDentifier (UUID) for the event.</td></tr><tr><td><strong><code>type</code></strong></td><td>An array of event types; e.g., <strong><code>[ "Activity", "Create" ]</code></strong>.</td></tr><tr><td><strong><code>name</code></strong></td><td>Name that denotes the event; e.g., <strong><code>service-started</code></strong>, <strong><code>openid-backend-idp-login</code></strong>, <strong><code>resource-created</code></strong>, <strong><code>access-grant-created</code></strong>, etc.</td></tr><tr><td><strong><code>summary</code></strong></td><td>Short description associated with the message <strong><code>name</code></strong>.</td></tr><tr><td><strong><code>generator</code></strong></td><td><p>JSON document identifying the software (e.g., service) producing the event. For example, the <strong><code>generator</code></strong> for a Pod provision event:</p><pre class="language-json"><code class="lang-json">"generator": {
   "id": "&#x3C;service URL>"
   "type": ["SoftwareApplication"],
   "name": "&#x3C;application name>",
   "qualifiedAssociation": "&#x3C;processId>",
   "wasAssociatedWith": "&#x3C;Kubernetes pod name>"
}
</code></pre></td></tr><tr><td><strong><code>actor</code></strong></td><td><p>An array of JSON documents that identify the agents, if any, associated with the event. The actor’s identity can be denoted by various combination of fields, such as (list below is not exhaustive):</p><ul><li><strong><code>id</code></strong> and <strong><code>type</code></strong> fields;</li><li><strong><code>name</code></strong> and <strong><code>type</code></strong> fields.</li></ul><p>The field can also be empty <strong><code>[]</code></strong> for events not initiated by a user (such as service start events, etc.).</p><p><br><strong><code>id</code></strong> and <strong><code>name</code></strong> fields are associated with a <strong><code>type</code></strong> field.<br></p><p>For example:</p><ul><li><p>For an Access Request/Grant event, which include the <strong><code>id</code></strong> and the <strong><code>type</code></strong>:</p><pre class="language-json"><code class="lang-json">"actor" : [
   {
      "id" : "https://id.example.com/someusername",
      "type" : [
         "Agent"
      ]
   }
],
</code></pre></li><li><p>For IdP login events:</p><pre class="language-json"><code class="lang-json">"actor": [
   {
      "name": "someusername",
      "type" : [
         "Agent"
      ]
   }
]
</code></pre></li></ul></td></tr><tr><td><strong><code>object</code></strong></td><td><p>An array of JSON documents that identify the objects associated with the event; that is, the objects against which the action is performed.</p><p>The object can be denoted by various combination of fields, such as (list below is not exhaustive):</p><ul><li><strong><code>id</code></strong> and <strong><code>name</code></strong> fields;</li><li><strong><code>id</code></strong> and <strong><code>type</code></strong> fields;</li><li><strong><code>name</code></strong> and <strong><code>qualifiedAssociation</code></strong> fields;</li></ul><p>For example:</p><ul><li><p>A Pod provisioned event:</p><pre class="language-json"><code class="lang-json">"object": [
   { "type": [ "Storage" ], "id": "&#x3C;Pod Root URL>" }
]
</code></pre></li><li><p>For Access Request/Grant/Denial creation events, the <strong><code>object</code></strong> field contains the created Access Request/Grant/Denial:</p><ul><li><p>The <strong><code>object</code></strong> for the create Access Request/Grant/Denial events contains the created Request/Grant/Denial in a document with:</p><ul><li>the <a href="https://www.w3.org/TR/activitystreams-vocabulary/#dfn-content">content</a> field that contains the Access Request/Grant/Denial as string and</li><li>the <a href="https://www.w3.org/TR/activitystreams-vocabulary/#dfn-mediatype">mediaType</a> field.</li></ul><p>Previously, the <strong><code>object</code></strong> contained the created Access Request/Grant/Denial directly as an element.</p></li><li>The <strong><code>object</code></strong> contains an element that identifies the Pod resource.</li></ul><pre class="language-json"><code class="lang-json">"object": [
   {
     "content" : "{\"id\":\"https://vc.example.com/vc/79288a3 ... }",
     "mediaType": "application/ld+json"
   },
  {
    "type" : [
      "Resource"
    ],
    "id" : "https://storage.example.com/ad3b.../some/resource"
  }
]
</code></pre></li><li><p>For Pod resource CRUD events ( <strong><code>resource-created</code></strong>, <strong><code>resource-read</code>,</strong> <strong><code>resource-updated</code></strong>, and <strong><code>resource-deleted</code></strong>) events, the <strong><code>object</code></strong> field include an object that identifies the Pod resource.<br></p><p>For example:</p><pre class="language-json"><code class="lang-json">"object": [
    {
        "generated" : "6cba8240-9f79-40db-a129-c6b8edddb840",
        "invalidated" : "4e12ed06-c4e5-41bc-bcd3-0f6a821c08ca",
        "type" : [
            "Resource"
        ],
        "id" : "https://storage.example.com/ad3b.../some/resource"
    }
]
</code></pre><p><br>The <strong><code>generated</code></strong> and <strong><code>invalidated</code></strong> fields are internal references to the content that appear in some combination depending on whether it’s a create/read/update/delete event.<br></p><p>The object may also include additional fields, such as <strong><code>accessControl</code></strong> for <strong><code>resource-created</code></strong> events.</p></li></ul></td></tr><tr><td><strong><code>instrument</code></strong></td><td><p>An array of JSON documents that identify:</p><ul><li><p>The clients associated with the event, if any. For example:</p><pre class="language-json"><code class="lang-json">"instrument" : [
    {
        "id" : "https://start.example.com/app/id",
        "summary" : "Client identifier",
    }
],
</code></pre><p><br>Instrument objects that identify the clients have an associated <strong><code>summary</code></strong> field with the value <strong><code>"Client identifier"</code></strong>.</p><p>Some events such as <strong><code>service-started</code></strong> do not have associated clients, and thus may have empty <strong><code>instrument</code></strong> array.</p></li><li><p>The associated <a href="https://opentelemetry.io/docs/what-is-opentelemetry/">OpenTelemetry</a> instrument info</p><p>For example:</p><pre class="language-json"><code class="lang-json">"instrument" : [
    // ...
    {
        "traceId" : "7decd3657a9efffc010a4b6a4b3da586",
        "spanId" : "91123fce3c668451",
        "isSampled" : true,
        "name" : "OpenTelemetry Span Context",
        "type" : [
            "SpanContext"
        ]
    },
// ...
],
</code></pre><p>OpenTelemetry <strong><code>traceId</code></strong> field can be used to correlate messages associated with a request. See <a data-mention href="/pages/A8OA7L4G564J9c0cDCqD">/pages/A8OA7L4G564J9c0cDCqD</a> for more information.</p></li><li><p>The associated Pod information:</p><ul><li><strong><code>hasDataSubject</code></strong> contains the Pod data subject information (Pod Data Subject refers to the agent who created the Pod. )</li><li><strong><code>hasStorage</code></strong> contains the Pod root URL.</li></ul><p>For example:</p><pre class="language-json"><code class="lang-json">"instrument" : [
    // ...
    {
        "hasDataSubject" : {
            "id" : "https://id.example.com/someusername",
            "type" : [
                "https://w3id.org/dpv#DataSubject"
            ]
        },
        "hasStorage" : "https://storage.example.com/root/",
        "type" : [
            "http://www.w3.org/2004/02/skos/core#Concept"
        ]
    }
    // ...
],
</code></pre></li><li><p><a href="https://docs.inrupt.com/ess/latest/administration/application-defined-metadata">Application-defined metadata</a> sent in client requests</p><p>For example:</p><pre class="language-json"><code class="lang-json">"instrument" : [
    // ...
    {
        "name" : "Application-Defined Request Metadata", 
        "items": [    
            {       
                "mediaType" : "text/plain",       
                "name" : "x-correlation-id",       
                "content" : "2049875809728750827498245084"    
            },    
            {       
                "mediaType": "text/plain",       
                "name": "my-client-version",       
                "content": "1.0.3"
            } 
        ], 
        "type": [    
            "urn:uuid:1a05e301-4013-40c9-bae7-5d719b7151c8" 
        ]
    }
    // ...
],
</code></pre><p>See <a href="https://docs.inrupt.com/ess/latest/administration/application-defined-metadata">Application-Defined Metadata</a> for the configuration needed to include in audit events.</p></li></ul></td></tr><tr><td><strong><code>result</code></strong></td><td><p>An array of JSON documents that contains associated results. For example:</p><ul><li>an <strong><code>access-request-verified</code></strong> event includes the results of the verification, or</li><li>an <strong><code>access-grant-revoked</code></strong> event includes the updated status.</li></ul></td></tr><tr><td><strong><code>published</code></strong></td><td>The timestamp of the event.</td></tr><tr><td><strong><code>identifier</code></strong></td><td>Identifier to use for correlated events from a <strong>single</strong> service that have occurred within the same request. To correlate events across services for a request, use the OpenTelemetry <strong><code>traceId</code></strong> in the <strong><code>instrument</code></strong> field.</td></tr></tbody></table>

For examples, see [Appendix: Audit Events Examples](/security/auditing/appendix-audit-events-examples)

### Integration with External Logging Systems

The ESS [Auditing Service](https://docs.inrupt.com/ess/latest/services/service-auditing/) can log to:

* **`sysout`** (default)
* Syslog
* [Microsoft Sentinel](https://azure.microsoft.com/en-us/services/microsoft-sentinel/#overview)

For more information, see [Integration with Syslog](https://docs.inrupt.com/ess/latest/services/service-auditing#integration-with-syslog) and [Integration with Sentinel](https://docs.inrupt.com/ess/latest/services/service-auditing#integration-with-sentinel).


# Appendix: Audit Event Correlation

### Correlation by **`identifier`** Field

To correlate events within a single service for a request, you can use the **`identifier`** field; such as, to correlate the **`request-authorized`** and the various access request/grant/denial lifecycle events.

Although you can also correlate the these events using the OpenTelemetry **`traceId`** field, the **`identifier`** field may be preferred as as the **`identifier`** field is managed by ESS whereas the **`traceId`** is subject to how the client specifies the **`traceId`** for its requests.

### OpenTelemetry **`traceId`** Field

Audit messages include the client-specified [OpenTelemetry](https://opentelemetry.io/docs/what-is-opentelemetry/) **`traceId`** (along with other OpenTelemetry data) in the **`instrument`** field. This field may be used to correlate messages across services.

For example, if a start app specifies a **`traceId`** for its start flow (i.e., user registration to get a WebID and a Pod), you can use this client-specified **`traceId`** value to correlate the events associated with that start flow:

{% hint style="info" %}
**Note**

* In the example messages below, various fields have been omitted for clarity/brevity.
* Correlation by **`traceId`** may not be suitable for audit trail purposes as the **`traceId`** is managed by the client.
  {% endhint %}

<pre class="language-json"><code class="lang-json">{
   //...
   "name" : "webid-created",
   "summary" : "WebId was created",
   //...
   "actor" : [
      {
         "type" : [
            "Agent"
         ],
         "id" : "https://id.example.com/owliverowner",
         "name" : "owliverowner"
      }
   ],
   "object" : [
      {
         "id" : "https://id.example.com/owliverowner",
         "type" : [
            "PersonalProfileDocument"
         ]
      }
   ],

   "instrument" : [
      {
         "id" : "https://start.example.com/app/id",
         "summary" : "Client identifier"
      },
      {
         "spanId" : "f59408a78e40f5a8",
<strong>         "traceId" : "1551e335cfde87a7df87d3242f2d060e",
</strong>         "parentId" : "fafb4c391e0d5189",
         "name" : "OpenTelemetry Span Context",
         "isSampled" : true,
         "type" : [
            "SpanContext"
         ]
      }
   ],
   "result" : [ ],
   "identifier" : "5244408d0f56431ba727cbdd4c177d61",
   "published" : "2023-12-06T01:57:27.835491323Z"
}

{
   //...
   "name" : "provisioned-pod-access-control",
   "summary" : "Provisioned Pod access control",
   //...
   "actor" : [
      {
         "type" : [
            "Agent"
         ],
         "id" : "https://provision.example.com/"
      }
   ],
   "object" : [ ],
   "instrument" : [
      {
         "type" : [
            "Storage"
         ],
         "id" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/"
      },
      {
         "name" : "OpenTelemetry Span Context",
         "isSampled" : true,
         "type" : [
            "SpanContext"
         ],
         "spanId" : "97e22fca05e84103",
<strong>         "traceId" : "1551e335cfde87a7df87d3242f2d060e",
</strong>         "parentId" : "163e168f726abcce"
      }
   ],
   "result" : [ ],
   "identifier" : "6c1ce07ec6b54ee09c21486d4366b277",
   "published" : "2023-12-06T01:57:30.672004Z"
}


{
   // ...
   "name" : "resource-created",
   "summary" : "Resource has been created",
   // ...
   "actor" : [
      {
         "id" : "https://id.example.com/owliverowner",
         "type" : [
            "Agent"
         ]
      }
   ],
   "object" : [
      {
         "accessControl" : [
            "https://authorization.example.com/1fb6b127afb9458b9cf7d405d1c47dde"
         ],
         "type" : [
            "Resource"
         ],
         "id" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/"
      }
   ],
   "instrument" : [
      {
         "id" : "https://start.example.com/app/id",
         "summary" : "Client identifier"
      },
      {
         "spanId" : "5a0fbc0f4aeb0c8f",
<strong>         "traceId" : "1551e335cfde87a7df87d3242f2d060e",
</strong>         "parentId" : "15e8a1b3cd149acf",
         "name" : "OpenTelemetry Span Context",
         "isSampled" : true,
         "type" : [
            "SpanContext"
         ]
      },
      {
         "hasDataSubject" : {
            "id" : "https://id.example.com/owliverowner",
            "type" : [
               "https://w3id.org/dpv#DataSubject"
            ]
         },
         "hasStorage" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/",
         "type" : [
            "http://www.w3.org/2004/02/skos/core#Concept"
         ]
      }
   ],
   "result" : [ ],
   "identifier" : "cf6507cf8b084f5ebfa489c300ae1ad4",
   "published" : "2023-12-06T01:57:30.736939618Z"
}

{
   // ...
   "name" : "acr-created",
   "summary" : "ACR has been created",
   // ...
   "actor" : [
      {
         "type" : [
            "Agent"
         ],
         "id" : "https://provision.example.com/"
      }
   ],
   "object" : [
      {
         "type" : [
            "Resource"
         ],
         "id" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/"
      },
      {
         "type" : [
            "AccessControlResource"
         ],
         "id" : "https://authorization.example.com/1fb6b127afb9458b9cf7d405d1c47dde"
      }
   ],
   "instrument" : [
      {
         "name" : "OpenTelemetry Span Context",
         "isSampled" : true,
         "type" : [
            "SpanContext"
         ],
         "spanId" : "5637a071550bc999",
<strong>         "traceId" : "1551e335cfde87a7df87d3242f2d060e",
</strong>         "parentId" : "91d7a028b1bdb074"
      },
      {
         "hasDataSubject" : {
            "id" : "https://id.example.com/owliverowner",
            "type" : [
               "https://w3id.org/dpv#DataSubject"
            ]
         },
         "hasStorage" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/",
         "type" : [
            "http://www.w3.org/2004/02/skos/core#Concept"
         ]
      }
   ],
   "result" : [ ],
   "identifier" : "83ceb448725e4e3c8bef7576e941e957",
   "published" : "2023-12-06T01:57:31.444925619Z"
}

{
   // ...
   "name" : "resource-created",
   "summary" : "Resource has been created",
   // ...
   "actor" : [
      {
         "id" : "https://id.example.com/owliverowner",
         "type" : [
            "Agent"
         ]
      }
   ],
   "object" : [
      {
         "generated" : "50ece2b2-f46b-4dca-90cb-3a42ee07f6fc",
         "accessControl" : [
            "https://authorization.example.com/0e7c68f9354742a9bbc29da64dcd14c8"
         ],
         "type" : [
            "Resource"
         ],
         "id" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/profile"
      }
   ],
   "instrument" : [
      {
         "id" : "https://start.example.com/app/id",
         "summary" : "Client identifier"
      },
      {
         "spanId" : "3cdec0dcecd7538d",
         "traceId" : "1551e335cfde87a7df87d3242f2d060e",
         "parentId" : "996f99df65b6ebc2",
         "name" : "OpenTelemetry Span Context",
         "isSampled" : true,
         "type" : [
            "SpanContext"
         ]
      },
      {
         "hasDataSubject" : {
            "id" : "https://id.example.com/owliverowner",
            "type" : [
               "https://w3id.org/dpv#DataSubject"
            ]
         },
         "hasStorage" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/",
         "type" : [
            "http://www.w3.org/2004/02/skos/core#Concept"
         ]
      }
   ],
   "result" : [ ],
   "identifier" : "cf6507cf8b084f5ebfa489c300ae1ad4",
   "published" : "2023-12-06T01:57:31.555962708Z"
}

{
   // ...
   "name" : "acr-created",
   "summary" : "ACR has been created",
   // ...
   "actor" : [
      {
         "type" : [
            "Agent"
         ],
         "id" : "https://provision.example.com/"
      }
   ],
   "object" : [
      {
         "type" : [
            "Resource"
         ],
         "id" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/profile"
      },
      {
         "type" : [
            "AccessControlResource"
         ],
         "id" : "https://authorization.example.com/0e7c68f9354742a9bbc29da64dcd14c8"
      }
   ],
   "instrument" : [
      {
         "name" : "OpenTelemetry Span Context",
         "isSampled" : true,
         "type" : [
            "SpanContext"
         ],
         "spanId" : "5b515094a69aaa03",
<strong>         "traceId" : "1551e335cfde87a7df87d3242f2d060e",
</strong>         "parentId" : "ce6251cc1b12dbda"
      },
      {
         "hasDataSubject" : {
            "id" : "https://id.example.com/owliverowner",
            "type" : [
               "https://w3id.org/dpv#DataSubject"
            ]
         },
         "hasStorage" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/",
         "type" : [
            "http://www.w3.org/2004/02/skos/core#Concept"
         ]
      }
   ],
   "result" : [ ],
   "identifier" : "dccd5eb3957d40dcb9974081d17b82fe",
   "published" : "2023-12-06T01:57:31.663061454Z"
}
{
   // ...
   "name" : "resource-updated",
   "summary" : "Resource timestamp has been updated",
   // ...
   "actor" : [
      {
         "id" : "https://id.example.com/owliverowner",
         "type" : [
            "Agent"
         ]
      }
   ],
   "object" : [
      {
         "id" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/",
         "type" : [
            "Resource"
         ]
      }
   ],
   "instrument" : [
      {
         "id" : "https://start.example.com/app/id",
         "summary" : "Client identifier"
      },
      {
         "spanId" : "3b5ec01c1bbf7cc2",
<strong>         "traceId" : "1551e335cfde87a7df87d3242f2d060e",
</strong>         "parentId" : "996f99df65b6ebc2",
         "name" : "OpenTelemetry Span Context",
         "isSampled" : true,
         "type" : [
            "SpanContext"
         ]
      },
      {
         "hasDataSubject" : {
            "id" : "https://id.example.com/owliverowner",
            "type" : [
               "https://w3id.org/dpv#DataSubject"
            ]
         },
         "hasStorage" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/",
         "type" : [
            "http://www.w3.org/2004/02/skos/core#Concept"
         ]
      }
   ],
   "result" : [ ],
   "identifier" : "cf6507cf8b084f5ebfa489c300ae1ad4",
   "published" : "2023-12-06T01:57:31.743024556Z"
}

{
   // ...
   "name" : "pod-provisioned",
   "summary" : "Pod provisioned",
   // ...
   "actor" : [
      {
         "id" : "https://id.example.com/owliverowner",
         "type" : [
            "Agent"
         ]
      }
   ],
   "object" : [
      {
         "id" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/",
         "type" : [
            "Storage"
         ]
      }
   ],
   "instrument" : [
      {
         "id" : "https://start.example.com/app/id",
         "summary" : "Client identifier"
      },
      {
         "spanId" : "36cbd6bd8488a2d9",
<strong>         "traceId" : "1551e335cfde87a7df87d3242f2d060e",
</strong>         "parentId" : "765195098215788f",
         "name" : "OpenTelemetry Span Context",
         "isSampled" : true,
         "type" : [
            "SpanContext"
         ]
      },
      {
         "hasDataSubject" : {
            "id" : "https://id.example.com/owliverowner",
            "type" : [
               "https://w3id.org/dpv#DataSubject"
            ]
         },
         "hasStorage" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/",
         "type" : [
            "http://www.w3.org/2004/02/skos/core#Concept"
         ]
      }
   ],
   "result" : [ ],
   "identifier" : "cf6507cf8b084f5ebfa489c300ae1ad4",
   "published" : "2023-12-06T01:57:31.782133926Z"
}
</code></pre>

For more information on OpenTelemetry, refer to the [OpenTelemetry](https://opentelemetry.io/docs/what-is-opentelemetry/) documentation.

#### Correlation by Application-Defined Property

ESS can propagate [application-defined metadata/properties](https://docs.inrupt.com/ess/latest/administration/application-defined-metadata/) sent in client requests to include in associated log messages, associated audit events, and associated response to the request.

Depending upon the [configuration](https://docs.inrupt.com/ess/latest/administration/application-defined-metadata#configuration), ESS audit events can include the application-defined request metadata in the **`instrument`** field:

{% hint style="info" %}
**Note**

* In the example messages below, various fields have been omitted for clarity/brevity.
* Correlation by client defined properties may not be suitable for audit trail purposes as these properties are subject to how the client manages these properties as well as the ESS configuration.
  {% endhint %}

```json
{
   // ...
   "name" : "request-authorized",
   "summary" : "Request has been authorized",
   // ...
   "instrument" : [
      // ...
      {

        "name" : "Application-Defined Request Metadata",
        "items": [
           {
              "mediaType" : "text/plain",
              "name" : "x-correlation-id",
              "content" : "2049875809728750827498245084"
           },
           {
              "mediaType":"text/plain",
              "name":"my-client-version",
              "content":"1.0.3"
           }
        ],
        "type":[
           "urn:uuid:1a05e301-4013-40c9-bae7-5d719b7151c8"
        ]
      }
      // ...
   ],
   // ...
}
{
   // ...
   "name" : "access-request-created",
   "summary" : ""Access Request has been created",

   "instrument" : [
      // ...
      {

        "name" : "Application-Defined Request Metadata",
        "items": [
           {
              "mediaType" : "text/plain",
              "name" : "x-correlation-id",
              "content" : "2049875809728750827498245084"
           },
           {
              "mediaType":"text/plain",
              "name":"my-client-version",
              "content":"1.0.3"
           }
        ],
        "type":[
           "urn:uuid:1a05e301-4013-40c9-bae7-5d719b7151c8"
        ]
      }
      // ...

   ],
   // ...
 }
```


# Appendix: Audit Events Examples

Audit messages, when logging to **`sysout`** (the default), uses formatted JSON string instead of formatted string.

The following provides some examples of audit events; i.e., the content is not an exhaustive catalog of the audit events.

{% hint style="info" %}
Audit messages are async.
{% endhint %}

## Examples: Service Startup/Shutdown Events

### `service-started`

```json
{
   "@context" : [
      "https://www.w3.org/ns/activitystreams",
      "https://schema.inrupt.com/audit/v1.jsonld"
   ],
   "id" : "urn:uuid:f762e7da-4716-4ed1-9fef-6674c0d5b314",
   "type" : [
      "Activity"
   ],
   "name" : "service-started",
   "summary" : "Service inrupt-provision-postgres-s3 has started up",
   "generator" : {
      "qualifiedAssociation" : "1",
      "type" : [
         "SoftwareApplication"
      ],
      "wasAssociatedWith" : "ess-pod-provision-b984d649b-2q8xr",
      "id" : "https://provision.example.com/",
      "name" : "inrupt-provision-postgres-s3"
   },
   "actor" : [ ],
   "object" : [
      {
         "name" : "quarkus",
         "qualifiedAssociation" : "2.2.0"
      }
   ],
   "instrument" : [ ],
   "result" : [ ],
   "identifier" : "ee61bf8ac01c41f7811debe09d84b0e0",
   "published" : "2023-12-06T01:44:47.218214562Z"
}
```

### `service-shutdown`

```json
{
   "@context" : [
      "https://www.w3.org/ns/activitystreams",
      "https://schema.inrupt.com/audit/v1.jsonld"
   ],
   "id" : "urn:uuid:058e7fb3-bd7b-48b8-aaf4-6a05f3acc243",
   "type" : [
      "Activity"
   ],
   "name" : "service-shutdown",
   "summary" : "Service inrupt-authorization-acp-postgres has shutdown",
   "generator" : {
      "id" : "https://authorization.example.com/",
      "name" : "inrupt-authorization-acp-postgres",
      "qualifiedAssociation" : "19",
      "type" : [
         "SoftwareApplication"
      ],
      "wasAssociatedWith" : "ess-authorization-acp-65cbb8b9db-862hl"
   },
   "actor" : [ ],
   "object" : [
      {
         "qualifiedAssociation" : "2.2.0",
         "name" : "quarkus"
      }
   ],
   "instrument" : [ ],
   "result" : [ ],
   "identifier" : "cb2a1dbe0ae44e7fae1151d9889c7cab",
   "published" : "2023-12-06T02:55:12.805148115Z"
}
```

## Examples: Authorization Code Flow Events

### `openid-authorization-initialized`

{% hint style="success" %}
The **`actor`** field is empty because the agent has yet to log in.
{% endhint %}

```json
{
   "@context" : [
      "https://www.w3.org/ns/activitystreams",
      "https://schema.inrupt.com/audit/v1.jsonld"
   ],
   "id" : "urn:uuid:ecdf3388-a2ab-4a79-839a-0232871a886a",
   "type" : [
      "Activity",
      "Delegation",
      "AuthorizationCodeFlow"
   ],
   "name" : "openid-authorization-initialized",
   "summary" : "Initialized an authorization code flow",
   "generator" : {
      "wasAssociatedWith" : "ess-openid-6f65d964fb-xqgbh",
      "type" : [
         "SoftwareApplication"
      ],
      "qualifiedAssociation" : "53",
      "name" : "inrupt-openid-postgres",
      "id" : "https://openid.example.com/"
   },
   "actor" : [ ],
   "object" : [
      {
         "id" : "https://start.example.com/profile/callback",
         "name" : "redirect_uri"
      }
   ],
   "instrument" : [
      {
         "id" : "https://start.example.com/app/id",
         "summary" : "Client identifier"
      },
      {
         "type" : [
            "SpanContext"
         ],
         "isSampled" : true,
         "spanId" : "db81996cd8e3c0c9",
         "traceId" : "41e9f8c55d811533bf68b850002c3226",
         "name" : "OpenTelemetry Span Context"
      }
   ],
   "result" : [ ],
   "identifier" : "80502e2f79df477ba81d378162a1cf9d",
   "published" : "2023-12-06T01:57:05.631746003Z"
}
```

### `openid-backend-idp-login`

```json
{
   "@context" : [
      "https://www.w3.org/ns/activitystreams",
      "https://schema.inrupt.com/audit/v1.jsonld"
   ],
   "id" : "urn:uuid:bbcc4b78-7571-4ed5-935b-baa6ca2104a7",
   "type" : [
      "Activity",
      "Delegation",
      "AuthorizationCodeFlow"
   ],
   "name" : "openid-backend-idp-login",
   "summary" : "Agent has successfully logged in through backend IdP",
   "generator" : {
      "wasAssociatedWith" : "ess-openid-6f65d964fb-xqgbh",
      "type" : [
         "SoftwareApplication"
      ],
      "qualifiedAssociation" : "53",
      "name" : "inrupt-openid-postgres",
      "id" : "https://openid.example.com/"
   },
   "actor" : [
      {
         "name" : "owliverowner",
         "type" : [
            "Agent"
         ]
      }
   ],
   "object" : [
      {
         "id" : "https://start.example.com/profile/callback",
         "name" : "redirect_uri"
      }
   ],
   "instrument" : [
      {
         "id" : "https://start.example.com/app/id",
         "summary" : "Client identifier"
      },
      {
         "type" : [
            "SpanContext"
         ],
         "isSampled" : true,
         "spanId" : "7758e20bebe9771b",
         "traceId" : "ccbcc02196bff3f965569426e95a0ffb",
         "name" : "OpenTelemetry Span Context"
      }
   ],
   "result" : [ ],
   "identifier" : "f39c96f3beed4cf2b15777de4bfb8a5a",
   "published" : "2023-12-06T01:57:24.796591586Z"
}
```

### `openid-token-requested`

{% hint style="warning" %}
**Tip**

The **`openid-token-requested`** occurs for both new and refresh token requests. The **`summary`** field specifies whether the event is for a new or a refresh token.
{% endhint %}

```json
{
   "@context" : [
      "https://www.w3.org/ns/activitystreams",
      "https://schema.inrupt.com/audit/v1.jsonld"
   ],
   "id" : "urn:uuid:d58b7a48-99b1-44b7-adcf-b9078d5951a6",
   "type" : [
      "Activity",
      "Delegation",
      "AuthorizationCodeFlow"
   ],
   "name" : "openid-token-requested",
   "summary" : "A new token was requested via the authorization code flow",
   "generator" : {
      "wasAssociatedWith" : "ess-openid-6f65d964fb-xqgbh",
      "type" : [
         "SoftwareApplication"
      ],
      "qualifiedAssociation" : "56",
      "name" : "inrupt-openid-postgres",
      "id" : "https://openid.example.com/"
   },
   "actor" : [
      {
         "id" : "https://id.example.com/owliverowner",
         "type" : [
            "Agent"
         ]
      }
   ],
   "object" : [
      {
         "scope" : "webid openid",
         "name" : "authorization_code"
      }
   ],
   "instrument" : [
      {
         "id" : "https://start.example.com/app/id",
         "summary" : "Client identifier"
      },
      {
         "isSampled" : true,
         "name" : "OpenTelemetry Span Context",
         "parentId" : "21fa744eb09a12b4",
         "traceId" : "9218d3aaf044efabd04aada0cebe8b23",
         "spanId" : "51f8de9d43629b49",
         "type" : [
            "SpanContext"
         ]
      }
   ],
   "result" : [ ],
   "identifier" : "0caf3dba1a294235b53f67daed81f20c",
   "published" : "2023-12-06T01:57:26.427829075Z"
}
```

### `uma-token-created`

```json
{
   "@context" : [
      "https://www.w3.org/ns/activitystreams",
      "https://schema.inrupt.com/audit/v1.jsonld"
   ],
   "id" : "urn:uuid:d322542b-fe25-45fc-b8c9-7f61e2e82422",
   "type" : [
      "Activity",
      "Delegation",
      "UmaGrant"
   ],
   "name" : "uma-token-created",
   "summary" : "An access token was created to access a resource",
   "generator" : {
      "wasAssociatedWith" : "ess-authorization-uma-5ccfcd6ffd-dgkvw",
      "type" : [
         "SoftwareApplication"
      ],
      "qualifiedAssociation" : "59",
      "name" : "inrupt-uma",
      "id" : "https://uma.example.com/"
   },
   "actor" : [
      {
         "id" : "https://id.example.com/requestingrabbit",
         "type" : [
            "Agent"
         ]
      }
   ],
   "object" : [
      {
         "scope" : "read",
         "type" : [
            "AccessToken",
            "Bearer"
         ]
      },
      {
         "id" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/shared/recipes/recipe1",
         "type" : [
            "Resource"
         ]
      }
   ],
   "instrument" : [
      {
         "id" : "https://myApp.example.com/appids/app.jsonld",
         "summary" : "Client identifier"
      },
      {
         "spanId" : "ab344dd3620e24ed",
         "traceId" : "0706bcbf2fa9898860e44bd2a30305b9",
         "name" : "OpenTelemetry Span Context",
         "type" : [
            "SpanContext"
         ],
         "isSampled" : true
      },
      {
         "hasDataSubject" : {
            "id" : "https://id.example.com/owliverowner",
            "type" : [
               "https://w3id.org/dpv#DataSubject"
            ]
         },
         "hasStorage" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/",
         "type" : [
            "http://www.w3.org/2004/02/skos/core#Concept"
         ]
      }
   ],
   "result" : [ ],
   "identifier" : "b4c3ba95c15b413a975ed725b66e1d36",
   "published" : "2023-12-06T02:09:42.245252680Z"
}
```

## Examples: WebID/Pod Provision Events

### `webid-created`

```json
{
   "@context" : [
      "https://www.w3.org/ns/activitystreams",
      "https://schema.inrupt.com/audit/v1.jsonld"
   ],
   "id" : "urn:uuid:8107a955-573e-4af4-adb8-ac89fb803dc4",
   "type" : [
      "Activity",
      "Create"
   ],
   "name" : "webid-created",
   "summary" : "WebId was created",
   "generator" : {
      "name" : "inrupt-webid-postgres",
      "qualifiedAssociation" : "54",
      "type" : [
         "SoftwareApplication"
      ],
      "wasAssociatedWith" : "ess-webid-6b94868f4d-ttxj5",
      "id" : "https://id.example.com/"
   },
   "actor" : [
      {
         "type" : [
            "Agent"
         ],
         "id" : "https://id.example.com/owliverowner"
      }
   ],
   "object" : [
      {
         "id" : "https://id.example.com/owliverowner",
         "type" : [
            "PersonalProfileDocument"
         ]
      }
   ],
   "instrument" : [
      {
         "id" : "https://start.example.com/app/id",
         "summary" : "Client identifier"
      },
      {
         "spanId" : "f59408a78e40f5a8",
         "traceId" : "1551e335cfde87a7df87d3242f2d060e",
         "parentId" : "fafb4c391e0d5189",
         "name" : "OpenTelemetry Span Context",
         "isSampled" : true,
         "type" : [
            "SpanContext"
         ]
      }
   ],
   "result" : [ ],
   "identifier" : "5244408d0f56431ba727cbdd4c177d61",
   "published" : "2023-12-06T01:57:27.835491323Z"
}
```

### `pod-provisioned`

```json
{
   "@context" : [
      "https://www.w3.org/ns/activitystreams",
      "https://schema.inrupt.com/audit/v1.jsonld"
   ],
   "id" : "urn:uuid:728562ca-a882-452e-ab82-2157db233761",
   "type" : [
      "Activity",
      "Create"
   ],
   "name" : "pod-provisioned",
   "summary" : "Pod provisioned",
   "generator" : {
      "qualifiedAssociation" : "88",
      "type" : [
         "SoftwareApplication"
      ],
      "wasAssociatedWith" : "ess-pod-provision-b984d649b-2q8xr",
      "id" : "https://provision.example.com/",
      "name" : "inrupt-provision-postgres-s3"
   },
   "actor" : [
      {
         "id" : "https://id.example.com/owliverowner",
         "type" : [
            "Agent"
         ]
      }
   ],
   "object" : [
      {
         "id" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/",
         "type" : [
            "Storage"
         ]
      }
   ],
   "instrument" : [
      {
         "id" : "https://start.example.com/app/id",
         "summary" : "Client identifier"
      },
      {
         "spanId" : "36cbd6bd8488a2d9",
         "traceId" : "1551e335cfde87a7df87d3242f2d060e",
         "parentId" : "765195098215788f",
         "name" : "OpenTelemetry Span Context",
         "isSampled" : true,
         "type" : [
            "SpanContext"
         ]
      },
      {
         "hasDataSubject" : {
            "id" : "https://id.example.com/owliverowner",
            "type" : [
               "https://w3id.org/dpv#DataSubject"
            ]
         },
         "hasStorage" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/",
         "type" : [
            "http://www.w3.org/2004/02/skos/core#Concept"
         ]
      }
   ],
   "result" : [ ],
   "identifier" : "cf6507cf8b084f5ebfa489c300ae1ad4",
   "published" : "2023-12-06T01:57:31.782133926Z"
}
```

### `provisioned-pod-access-control`

{% hint style="info" %}
**Note**

During Pod creation, both **`provisioned-pod-access-control`** and **`acr-created`** events are issued (along with other events). Once a Pod is created, if an ACR is created for a new resource, only the **`acr-created`** event is issued.
{% endhint %}

```json
{
   "@context" : [
      "https://www.w3.org/ns/activitystreams",
      "https://schema.inrupt.com/audit/v1.jsonld"
   ],
   "id" : "urn:uuid:f1a94832-53fd-450c-8206-9290d1699c85",
   "type" : [
      "Activity",
      "Create"
   ],
   "name" : "provisioned-pod-access-control",
   "summary" : "Provisioned Pod access control",
   "generator" : {
      "id" : "https://authorization.example.com/",
      "name" : "inrupt-authorization-acp-postgres",
      "qualifiedAssociation" : "61",
      "type" : [
         "SoftwareApplication"
      ],
      "wasAssociatedWith" : "ess-authorization-acp-65cbb8b9db-862hl"
   },
   "actor" : [
      {
         "type" : [
            "Agent"
         ],
         "id" : "https://provision.example.com/"
      }
   ],
   "object" : [ ],
   "instrument" : [
      {
         "type" : [
            "Storage"
         ],
         "id" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/"
      },
      {
         "name" : "OpenTelemetry Span Context",
         "isSampled" : true,
         "type" : [
            "SpanContext"
         ],
         "spanId" : "97e22fca05e84103",
         "traceId" : "1551e335cfde87a7df87d3242f2d060e",
         "parentId" : "163e168f726abcce"
      }
   ],
   "result" : [ ],
   "identifier" : "6c1ce07ec6b54ee09c21486d4366b277",
   "published" : "2023-12-06T01:57:30.672004Z"
}
```

## Examples: Access Control Resource (ACR) Events

### `acr-created`

{% hint style="info" %}
**Note**

During Pod creation, both **`provisioned-pod-access-control`** and **`acr-created`** events are issued (along with other events). Once a Pod is created, if an ACR is created for a new resource, only the **`acr-created`** event is issued.
{% endhint %}

```json
{
   "@context" : [
      "https://www.w3.org/ns/activitystreams",
      "https://schema.inrupt.com/audit/v1.jsonld"
   ],
   "id" : "urn:uuid:5fdb6043-6ec8-44d9-937a-4b19a14f3d60",
   "type" : [
      "Activity",
      "Create"
   ],
   "name" : "acr-created",
   "summary" : "ACR has been created",
   "generator" : {
      "id" : "https://authorization.example.com/",
      "name" : "inrupt-authorization-acp-postgres",
      "qualifiedAssociation" : "61",
      "type" : [
         "SoftwareApplication"
      ],
      "wasAssociatedWith" : "ess-authorization-acp-65cbb8b9db-862hl"
   },
   "actor" : [
      {
         "type" : [
            "Agent"
         ],
         "id" : "https://provision.example.com/"
      }
   ],
   "object" : [
      {
         "type" : [
            "Resource"
         ],
         "id" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/"
      },
      {
         "type" : [
            "AccessControlResource"
         ],
         "id" : "https://authorization.example.com/1fb6b127afb9458b9cf7d405d1c47dde"
      }
   ],
   "instrument" : [
      {
         "name" : "OpenTelemetry Span Context",
         "isSampled" : true,
         "type" : [
            "SpanContext"
         ],
         "spanId" : "5637a071550bc999",
         "traceId" : "1551e335cfde87a7df87d3242f2d060e",
         "parentId" : "91d7a028b1bdb074"
      },
      {
         "hasDataSubject" : {
            "id" : "https://id.example.com/owliverowner",
            "type" : [
               "https://w3id.org/dpv#DataSubject"
            ]
         },
         "hasStorage" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/",
         "type" : [
            "http://www.w3.org/2004/02/skos/core#Concept"
         ]
      }
   ],
   "result" : [ ],
   "identifier" : "83ceb448725e4e3c8bef7576e941e957",
   "published" : "2023-12-06T01:57:31.444925619Z"
}
```

### `acr-updated`

```json
{
   "@context" : [
      "https://www.w3.org/ns/activitystreams",
      "https://schema.inrupt.com/audit/v1.jsonld"
   ],
   "id" : "urn:uuid:26eec13f-2ccb-4719-b3a0-e39cd277e0c5",
   "type" : [
      "Activity",
      "Update"
   ],
   "name" : "acr-updated",
   "summary" : "ACR has been updated",
   "generator" : {
      "id" : "https://authorization.example.com/",
      "name" : "inrupt-authorization-acp-postgres",
      "qualifiedAssociation" : "74",
      "type" : [
         "SoftwareApplication"
      ],
      "wasAssociatedWith" : "ess-authorization-acp-65cbb8b9db-862hl"
   },
   "actor" : [
      {
         "type" : [
            "Agent"
         ],
         "id" : "https://id.example.com/owliverowner"
      }
   ],
   "object" : [
      {
         "type" : [
            "AccessControlResource"
         ],
         "id" : "https://authorization.example.com/729aacc4ea9349bcbdbd39a70b3e1609"
      },
      {
         "type" : [
            "Resource"
         ],
         "id" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/shared/recipes/recipe1"
      }
   ],
   "instrument" : [
      {
         "summary" : "Client identifier",
         "id" : "https://myApp.example.com/appids/app.jsonld"
      },
      {
         "isSampled" : true,
         "type" : [
            "SpanContext"
         ],
         "name" : "OpenTelemetry Span Context",
         "traceId" : "79fcbf905d908657c0c737711afd662e",
         "spanId" : "70b3b880d42a7935"
      },
      {
         "hasDataSubject" : {
            "id" : "https://id.example.com/owliverowner",
            "type" : [
               "https://w3id.org/dpv#DataSubject"
            ]
         },
         "hasStorage" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/",
         "type" : [
            "http://www.w3.org/2004/02/skos/core#Concept"
         ]
      }
   ],
   "result" : [ ],
   "identifier" : "8dc9bd8318cf41f998a62a16968649ef",
   "published" : "2023-12-06T02:16:10.669229903Z"
}
```

### `acr-deleted`

```json
{
   "@context" : [
      "https://www.w3.org/ns/activitystreams",
      "https://schema.inrupt.com/audit/v1.jsonld"
   ],
   "id" : "urn:uuid:cd122d09-859d-4b2f-9fd3-ad98babcb4f6",
   "type" : [
      "Activity",
      "Delete"
   ],
   "name" : "acr-deleted",
   "summary" : "ACR has been deleted",
   "generator" : {
      "id" : "https://authorization.example.com/",
      "name" : "inrupt-authorization-acp-postgres",
      "qualifiedAssociation" : "65",
      "type" : [
         "SoftwareApplication"
      ],
      "wasAssociatedWith" : "ess-authorization-acp-65cbb8b9db-862hl"
   },
   "actor" : [
      {
         "type" : [
            "Agent"
         ],
         "id" : "https://storage.example.com/"
      }
   ],
   "object" : [
      {
         "type" : [
            "Resource"
         ],
         "id" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/shared/recipes/foobar"
      },
      {
         "type" : [
            "AccessControlResource"
         ],
         "id" : "https://authorization.example.com/ba8724f3d1b4492196dfe1807e4acf24"
      }
   ],
   "instrument" : [
      {
         "name" : "OpenTelemetry Span Context",
         "isSampled" : true,
         "type" : [
            "SpanContext"
         ],
         "spanId" : "6ee2e5a45fb89748",
         "traceId" : "fe7a333fee11532c8d521acd5bb83dcb",
         "parentId" : "940b1cda19fe883e"
      },
      {
         "hasDataSubject" : {
            "id" : "https://id.example.com/owliverowner",
            "type" : [
               "https://w3id.org/dpv#DataSubject"
            ]
         },
         "hasStorage" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/",
         "type" : [
            "http://www.w3.org/2004/02/skos/core#Concept"
         ]
      }
   ],
   "result" : [ ],
   "identifier" : "f3a793215e8d4f5fb5ae1e26aa9a5a03",
   "published" : "2023-12-06T02:02:18.207395621Z"
}
```

## Examples: Resource Lifecycle Events

### `resource-created`

```json
{
   "@context" : [
      "https://www.w3.org/ns/activitystreams",
      "https://schema.inrupt.com/audit/v1.jsonld"
   ],
   "id" : "urn:uuid:c7a6e6c2-d1dd-45a0-bcab-3da70326ec90",
   "type" : [
      "Activity",
      "Create"
   ],
   "name" : "resource-created",
   "summary" : "Resource has been created",
   "generator" : {
      "id" : "https://storage.example.com/",
      "name" : "inrupt-storage-postgres-s3",
      "qualifiedAssociation" : "103",
      "type" : [
         "SoftwareApplication"
      ],
      "wasAssociatedWith" : "ess-pod-storage-77b85c47d8-67k8m"
   },
   "actor" : [
      {
         "type" : [
            "Agent"
         ],
         "id" : "https://id.example.com/owliverowner"
      }
   ],
   "object" : [
      {
         "id" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/shared/recipes/recipe1",
         "generated" : "da5c22ac-efe1-46ae-ac16-4f1be5652ee8",
         "accessControl" : [
            "https://authorization.example.com/729aacc4ea9349bcbdbd39a70b3e1609"
         ],
         "type" : [
            "Resource"
         ]
      }
   ],
   "instrument" : [
      {
         "summary" : "Client identifier",
         "id" : "https://myApp.example.com/appids/app.jsonld"
      },
      {
         "type" : [
            "SpanContext"
         ],
         "spanId" : "a2ee0d9c9eeda575",
         "traceId" : "b0765b873cb2d4fefacac7d346f34952",
         "parentId" : "a09e12bef00a0d70",
         "name" : "OpenTelemetry Span Context",
         "isSampled" : true
      },
      {
         "hasDataSubject" : {
            "id" : "https://id.example.com/owliverowner",
            "type" : [
               "https://w3id.org/dpv#DataSubject"
            ]
         },
         "hasStorage" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/",
         "type" : [
            "http://www.w3.org/2004/02/skos/core#Concept"
         ]
      }
   ],
   "result" : [ ],
   "identifier" : "5d6f751d983541fbb4d4318d658ba7c0",
   "published" : "2023-12-06T02:00:10.973550535Z"
}
```

### `resource-read`

{% hint style="info" %}
**Note**

Starting in 2.2, Pod resource lifecycle events no longer include a **`StorageCreator`** object. See the **`instrument.hasDataSubject`** field instead.
{% endhint %}

```json
{
   "@context" : [
      "https://www.w3.org/ns/activitystreams",
      "https://schema.inrupt.com/audit/v1.jsonld"
   ],
   "id" : "urn:uuid:d5bbc3c9-3d77-4c7f-bf00-5a0872bdf21b",
   "type" : [
      "Activity",
      "Read"
   ],
   "name" : "resource-read",
   "summary" : "Resource has been read",
   "generator" : {
      "id" : "https://storage.example.com/",
      "name" : "inrupt-storage-postgres-s3",
      "qualifiedAssociation" : "103",
      "type" : [
         "SoftwareApplication"
      ],
      "wasAssociatedWith" : "ess-pod-storage-77b85c47d8-67k8m"
   },
   "actor" : [
      {
         "type" : [
            "Agent"
         ],
         "id" : "https://id.example.com/owliverowner"
      }
   ],
   "object" : [
      {
         "type" : [
            "Resource"
         ],
         "id" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/shared/recipes/recipe1"
      }
   ],
   "instrument" : [
      {
         "summary" : "Client identifier",
         "id" : "https://myApp.example.com/appids/app.jsonld"
      },
      {
         "name" : "OpenTelemetry Span Context",
         "traceId" : "714d046745a8edbbfe796201541a78d5",
         "spanId" : "962b26f880fb7f8c",
         "isSampled" : true,
         "type" : [
            "SpanContext"
         ]
      },
      {
         "hasDataSubject" : {
            "id" : "https://id.example.com/owliverowner",
            "type" : [
               "https://w3id.org/dpv#DataSubject"
            ]
         },
         "hasStorage" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/",
         "type" : [
            "http://www.w3.org/2004/02/skos/core#Concept"
         ]
      }
   ],
   "result" : [ ],
   "identifier" : "fc86c6ba8d07444f920db8742aee2aef",
   "published" : "2023-12-06T02:00:25.823085511Z"
}
```

### `resource-updated`

```json
{
   "@context" : [
      "https://www.w3.org/ns/activitystreams",
      "https://schema.inrupt.com/audit/v1.jsonld"
   ],
   "id" : "urn:uuid:00508f6b-5f75-4ebb-8f48-95d550e79f35",
   "type" : [
      "Activity",
      "Update"
   ],
   "name" : "resource-updated",
   "summary" : "Resource has been updated",
   "generator" : {
      "id" : "https://storage.example.com/",
      "name" : "inrupt-storage-postgres-s3",
      "qualifiedAssociation" : "103",
      "type" : [
         "SoftwareApplication"
      ],
      "wasAssociatedWith" : "ess-pod-storage-77b85c47d8-67k8m"
   },
   "actor" : [
      {
         "type" : [
            "Agent"
         ],
         "id" : "https://id.example.com/owliverowner"
      }
   ],
   "object" : [
      {
         "id" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/shared/recipes/recipe1",
         "generated" : "0f6c262b-3659-4e81-8700-ed7fe23732ec",
         "invalidated" : "da5c22ac-efe1-46ae-ac16-4f1be5652ee8",
         "type" : [
            "Resource"
         ]
      }
   ],
   "instrument" : [
      {
         "summary" : "Client identifier",
         "id" : "https://myApp.example.com/appids/app.jsonld"
      },
      {
         "type" : [
            "SpanContext"
         ],
         "spanId" : "1d3da2a8014f87f0",
         "traceId" : "178f00a057a463e09edf5f84205feaff",
         "parentId" : "b4f74f4f3ec5b456",
         "name" : "OpenTelemetry Span Context",
         "isSampled" : true
      },
      {
         "hasDataSubject" : {
            "id" : "https://id.example.com/owliverowner",
            "type" : [
               "https://w3id.org/dpv#DataSubject"
            ]
         },
         "hasStorage" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/",
         "type" : [
            "http://www.w3.org/2004/02/skos/core#Concept"
         ]
      }
   ],
   "result" : [ ],
   "identifier" : "54b744af27d046578372f85dfff6bad4",
   "published" : "2023-12-06T02:01:19.891576528Z"
}
```

### `resource-deleted`

```json
{
   "@context" : [
      "https://www.w3.org/ns/activitystreams",
      "https://schema.inrupt.com/audit/v1.jsonld"
   ],
   "id" : "urn:uuid:6c490719-a0a9-41f5-abd0-b53628bf70cf",
   "type" : [
      "Activity",
      "Delete"
   ],
   "name" : "resource-deleted",
   "summary" : "Resource has been deleted",
   "generator" : {
      "id" : "https://storage.example.com/",
      "name" : "inrupt-storage-postgres-s3",
      "qualifiedAssociation" : "103",
      "type" : [
         "SoftwareApplication"
      ],
      "wasAssociatedWith" : "ess-pod-storage-77b85c47d8-67k8m"
   },
   "actor" : [
      {
         "type" : [
            "Agent"
         ],
         "id" : "https://id.example.com/owliverowner"
      }
   ],
   "object" : [
      {
         "type" : [
            "Resource"
         ],
         "id" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/shared/recipes/foobar",
         "invalidated" : "fae4b4dd-f7e7-4a17-8bc5-d91d44eee785"
      }
   ],
   "instrument" : [
      {
         "summary" : "Client identifier",
         "id" : "https://myApp.example.com/appids/app.jsonld"
      },
      {
         "type" : [
            "SpanContext"
         ],
         "spanId" : "355464ca06a9b3fd",
         "traceId" : "fe7a333fee11532c8d521acd5bb83dcb",
         "parentId" : "98ba369de34be0ea",
         "name" : "OpenTelemetry Span Context",
         "isSampled" : true
      },
      {
         "hasDataSubject" : {
            "id" : "https://id.example.com/owliverowner",
            "type" : [
               "https://w3id.org/dpv#DataSubject"
            ]
         },
         "hasStorage" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/",
         "type" : [
            "http://www.w3.org/2004/02/skos/core#Concept"
         ]
      }
   ],
   "result" : [ ],
   "identifier" : "252fc63ae93042d893f0e8e77a1cb39e",
   "published" : "2023-12-06T02:02:18.216304186Z"
}
```

## Examples: Access Requests/Grants Lifecycle Events

The following displays some of the events related to access requests & access grants lifecycle.

{% hint style="info" %}
**Note**

Authenticated access requests/grants/denials events are preceded by a corresponding **`request-authorized`** event that contain additional information, such as the Access Grant service endpoint, the WebID, the client id, etc. **However**, other than the service endpoint, the same information may be found in the **`access-*`** event messages themselves.

For each access requests/grants/denials lifecycle event that are authenticated, the associated **`request-authorized`** events are also provided below. You can find the corresponding **`request-authorized`** event using the [instrument field](https://docs.inrupt.com/ess/latest/services/service-auditing/#audit-event-message-internal-format).
{% endhint %}

### `access-request-created`

```json
{
   "@context" : [
      "https://www.w3.org/ns/activitystreams",
      "https://schema.inrupt.com/audit/v1.jsonld"
   ],
   "id" : "urn:uuid:e6498113-c4a5-49af-8838-266e24b190ce",
   "type" : [
      "Activity",
      "Create"
   ],
   "name" : "access-request-created",
   "summary" : "Access Request has been created",
   "generator" : {
      "name" : "inrupt-verifiable-credentials-postgres",
      "id" : "https://vc.example.com/",
      "wasAssociatedWith" : "ess-verifiable-credentials-55599fc46f-6xtpq",
      "type" : [
         "SoftwareApplication"
      ],
      "qualifiedAssociation" : "54"
   },
   "actor" : [
      {
         "type" : [
            "Agent"
         ],
         "id" : "https://id.example.com/requestingrabbit"
      }
   ],
   "object" : [
      {
         "mediaType" : "application/ld+json",
         "content" : "{\"id\":\"https://vc.example.com/vc/e9269ea3-391f-42e7-adaa-58a9146e81ad\",\"type\":[\"VerifiableCredential\",\"SolidAccessRequest\"],\"proof\":{\"type\":\"Ed25519Signature2020\",\"created\":\"2023-12-06T02:03:13.737Z\",\"domain\":\"solid\",\"proofPurpose\":\"assertionMethod\",\"proofValue\":\"z4j8PWVUsu4RE4JyUSteqncXd1DosUyjABQwJ2RjvAVsqFEVCm2qYUyEbQknzXptk4pnACUW4CyHBP1gULBr6ShCB\",\"verificationMethod\":\"https://vc.example.com/key/808ee0d5-6cbb-3bbe-ac7e-41050e32ce69\"},\"credentialStatus\":{\"id\":\"https://vc.example.com/status/F2YF#0\",\"type\":\"RevocationList2020Status\",\"revocationListCredential\":\"https://vc.example.com/status/F2YF\",\"revocationListIndex\":\"0\"},\"credentialSubject\":{\"id\":\"https://id.example.com/requestingrabbit\",\"hasConsent\":{\"mode\":\"Read\",\"forPersonalData\":\"https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/shared/recipes/recipe1\",\"forPurpose\":\"https://example.com/purposes#print\",\"hasStatus\":\"ConsentStatusRequested\",\"isConsentForDataSubject\":\"https://id.example.com/owliverowner\"}},\"expirationDate\":\"2024-12-05T02:03:13.384255813Z\",\"issuanceDate\":\"2023-12-06T02:03:13.385Z\",\"issuer\":\"https://vc.example.com\",\"@context\":[\"https://www.w3.org/2018/credentials/v1\",\"https://schema.inrupt.com/credentials/v2.jsonld\",\"https://w3id.org/security/data-integrity/v1\",\"https://w3id.org/vc-revocation-list-2020/v1\",\"https://w3id.org/vc/status-list/2021/v1\",\"https://w3id.org/security/suites/ed25519-2020/v1\"]}"
      },
      {
         "type" : [
            "Resource"
         ],
         "id" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/shared/recipes/recipe1"
      }
   ],
   "instrument" : [
      {
         "summary" : "Client identifier",
         "id" : "https://myApp.example.com/appids/app.jsonld"
      },
      {
         "name" : "OpenTelemetry Span Context",
         "type" : [
            "SpanContext"
         ],
         "isSampled" : true,
         "spanId" : "db664878c9b1cf27",
         "traceId" : "45946ad4df2d8aefcfc0c01f433f9a94"
      },
      {
         "hasDataSubject" : {
            "id" : "https://id.example.com/owliverowner",
            "type" : [
               "https://w3id.org/dpv#DataSubject"
            ]
         },
         "hasStorage" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/",
         "type" : [
            "http://www.w3.org/2004/02/skos/core#Concept"
         ]
      }
   ],
   "result" : [ ],
   "identifier" : "a56ec16479774029bc3ee10d4bb2f347",
   "published" : "2023-12-06T02:03:13.876566145Z"
}
```

And the associated (i.e., **`"identifier" : "a56ec16479774029bc3ee10d4bb2f347"`**) **`request-authorized`** event:

```json
{
   "@context" : [
      "https://www.w3.org/ns/activitystreams",
      "https://schema.inrupt.com/audit/v1.jsonld"
   ],
   "id" : "urn:uuid:ac3b7866-4ecb-4431-b3a3-0ca3ab0fbe9d",
   "type" : [
      "Activity"
   ],
   "name" : "request-authorized",
   "summary" : "Request has been authorized",
   "generator" : {
      "name" : "inrupt-verifiable-credentials-postgres",
      "id" : "https://vc.example.com/",
      "wasAssociatedWith" : "ess-verifiable-credentials-55599fc46f-6xtpq",
      "type" : [
         "SoftwareApplication"
      ],
      "qualifiedAssociation" : "54"
   },
   "actor" : [
      {
         "type" : [
            "Agent"
         ],
         "id" : "https://id.example.com/requestingrabbit"
      }
   ],
   "object" : [
      {
         "id" : "https://vc.example.com/issue"
      }
   ],
   "instrument" : [
      {
         "summary" : "Client identifier",
         "id" : "https://myApp.example.com/appids/app.jsonld"
      },
      {
         "name" : "OpenTelemetry Span Context",
         "type" : [
            "SpanContext"
         ],
         "isSampled" : true,
         "spanId" : "db664878c9b1cf27",
         "traceId" : "45946ad4df2d8aefcfc0c01f433f9a94"
      }
   ],
   "result" : [ ],
   "identifier" : "a56ec16479774029bc3ee10d4bb2f347",
   "published" : "2023-12-06T02:03:12.906362573Z"
}
```

### `access-grant-created`

```json
{
   "@context" : [
      "https://www.w3.org/ns/activitystreams",
      "https://schema.inrupt.com/audit/v1.jsonld"
   ],
   "id" : "urn:uuid:89bfe43d-f336-446f-9337-f4098c6343f5",
   "type" : [
      "Activity",
      "Create"
   ],
   "name" : "access-grant-created",
   "summary" : "Access Grant has been created",
   "generator" : {
      "name" : "inrupt-verifiable-credentials-postgres",
      "id" : "https://vc.example.com/",
      "wasAssociatedWith" : "ess-verifiable-credentials-55599fc46f-6xtpq",
      "type" : [
         "SoftwareApplication"
      ],
      "qualifiedAssociation" : "63"
   },
   "actor" : [
      {
         "type" : [
            "Agent"
         ],
         "id" : "https://id.example.com/owliverowner"
      }
   ],
   "object" : [
      {
         "mediaType" : "application/ld+json",
         "content" : "{\"id\":\"https://vc.example.com/vc/b1fab093-084e-4281-b663-4deaa7ac9999\",\"type\":[\"SolidAccessGrant\",\"VerifiableCredential\"],\"proof\":{\"type\":\"Ed25519Signature2020\",\"created\":\"2023-12-06T02:07:26.324Z\",\"domain\":\"solid\",\"proofPurpose\":\"assertionMethod\",\"proofValue\":\"z3ofsjethoFSHxDmoKqzYBUHzfjB3s4PfQ8ZqHBufmXkVd5zbEUyhfXJuMvxNYuQLsybBsN1fK66sNKycrcX7auPq\",\"verificationMethod\":\"https://vc.example.com/key/808ee0d5-6cbb-3bbe-ac7e-41050e32ce69\"},\"credentialStatus\":{\"id\":\"https://vc.example.com/status/xHeZ#0\",\"type\":\"RevocationList2020Status\",\"revocationListCredential\":\"https://vc.example.com/status/xHeZ\",\"revocationListIndex\":\"0\"},\"credentialSubject\":{\"id\":\"https://id.example.com/owliverowner\",\"providedConsent\":{\"mode\":\"Read\",\"forPersonalData\":\"https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/shared/recipes/recipe1\",\"forPurpose\":\"https://example.com/purposes#print\",\"hasStatus\":\"ConsentStatusExplicitlyGiven\",\"isProvidedTo\":\"https://id.example.com/requestingrabbit\"}},\"expirationDate\":\"2024-12-05T02:03:13.384Z\",\"issuanceDate\":\"2023-12-06T02:03:13.385Z\",\"issuer\":\"https://vc.example.com\",\"@context\":[\"https://www.w3.org/2018/credentials/v1\",\"https://schema.inrupt.com/credentials/v2.jsonld\",\"https://w3id.org/security/data-integrity/v1\",\"https://w3id.org/vc-revocation-list-2020/v1\",\"https://w3id.org/vc/status-list/2021/v1\",\"https://w3id.org/security/suites/ed25519-2020/v1\"]}"
      },
      {
         "type" : [
            "Resource"
         ],
         "id" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/shared/recipes/recipe1"
      }
   ],
   "instrument" : [
      {
         "summary" : "Client identifier",
         "id" : "https://myApp.example.com/appids/app.jsonld"
      },
      {
         "name" : "OpenTelemetry Span Context",
         "type" : [
            "SpanContext"
         ],
         "isSampled" : true,
         "spanId" : "a9408baf23327b79",
         "traceId" : "95f66eedd75ac5548747a59add2a2903"
      },
      {
         "hasDataSubject" : {
            "id" : "https://id.example.com/owliverowner",
            "type" : [
               "https://w3id.org/dpv#DataSubject"
            ]
         },
         "hasStorage" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/",
         "type" : [
            "http://www.w3.org/2004/02/skos/core#Concept"
         ]
      }
   ],
   "result" : [ ],
   "identifier" : "3e6fd53802cb4f939ace03aec16ac031",
   "published" : "2023-12-06T02:07:26.357470152Z"
}
```

And the associated (i.e., **`"identifier" : "3e6fd53802cb4f939ace03aec16ac031"`**) **`request-authorized`** event:

```json
{
   "@context" : [
      "https://www.w3.org/ns/activitystreams",
      "https://schema.inrupt.com/audit/v1.jsonld"
   ],
   "id" : "urn:uuid:6112b98b-a876-4335-a05c-dbb2b24c95ab",
   "type" : [
      "Activity"
   ],
   "name" : "request-authorized",
   "summary" : "Request has been authorized",
   "generator" : {
      "name" : "inrupt-verifiable-credentials-postgres",
      "id" : "https://vc.example.com/",
      "wasAssociatedWith" : "ess-verifiable-credentials-55599fc46f-6xtpq",
      "type" : [
         "SoftwareApplication"
      ],
      "qualifiedAssociation" : "63"
   },
   "actor" : [
      {
         "type" : [
            "Agent"
         ],
         "id" : "https://id.example.com/owliverowner"
      }
   ],
   "object" : [
      {
         "id" : "https://vc.example.com/issue"
      }
   ],
   "instrument" : [
      {
         "summary" : "Client identifier",
         "id" : "https://myApp.example.com/appids/app.jsonld"
      },
      {
         "name" : "OpenTelemetry Span Context",
         "type" : [
            "SpanContext"
         ],
         "isSampled" : true,
         "spanId" : "a9408baf23327b79",
         "traceId" : "95f66eedd75ac5548747a59add2a2903"
      }
   ],
   "result" : [ ],
   "identifier" : "3e6fd53802cb4f939ace03aec16ac031",
   "published" : "2023-12-06T02:07:26.214344831Z"
}
```

### `access-request-read`

{% hint style="warning" %}
**Tip**

The **`object`** field, not the **`result`** field, contains the Access Request.
{% endhint %}

```json
{
   "@context" : [
      "https://www.w3.org/ns/activitystreams",
      "https://schema.inrupt.com/audit/v1.jsonld"
   ],
   "id" : "urn:uuid:31f73dc1-39b2-4fb8-b0fb-338e563c8ce1",
   "type" : [
      "Activity",
      "Read"
   ],
   "name" : "access-request-read",
   "summary" : "Access Request has been read",
   "generator" : {
      "name" : "inrupt-verifiable-credentials-postgres",
      "id" : "https://vc.example.com/",
      "wasAssociatedWith" : "ess-verifiable-credentials-55599fc46f-6xtpq",
      "type" : [
         "SoftwareApplication"
      ],
      "qualifiedAssociation" : "64"
   },
   "actor" : [
      {
         "type" : [
            "Agent"
         ],
         "id" : "https://id.example.com/owliverowner"
      }
   ],
   "object" : [
      {
         "mediaType" : "application/ld+json",
         "content" : "{\"id\":\"https://vc.example.com/vc/e9269ea3-391f-42e7-adaa-58a9146e81ad\",\"type\":[\"VerifiableCredential\",\"SolidAccessRequest\"],\"proof\":{\"type\":\"Ed25519Signature2020\",\"created\":\"2023-12-06T02:03:13.737Z\",\"domain\":\"solid\",\"proofPurpose\":\"assertionMethod\",\"proofValue\":\"z4j8PWVUsu4RE4JyUSteqncXd1DosUyjABQwJ2RjvAVsqFEVCm2qYUyEbQknzXptk4pnACUW4CyHBP1gULBr6ShCB\",\"verificationMethod\":\"https://vc.example.com/key/808ee0d5-6cbb-3bbe-ac7e-41050e32ce69\"},\"credentialStatus\":{\"id\":\"https://vc.example.com/status/F2YF#0\",\"type\":\"RevocationList2020Status\",\"revocationListCredential\":\"https://vc.example.com/status/F2YF\",\"revocationListIndex\":\"0\"},\"credentialSubject\":{\"id\":\"https://id.example.com/requestingrabbit\",\"hasConsent\":{\"mode\":\"Read\",\"forPersonalData\":\"https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/shared/recipes/recipe1\",\"forPurpose\":\"https://example.com/purposes#print\",\"hasStatus\":\"ConsentStatusRequested\",\"isConsentForDataSubject\":\"https://id.example.com/owliverowner\"}},\"expirationDate\":\"2024-12-05T02:03:13.384255813Z\",\"issuanceDate\":\"2023-12-06T02:03:13.385Z\",\"issuer\":\"https://vc.example.com\",\"@context\":[\"https://www.w3.org/2018/credentials/v1\",\"https://schema.inrupt.com/credentials/v2.jsonld\",\"https://w3id.org/security/data-integrity/v1\",\"https://w3id.org/vc-revocation-list-2020/v1\",\"https://w3id.org/vc/status-list/2021/v1\",\"https://w3id.org/security/suites/ed25519-2020/v1\"]}"
      },
      {
         "type" : [
            "Resource"
         ],
         "id" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/shared/recipes/recipe1"
      }
   ],
   "instrument" : [
      {
         "summary" : "Client identifier",
         "id" : "https://myApp.example.com/appids/app.jsonld"
      },
      {
         "name" : "OpenTelemetry Span Context",
         "type" : [
            "SpanContext"
         ],
         "isSampled" : true,
         "spanId" : "6c2ee165eff4973e",
         "traceId" : "cc5b495f0519023a5c57128aec891cf5"
      },
      {
         "hasDataSubject" : {
            "id" : "https://id.example.com/owliverowner",
            "type" : [
               "https://w3id.org/dpv#DataSubject"
            ]
         },
         "hasStorage" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/",
         "type" : [
            "http://www.w3.org/2004/02/skos/core#Concept"
         ]
      }
   ],
   "result" : [ ],
   "identifier" : "3df615dea1c8425f92765f79b40ff149",
   "published" : "2023-12-06T02:10:48.545339511Z"
}
```

And the associated (i.e., **`"identifier" : "3df615dea1c8425f92765f79b40ff149"`**) **`request-authorized`** event:

```json
{
   "@context" : [
      "https://www.w3.org/ns/activitystreams",
      "https://schema.inrupt.com/audit/v1.jsonld"
   ],
   "id" : "urn:uuid:fd7ecf12-62e3-4982-a9fb-23b0532ff485",
   "type" : [
      "Activity"
   ],
   "name" : "request-authorized",
   "summary" : "Request has been authorized",
   "generator" : {
      "name" : "inrupt-verifiable-credentials-postgres",
      "id" : "https://vc.example.com/",
      "wasAssociatedWith" : "ess-verifiable-credentials-55599fc46f-6xtpq",
      "type" : [
         "SoftwareApplication"
      ],
      "qualifiedAssociation" : "64"
   },
   "actor" : [
      {
         "type" : [
            "Agent"
         ],
         "id" : "https://id.example.com/owliverowner"
      }
   ],
   "object" : [
      {
         "id" : "https://vc.example.com/vc/e9269ea3-391f-42e7-adaa-58a9146e81ad"
      }
   ],
   "instrument" : [
      {
         "summary" : "Client identifier",
         "id" : "https://myApp.example.com/appids/app.jsonld"
      },
      {
         "name" : "OpenTelemetry Span Context",
         "type" : [
            "SpanContext"
         ],
         "isSampled" : true,
         "spanId" : "6c2ee165eff4973e",
         "traceId" : "cc5b495f0519023a5c57128aec891cf5"
      }
   ],
   "result" : [ ],
   "identifier" : "3df615dea1c8425f92765f79b40ff149",
   "published" : "2023-12-06T02:10:48.494225888Z"
}
```

### `access-grant-read`

{% hint style="info" %}
**Tip**

The **`object`** field, not the **`result`** field, contains the Access Grant.
{% endhint %}

```json
{
   "@context" : [
      "https://www.w3.org/ns/activitystreams",
      "https://schema.inrupt.com/audit/v1.jsonld"
   ],
   "id" : "urn:uuid:8b0f42f9-9625-43ab-9bbc-cfce0fd5cd86",
   "type" : [
      "Activity",
      "Read"
   ],
   "name" : "access-grant-read",
   "summary" : "Access Grant has been read",
   "generator" : {
      "id" : "https://vc.example.com/",
      "name" : "inrupt-verifiable-credentials-postgres",
      "qualifiedAssociation" : "54",
      "type" : [
         "SoftwareApplication"
      ],
      "wasAssociatedWith" : "ess-verifiable-credentials-55599fc46f-g948f"
   },
   "actor" : [
      {
         "type" : [
            "Agent"
         ],
         "id" : "https://id.example.com/requestingrabbit"
      }
   ],
   "object" : [
      {
         "mediaType" : "application/ld+json",
         "content" : "{\"id\":\"https://vc.example.com/vc/7c337b74-ff0d-4f5b-87e0-50ec51f4a2a9\",\"type\":[\"SolidAccessGrant\",\"VerifiableCredential\"],\"proof\":{\"type\":\"Ed25519Signature2020\",\"created\":\"2023-12-06T20:28:19.219Z\",\"domain\":\"solid\",\"proofPurpose\":\"assertionMethod\",\"proofValue\":\"z4RMfFdNJQEXrnBbZvYE8yKYxoHbW4Ev6b1eEHvjZa2hnPPHaWjpgdd1JVjbFDbRAQ7YwVJXE35f6jbjuGR9Rpazh\",\"verificationMethod\":\"https://vc.example.com/key/246ab7ff-d416-3353-872d-f0eb82098136\"},\"credentialStatus\":{\"id\":\"https://vc.example.com/status/tkoO#0\",\"type\":\"RevocationList2020Status\",\"revocationListCredential\":\"https://vc.example.com/status/tkoO\",\"revocationListIndex\":\"0\"},\"credentialSubject\":{\"id\":\"https://id.example.com/owliverowner\",\"providedConsent\":{\"mode\":\"Read\",\"forPersonalData\":\"https://storage.example.com/cf377182-f514-4900-b54d-71485037fada/shared/recipes/recipe1\",\"forPurpose\":\"https://example.com/purposes#print\",\"hasStatus\":\"ConsentStatusExplicitlyGiven\",\"isProvidedTo\":\"https://id.example.com/requestingrabbit\"}},\"expirationDate\":\"2024-12-05T20:25:25.734Z\",\"issuanceDate\":\"2023-12-06T20:25:25.734Z\",\"issuer\":\"https://vc.example.com\",\"@context\":[\"https://www.w3.org/2018/credentials/v1\",\"https://schema.inrupt.com/credentials/v2.jsonld\",\"https://w3id.org/security/data-integrity/v1\",\"https://w3id.org/vc-revocation-list-2020/v1\",\"https://w3id.org/vc/status-list/2021/v1\",\"https://w3id.org/security/suites/ed25519-2020/v1\"]}"
      },
      {
         "type" : [
            "Resource"
         ],
         "id" : "https://storage.example.com/cf377182-f514-4900-b54d-71485037fada/shared/recipes/recipe1"
      }
   ],
   "instrument" : [
      {
         "summary" : "Client identifier",
         "id" : "https://myApp.example.com/appids/app.jsonld"
      },
      {
         "name" : "OpenTelemetry Span Context",
         "traceId" : "4d6670e20ba3bab9e0edbaaa687b9323",
         "spanId" : "a2fdb4eaeaaa72b3",
         "isSampled" : true,
         "type" : [
            "SpanContext"
         ]
      },
      {
         "type" : [
            "http://www.w3.org/2004/02/skos/core#Concept"
         ],
         "hasStorage" : "https://storage.example.com/cf377182-f514-4900-b54d-71485037fada/",
         "hasDataSubject" : {
            "type" : [
               "https://w3id.org/dpv#DataSubject"
            ],
            "id" : "https://id.example.com/owliverowner"
         }
      }
   ],
   "result" : [ ],
   "identifier" : "bfe04951202643e788eeb569205c060b",
   "published" : "2023-12-06T20:28:40.993897608Z"
}
```

And the associated (i.e., **`"identifier" : "bfe04951202643e788eeb569205c060b"`**) **`request-authorized`** event:

```json
{
   "@context" : [
      "https://www.w3.org/ns/activitystreams",
      "https://schema.inrupt.com/audit/v1.jsonld"
   ],
   "id" : "urn:uuid:c7dd2d06-a2a3-4b17-a6f8-d2800596271a",
   "type" : [
      "Activity"
   ],
   "name" : "request-authorized",
   "summary" : "Request has been authorized",
   "generator" : {
      "id" : "https://vc.example.com/",
      "name" : "inrupt-verifiable-credentials-postgres",
      "qualifiedAssociation" : "54",
      "type" : [
         "SoftwareApplication"
      ],
      "wasAssociatedWith" : "ess-verifiable-credentials-55599fc46f-g948f"
   },
   "actor" : [
      {
         "type" : [
            "Agent"
         ],
         "id" : "https://id.example.com/requestingrabbit"
      }
   ],
   "object" : [
      {
         "id" : "https://vc.example.com/vc/7c337b74-ff0d-4f5b-87e0-50ec51f4a2a9"
      }
   ],
   "instrument" : [
      {
         "summary" : "Client identifier",
         "id" : "https://myApp.example.com/appids/app.jsonld"
      },
      {
         "name" : "OpenTelemetry Span Context",
         "traceId" : "4d6670e20ba3bab9e0edbaaa687b9323",
         "spanId" : "a2fdb4eaeaaa72b3",
         "isSampled" : true,
         "type" : [
            "SpanContext"
         ]
      }
   ],
   "result" : [ ],
   "identifier" : "bfe04951202643e788eeb569205c060b",
   "published" : "2023-12-06T20:28:40.950169953Z"
}
```

### `access-request-verified`

**Passed Verification Event**

{% hint style="info" %}
**Tip**

The **`result`** field contains information about whether the verification has passed or failed.
{% endhint %}

```json
{
   "@context" : [
      "https://www.w3.org/ns/activitystreams",
      "https://schema.inrupt.com/audit/v1.jsonld"
   ],
   "id" : "urn:uuid:57145c05-eb02-4564-940d-2df1d878c41a",
   "type" : [
      "Activity",
      "Read",
      "Question"
   ],
   "name" : "access-request-verified",
   "summary" : "Access Request has been verified",
   "generator" : {
      "name" : "inrupt-verifiable-credentials-postgres",
      "id" : "https://vc.example.com/",
      "wasAssociatedWith" : "ess-verifiable-credentials-55599fc46f-6xtpq",
      "type" : [
         "SoftwareApplication"
      ],
      "qualifiedAssociation" : "54"
   },
   "actor" : [
      {
         "type" : [
            "Agent"
         ],
         "id" : "https://id.example.com/owliverowner"
      }
   ],
   "object" : [
      {
         "@context" : [
            "https://www.w3.org/2018/credentials/v1",
            "https://schema.inrupt.com/credentials/v2.jsonld"
         ],
         "id" : "https://vc.example.com/vc/e9269ea3-391f-42e7-adaa-58a9146e81ad",
         "type" : [
            "VerifiableCredential",
            "SolidAccessRequest"
         ]
      },
      {
         "type" : [
            "Resource"
         ],
         "id" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/shared/recipes/recipe1"
      }
   ],
   "instrument" : [
      {
         "summary" : "Client identifier",
         "id" : "https://myApp.example.com/appids/app.jsonld"
      },
      {
         "name" : "OpenTelemetry Span Context",
         "type" : [
            "SpanContext"
         ],
         "isSampled" : true,
         "spanId" : "d3888911cb44ac6e",
         "traceId" : "9fb3370da8d77bdbba283904c6c39cb7"
      },
      {
         "hasDataSubject" : {
            "id" : "https://id.example.com/owliverowner",
            "type" : [
               "https://w3id.org/dpv#DataSubject"
            ]
         },
         "hasStorage" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/",
         "type" : [
            "http://www.w3.org/2004/02/skos/core#Concept"
         ]
      }
   ],
   "result" : [
      {
         "mediaType" : "application/json",
         "name" : "Verification passed",
         "type" : [
            "Verification"
         ],
         "content" : "{\"checks\":[\"issuanceDate\",\"proof\",\"expirationDate\",\"credentialStatus\"],\"warnings\":[],\"errors\":[]}"
      }
   ],
   "identifier" : "33e2f68ce12a4ea2b5815997c95aca39",
   "published" : "2023-12-06T02:04:08.106287125Z"
}
```

And the associated (i.e., **`"identifier" : "33e2f68ce12a4ea2b5815997c95aca39"`**) **`request-authorized`** event:

```json
{
   "@context" : [
      "https://www.w3.org/ns/activitystreams",
      "https://schema.inrupt.com/audit/v1.jsonld"
   ],
   "id" : "urn:uuid:e5a7457e-7150-40ef-bedf-56684cbb098b",
   "type" : [
      "Activity"
   ],
   "name" : "request-authorized",
   "summary" : "Request has been authorized",
   "generator" : {
      "name" : "inrupt-verifiable-credentials-postgres",
      "id" : "https://vc.example.com/",
      "wasAssociatedWith" : "ess-verifiable-credentials-55599fc46f-6xtpq",
      "type" : [
         "SoftwareApplication"
      ],
      "qualifiedAssociation" : "54"
   },
   "actor" : [
      {
         "type" : [
            "Agent"
         ],
         "id" : "https://id.example.com/owliverowner"
      }
   ],
   "object" : [
      {
         "id" : "https://vc.example.com/verify"
      }
   ],
   "instrument" : [
      {
         "summary" : "Client identifier",
         "id" : "https://myApp.example.com/appids/app.jsonld"
      },
      {
         "name" : "OpenTelemetry Span Context",
         "type" : [
            "SpanContext"
         ],
         "isSampled" : true,
         "spanId" : "d3888911cb44ac6e",
         "traceId" : "9fb3370da8d77bdbba283904c6c39cb7"
      }
   ],
   "result" : [ ],
   "identifier" : "33e2f68ce12a4ea2b5815997c95aca39",
   "published" : "2023-12-06T02:04:08.001818229Z"
}
```

**Failed Verification Event**

{% hint style="info" %}
**Tip**

The **`result`** field contains information about whether the verification has passed or failed.
{% endhint %}

```json
{
   "@context" : [
      "https://www.w3.org/ns/activitystreams",
      "https://schema.inrupt.com/audit/v1.jsonld"
   ],
   "id" : "urn:uuid:a703205d-a98d-4052-ba8d-51f48e217218",
   "type" : [
      "Activity",
      "Read",
      "Question"
   ],
   "name" : "access-request-verified",
   "summary" : "Access Request has been verified",
   "generator" : {
      "id" : "https://vc.example.com/",
      "name" : "inrupt-verifiable-credentials-postgres",
      "qualifiedAssociation" : "54",
      "type" : [
         "SoftwareApplication"
      ],
      "wasAssociatedWith" : "ess-verifiable-credentials-55599fc46f-g948f"
   },
   "actor" : [
      {
         "type" : [
            "Agent"
         ],
         "id" : "https://id.example.com/owliverowner"
      }
   ],
   "object" : [
      {
         "type" : [
            "VerifiableCredential",
            "SolidAccessRequest"
         ],
         "id" : "https://vc.example.com/vc/2a5eda5e-cf16-4f87-8da1-b17ebc41347b",
         "@context" : [
            "https://www.w3.org/2018/credentials/v1",
            "https://schema.inrupt.com/credentials/v2.jsonld"
         ]
      },
      {
         "type" : [
            "Resource"
         ],
         "id" : "https://storage.example.com/cf377182-f514-4900-b54d-71485037fada/shared/recipes/recipe1"
      }
   ],
   "instrument" : [
      {
         "summary" : "Client identifier",
         "id" : "https://myApp.example.com/appids/app.jsonld"
      },
      {
         "name" : "OpenTelemetry Span Context",
         "traceId" : "2fb93270fce6f46fea8d0f2eaa0e20b2",
         "spanId" : "5bd4ffe43d07423c",
         "isSampled" : true,
         "type" : [
            "SpanContext"
         ]
      },
      {
         "type" : [
            "http://www.w3.org/2004/02/skos/core#Concept"
         ],
         "hasStorage" : "https://storage.example.com/cf377182-f514-4900-b54d-71485037fada/",
         "hasDataSubject" : {
            "type" : [
               "https://w3id.org/dpv#DataSubject"
            ],
            "id" : "https://id.example.com/owliverowner"
         }
      }
   ],
   "result" : [
      {
         "mediaType" : "application/json",
         "content" : "{\"checks\":[\"issuanceDate\",\"proof\",\"expirationDate\",\"credentialStatus\"],\"warnings\":[],\"errors\":[\"Signature validation has failed\"]}",
         "type" : [
            "Verification"
         ],
         "name" : "Verification failed"
      }
   ],
   "identifier" : "f2dd6fa40d9c4b74a30bd1cab02b5abc",
   "published" : "2023-12-06T20:27:01.912472426Z"
}
```

And the associated (i.e., **`"identifier" : "f2dd6fa40d9c4b74a30bd1cab02b5abc"`**) **`request-authorized`** event:

```json
{
   "@context" : [
      "https://www.w3.org/ns/activitystreams",
      "https://schema.inrupt.com/audit/v1.jsonld"
   ],
   "id" : "urn:uuid:8c755df6-ea97-4145-9cde-38f2e1cb2427",
   "type" : [
      "Activity"
   ],
   "name" : "request-authorized",
   "summary" : "Request has been authorized",
   "generator" : {
      "id" : "https://vc.example.com/",
      "name" : "inrupt-verifiable-credentials-postgres",
      "qualifiedAssociation" : "54",
      "type" : [
         "SoftwareApplication"
      ],
      "wasAssociatedWith" : "ess-verifiable-credentials-55599fc46f-g948f"
   },
   "actor" : [
      {
         "type" : [
            "Agent"
         ],
         "id" : "https://id.example.com/owliverowner"
      }
   ],
   "object" : [
      {
         "id" : "https://vc.example.com/verify"
      }
   ],
   "instrument" : [
      {
         "summary" : "Client identifier",
         "id" : "https://myApp.example.com/appids/app.jsonld"
      },
      {
         "name" : "OpenTelemetry Span Context",
         "traceId" : "2fb93270fce6f46fea8d0f2eaa0e20b2",
         "spanId" : "5bd4ffe43d07423c",
         "isSampled" : true,
         "type" : [
            "SpanContext"
         ]
      }
   ],
   "result" : [ ],
   "identifier" : "f2dd6fa40d9c4b74a30bd1cab02b5abc",
   "published" : "2023-12-06T20:27:01.826604127Z"
}
```

### `access-grant-verified`

{% hint style="info" %}
**Tip**

The **`result`** field contains information about whether the verification has passed or failed.
{% endhint %}

```json
{
   "@context" : [
      "https://www.w3.org/ns/activitystreams",
      "https://schema.inrupt.com/audit/v1.jsonld"
   ],
   "id" : "urn:uuid:ce316a60-37b6-4294-a006-8a2e276e526e",
   "type" : [
      "Activity",
      "Read",
      "Question"
   ],
   "name" : "access-grant-verified",
   "summary" : "Access Grant has been verified",
   "generator" : {
      "name" : "inrupt-verifiable-credentials-postgres",
      "id" : "https://vc.example.com/",
      "wasAssociatedWith" : "ess-verifiable-credentials-55599fc46f-6xtpq",
      "type" : [
         "SoftwareApplication"
      ],
      "qualifiedAssociation" : "54"
   },
   "actor" : [
      {
         "type" : [
            "Agent"
         ],
         "id" : "https://id.example.com/owliverowner"
      }
   ],
   "object" : [
      {
         "@context" : [
            "https://www.w3.org/2018/credentials/v1",
            "https://schema.inrupt.com/credentials/v2.jsonld"
         ],
         "id" : "https://vc.example.com/vc/b1fab093-084e-4281-b663-4deaa7ac9999",
         "type" : [
            "VerifiableCredential",
            "SolidAccessGrant"
         ]
      },
      {
         "type" : [
            "Resource"
         ],
         "id" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/shared/recipes/recipe1"
      }
   ],
   "instrument" : [
      {
         "summary" : "Client identifier",
         "id" : "https://myApp.example.com/appids/app.jsonld"
      },
      {
         "name" : "OpenTelemetry Span Context",
         "type" : [
            "SpanContext"
         ],
         "isSampled" : true,
         "spanId" : "255c4ecf07dad61c",
         "traceId" : "091300887526d85075c8225b99150cc1"
      },
      {
         "hasDataSubject" : {
            "id" : "https://id.example.com/owliverowner",
            "type" : [
               "https://w3id.org/dpv#DataSubject"
            ]
         },
         "hasStorage" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/",
         "type" : [
            "http://www.w3.org/2004/02/skos/core#Concept"
         ]
      }
   ],
   "result" : [
      {
         "mediaType" : "application/json",
         "name" : "Verification passed",
         "type" : [
            "Verification"
         ],
         "content" : "{\"checks\":[\"issuanceDate\",\"proof\",\"expirationDate\",\"credentialStatus\"],\"warnings\":[],\"errors\":[]}"
      }
   ],
   "identifier" : "1f62aae1aa0745b89a2e19cadade449c",
   "published" : "2023-12-06T02:07:51.502700524Z"
}
```

And the associated (i.e., **`"identifier" : "1f62aae1aa0745b89a2e19cadade449c"`**) **`request-authorized`** event:

```json
{
   "@context" : [
      "https://www.w3.org/ns/activitystreams",
      "https://schema.inrupt.com/audit/v1.jsonld"
   ],
   "id" : "urn:uuid:fb3a9c6e-7585-4377-9833-4ac21b82531d",
   "type" : [
      "Activity"
   ],
   "name" : "request-authorized",
   "summary" : "Request has been authorized",
   "generator" : {
      "name" : "inrupt-verifiable-credentials-postgres",
      "id" : "https://vc.example.com/",
      "wasAssociatedWith" : "ess-verifiable-credentials-55599fc46f-6xtpq",
      "type" : [
         "SoftwareApplication"
      ],
      "qualifiedAssociation" : "54"
   },
   "actor" : [
      {
         "type" : [
            "Agent"
         ],
         "id" : "https://id.example.com/owliverowner"
      }
   ],
   "object" : [
      {
         "id" : "https://vc.example.com/verify"
      }
   ],
   "instrument" : [
      {
         "summary" : "Client identifier",
         "id" : "https://myApp.example.com/appids/app.jsonld"
      },
      {
         "name" : "OpenTelemetry Span Context",
         "type" : [
            "SpanContext"
         ],
         "isSampled" : true,
         "spanId" : "255c4ecf07dad61c",
         "traceId" : "091300887526d85075c8225b99150cc1"
      }
   ],
   "result" : [ ],
   "identifier" : "1f62aae1aa0745b89a2e19cadade449c",
   "published" : "2023-12-06T02:07:51.433099762Z"
}
```

### `access-grant-queried`

```json
{
   "@context" : [
      "https://www.w3.org/ns/activitystreams",
      "https://schema.inrupt.com/audit/v1.jsonld"
   ],
   "id" : "urn:uuid:03792e68-0393-47d8-980c-024496a6b57e",
   "type" : [
      "Activity",
      "Read",
      "Question"
   ],
   "name" : "access-grant-queried",
   "summary" : "Access Grant has been queried",
   "generator" : {
      "name" : "inrupt-verifiable-credentials-postgres",
      "id" : "https://vc.example.com/",
      "wasAssociatedWith" : "ess-verifiable-credentials-55599fc46f-6xtpq",
      "type" : [
         "SoftwareApplication"
      ],
      "qualifiedAssociation" : "54"
   },
   "actor" : [
      {
         "type" : [
            "Agent"
         ],
         "id" : "https://id.example.com/requestingrabbit"
      }
   ],
   "object" : [
      {
         "mediaType" : "application/json",
         "content" : "{\"verifiableCredential\":{\"@context\":[\"https://www.w3.org/2018/credentials/v1\",\"https://schema.inrupt.com/credentials/v2.jsonld\"],\"type\":[\"SolidAccessGrant\"],\"credentialSubject\":{\"providedConsent\":{\"isProvidedTo\":\"https://id.example.com/requestingrabbit\"}}}}"
      }
   ],
   "instrument" : [
      {
         "summary" : "Client identifier",
         "id" : "https://myApp.example.com/appids/app.jsonld"
      },
      {
         "name" : "OpenTelemetry Span Context",
         "type" : [
            "SpanContext"
         ],
         "isSampled" : true,
         "spanId" : "1a2e03ba340c1e77",
         "traceId" : "fd121ceb727907e761cf691dcecd5323"
      }
   ],
   "result" : [ ],
   "identifier" : "e14d7c7f65bd461c8063401df3f2b3c3",
   "published" : "2023-12-06T02:08:20.900046559Z"
}
```

And the associated (i.e., **`"identifier" : "e14d7c7f65bd461c8063401df3f2b3c3"`**) **`request-authorized`** event:

```json
{
   "@context" : [
      "https://www.w3.org/ns/activitystreams",
      "https://schema.inrupt.com/audit/v1.jsonld"
   ],
   "id" : "urn:uuid:daaf1582-3384-416d-b15a-d26cccd63d26",
   "type" : [
      "Activity"
   ],
   "name" : "request-authorized",
   "summary" : "Request has been authorized",
   "generator" : {
      "name" : "inrupt-verifiable-credentials-postgres",
      "id" : "https://vc.example.com/",
      "wasAssociatedWith" : "ess-verifiable-credentials-55599fc46f-6xtpq",
      "type" : [
         "SoftwareApplication"
      ],
      "qualifiedAssociation" : "54"
   },
   "actor" : [
      {
         "type" : [
            "Agent"
         ],
         "id" : "https://id.example.com/requestingrabbit"
      }
   ],
   "object" : [
      {
         "id" : "https://vc.example.com/derive"
      }
   ],
   "instrument" : [
      {
         "summary" : "Client identifier",
         "id" : "https://myApp.example.com/appids/app.jsonld"
      },
      {
         "name" : "OpenTelemetry Span Context",
         "type" : [
            "SpanContext"
         ],
         "isSampled" : true,
         "spanId" : "1a2e03ba340c1e77",
         "traceId" : "fd121ceb727907e761cf691dcecd5323"
      }
   ],
   "result" : [ ],
   "identifier" : "e14d7c7f65bd461c8063401df3f2b3c3",
   "published" : "2023-12-06T02:08:20.870682246Z"
}
```

### `access-grant-revoked`

```json
{
   "@context" : [
      "https://www.w3.org/ns/activitystreams",
      "https://schema.inrupt.com/audit/v1.jsonld"
   ],
   "id" : "urn:uuid:036ff9f5-3d47-4ebe-aef2-f7d0f2d4c581",
   "type" : [
      "Activity",
      "Update"
   ],
   "name" : "access-grant-revoked",
   "summary" : "Access Grant status has been revoked",
   "generator" : {
      "id" : "https://vc.example.com/",
      "name" : "inrupt-verifiable-credentials-postgres",
      "qualifiedAssociation" : "56",
      "type" : [
         "SoftwareApplication"
      ],
      "wasAssociatedWith" : "ess-verifiable-credentials-55599fc46f-g948f"
   },
   "actor" : [
      {
         "type" : [
            "Agent"
         ],
         "id" : "https://id.example.com/owliverowner"
      }
   ],
   "object" : [
      {
         "type" : [
            "VerifiableCredential",
            "SolidAccessGrant"
         ],
         "id" : "https://vc.example.com/vc/7c337b74-ff0d-4f5b-87e0-50ec51f4a2a9",
         "@context" : [
            "https://www.w3.org/2018/credentials/v1",
            "https://schema.inrupt.com/credentials/v2.jsonld"
         ]
      },
      {
         "type" : [
            "VerifiableCredential",
            "RevocationList2020Credential"
         ],
         "id" : "https://vc.example.com/status/tkoO#0",
         "@context" : [
            "https://www.w3.org/2018/credentials/v1",
            "https://w3id.org/vc-revocation-list-2020/v1"
         ]
      },
      {
         "type" : [
            "Resource"
         ],
         "id" : "https://storage.example.com/cf377182-f514-4900-b54d-71485037fada/shared/recipes/recipe1"
      }
   ],
   "instrument" : [
      {
         "summary" : "Client identifier",
         "id" : "https://myApp.example.com/appids/app.jsonld"
      },
      {
         "name" : "OpenTelemetry Span Context",
         "traceId" : "b67f44beefd05ffc4c447fec52f009e3",
         "spanId" : "ccf9c5b935845597",
         "isSampled" : true,
         "type" : [
            "SpanContext"
         ]
      },
      {
         "type" : [
            "http://www.w3.org/2004/02/skos/core#Concept"
         ],
         "hasStorage" : "https://storage.example.com/cf377182-f514-4900-b54d-71485037fada/",
         "hasDataSubject" : {
            "type" : [
               "https://w3id.org/dpv#DataSubject"
            ],
            "id" : "https://id.example.com/owliverowner"
         }
      }
   ],
   "result" : [
      {
         "type" : [
            "Status"
         ],
         "name" : "Revoked"
      }
   ],
   "identifier" : "48348a98ee3f4a50add60836625c77c2",
   "published" : "2023-12-06T20:31:02.707352307Z"
}
```

And the associated (i.e., **`"identifier" : "48348a98ee3f4a50add60836625c77c2"`**) **`request-authorized`** event:

```json
{
   "@context" : [
      "https://www.w3.org/ns/activitystreams",
      "https://schema.inrupt.com/audit/v1.jsonld"
   ],
   "id" : "urn:uuid:805bb871-2843-472a-b943-e9e3d52eafca",
   "type" : [
      "Activity"
   ],
   "name" : "request-authorized",
   "summary" : "Request has been authorized",
   "generator" : {
      "id" : "https://vc.example.com/",
      "name" : "inrupt-verifiable-credentials-postgres",
      "qualifiedAssociation" : "56",
      "type" : [
         "SoftwareApplication"
      ],
      "wasAssociatedWith" : "ess-verifiable-credentials-55599fc46f-g948f"
   },
   "actor" : [
      {
         "type" : [
            "Agent"
         ],
         "id" : "https://id.example.com/owliverowner"
      }
   ],
   "object" : [
      {
         "id" : "https://vc.example.com/status"
      }
   ],
   "instrument" : [
      {
         "summary" : "Client identifier",
         "id" : "https://myApp.example.com/appids/app.jsonld"
      },
      {
         "name" : "OpenTelemetry Span Context",
         "traceId" : "b67f44beefd05ffc4c447fec52f009e3",
         "spanId" : "ccf9c5b935845597",
         "isSampled" : true,
         "type" : [
            "SpanContext"
         ]
      }
   ],
   "result" : [ ],
   "identifier" : "48348a98ee3f4a50add60836625c77c2",
   "published" : "2023-12-06T20:31:02.568709811Z"
}
```

### `revocation-status-read`

{% hint style="info" %}
**Note**

As part of the Access Grant to UMA access token exchange (which is an **unauthenticated** event), **`revocation-status-read`** events occur. These **`revocation-status-read`** events, which are also *unauthenticated*, do not have an associated **`request-authorized`** event.
{% endhint %}

```json
{
   "@context" : [
      "https://www.w3.org/ns/activitystreams",
      "https://schema.inrupt.com/audit/v1.jsonld"
   ],
   "id" : "urn:uuid:3c54964f-58c8-4def-b4ad-99dcb6557715",
   "type" : [
      "Activity",
      "Read"
   ],
   "name" : "revocation-status-read",
   "summary" : "Revocation status has been read",
   "generator" : {
      "name" : "inrupt-verifiable-credentials-postgres",
      "id" : "https://vc.example.com/",
      "wasAssociatedWith" : "ess-verifiable-credentials-55599fc46f-6xtpq",
      "type" : [
         "SoftwareApplication"
      ],
      "qualifiedAssociation" : "64"
   },
   "actor" : [
      {
         "summary" : "Unauthenticated agent",
         "type" : [
            "Agent"
         ]
      }
   ],
   "object" : [
      {
         "@context" : [
            "https://www.w3.org/2018/credentials/v1",
            "https://w3id.org/vc-revocation-list-2020/v1"
         ],
         "id" : "https://vc.example.com/status/xHeZ",
         "type" : [
            "VerifiableCredential",
            "RevocationList2020Credential"
         ]
      }
   ],
   "instrument" : [
      {
         "spanId" : "f6b251c6ef5be50d",
         "type" : [
            "SpanContext"
         ],
         "isSampled" : true,
         "name" : "OpenTelemetry Span Context",
         "parentId" : "052078377686600b",
         "traceId" : "0706bcbf2fa9898860e44bd2a30305b9"
      }
   ],
   "result" : [ ],
   "identifier" : "f41f866f9c0d4eda842f048235543242",
   "published" : "2023-12-06T02:09:41.739987420Z"
}
```

## Examples: Query

```json
{
   "@context" : [
      "https://www.w3.org/ns/activitystreams",
      "https://schema.inrupt.com/audit/v1.jsonld"
   ],
   "id" : "urn:uuid:e3004da1-c78e-4341-88d2-d9602f965207",
   "type" : [
      "Activity",
      "https://www.w3.org/ns/activitystreams#Read"
   ],
   "name" : "query-succeeded",
   "summary" : "Query succeeded",
   "generator" : {
      "name" : "inrupt-query-fragments-postgres-uma",
      "qualifiedAssociation" : "62",
      "type" : [
         "SoftwareApplication"
      ],
      "wasAssociatedWith" : "ess-fragments-query-bd5d8ffd7-jj98l",
      "id" : "https://fragments.example.com/"
   },
   "actor" : [
      {
         "id" : "https://id.example.com/owliverowner",
         "type" : [
            "Agent"
         ]
      }
   ],
   "object" : [
      {
         "id" : "https://fragments.example.com/qpf?storage=https%3A%2F%2Fstorage.example.com%2F7865026e-5450-44a2-82e5-67c8b28e905d%2F&object=https%3A%2F%2Fschema.org%2FRecipe",
         "type" : [
            "http://rdfs.org/ns/void#Dataset"
         ]
      },
      {
         "id" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/",
         "type" : [
            "Storage"
         ]
      }
   ],
   "instrument" : [
      {
         "id" : "https://myApp.example.com/appids/app.jsonld",
         "summary" : "Client identifier"
      },
      {
         "hasDataSubject" : {
            "id" : "https://id.example.com/owliverowner",
            "type" : [
               "https://w3id.org/dpv#DataSubject"
            ]
         },
         "hasStorage" : "https://storage.example.com/7865026e-5450-44a2-82e5-67c8b28e905d/",
         "type" : [
            "http://www.w3.org/2004/02/skos/core#Concept"
         ]
      }
   ],
   "result" : [ ],
   "identifier" : "e770ac9cf8d24296be30ce176124a07a",
   "published" : "2023-12-06T02:12:42.810948225Z"
}
```


# Security FAQ

## QUESTION 1: Impersonation

### **Can posting a grant request impersonate a user from within the system (or does user/password prevent this)?**

**ANSWER** : Impersonation via a grant request is effectively protected by requiring a valid, securely issued, and properly scoped ID token. However, maintaining the overall security of this mechanism requires best practices for configuration and monitoring. The systems typically implement OAuth 2.0 and OpenID Connect protocols for secure authorization and authentication. Access grants and requests require an ID token, usually a JSON Web Token (JWT), cryptographically signed and issued by the Identity Provider (IDP) after successful authentication.

Key security features include:

* **Multi-factor Authentication (MFA)** for enhanced user verification.
* **Short-lived tokens** with limited expiration times to minimize potential abuse.
* **Scope-limited tokens** issued with minimum necessary permissions.
* **Secure token validation** processes by resource servers.

While these measures significantly reduce impersonation risks, security ultimately depends on the integrity of an IDP. To mitigate risks, best practices should be rigorously followed, including:

* **Strong authentication methods** for the IDP itself.
* **Regular security audits** of the IDP and connected systems.
* **Continuous monitoring** for suspicious activities.
* **Proper key management** with regular rotation.

Thus, impersonation within the W3C Solid and Inrupt ESS systems can be made highly unlikely due to robust security mechanisms.

## QUESTION 2: MitM

### **How does Inrupt’s ESS address the risk of man-in-the-middle (MitM) attacks?**

**ANSWER** : MitM attacks are effectively mitigated through a multi-layered approach combining strong encryption protocols, secure deployment practices, continuous monitoring, and proactive security measures. While these significantly reduce risks, regular security audits and updates remain crucial to maintain a high level of security against evolving threats.

Key measures include:

1. **Network segmentation and secured channels** for all microservices:

* **Mutual TLS (mTLS)**
* **HTTPS**
* **Encrypted Kafka queues** (for data encryption and authentication between services)

2. **Certificate Pinning** to prevent bad certs.
3. **DNSSEC** to protect against spoofing attacks.
4. **API Gateway** : Single entry point to manage and monitor all requests.
5. **SIEM Integration** : Continuous monitoring of security events and anomalies.
6. **Infrastructure Security** :

* Deployment managed through **Infrastructure as Code (IaC)** .
* **SSH access** to running containers prohibited.
* Configuration managed through **version control** with change approval.

7. **Secure Key Management** : Regular key rotation and secure storage.
8. **Content Security Policy (CSP)** : Prevent injection attacks.

**Additional security measures** include:

* **Regular third-party penetration testing** .
* **Ongoing security awareness training** for developers and system administrators.
* A **well-defined incident response plan** .
* **Regular updates and security audits** of open-source components, if used.

Thus, W3C Solid and Inrupt ESS environments can support robust measures to prevent MitM.


# PodSpaces

{% hint style="warning" %}
PodSpaces and PodSpaces AP are currently available as Developer Preview. Do not use for production or for storing sensitive/personal data.
{% endhint %}

Inrupt provides the following hosted versions of its [Enterprise Solid Server (ESS)](https://docs.inrupt.com/ess/latest/introduction) :

* [Inrupt PodSpaces](https://start.inrupt.com/)
* [Inrupt PodSpaces AP (Asia-Pacific)](https://start.ap.inrupt.com) .

## Features

### WebID

{% tabs %}
{% tab title="PodSpaces" %}
WebIDs created through PodSpaces:

* Have the form **`https://id.inrupt.com/{username}`** .
* Uses Inrupt’s OpenID Provider **`https://login.inrupt.com`** .
  {% endtab %}

{% tab title="PodSpaces AP" %}
WebIDs created through PodSpaces AP:

* Have the form **`https://id.inrupt.com/{username}`** .
* Uses Inrupt’s OpenID Provider **`https://login.inrupt.com`** .
  {% endtab %}
  {% endtabs %}

### Pod Storage

{% tabs %}
{% tab title="PodSpaces" %}
Pods on PodSpaces have the URL **`https://storage.inrupt.com/{Pod Identifier}`** where **`{Pod Identifier}`** is autogenerated.
{% endtab %}

{% tab title="PodSpaces AP" %}
Pods on PodSpaces AP have the URL **`https://storage.ap.inrupt.com/{Pod Identifier}`** where **`{Pod Identifier}`** is autogenerated.
{% endtab %}
{% endtabs %}

### Access Control

To manage access to resources stored in its Pods, PodSpaces uses:

* [Access Control Policies (ACP)](/security/authorization/acp)
* [Access Requests and Grants](https://docs.inrupt.com/ess/2.7/services/service-access-grant/)

### Limits

There is a 100MB limit on each Resource stored in the Pod.

## Issues & Help

For non-public feedback or support inquiries, please use the [Inrupt Service Desk](https://inrupt.atlassian.net/servicedesk).


# Getting Started (PodSpaces)

Inrupt’s [PodSpaces](https://start.inrupt.com) is a hosted version of the [Enterprise Solid Server (ESS)](https://docs.inrupt.com/ess/latest/introduction/) . To manage access to resources stored in its Pods, Inrupt’s PodSpaces can use:

* [Access Control Policies (ACP)](/guides/access-control-policies)
* [Access Requests and Grants](/security/authorization/access-requests-grants)

### Sign Up/Create an Account

{% hint style="warning" %}
PodSpaces is currently available as Developer Preview. Do not use for production or storing sensitive/personal data.
{% endhint %}

1. Go to [PodSpaces](https://start.inrupt.com)
2. To create an account, you must agree to the Inrupt’s Terms of Service. To agree, select the checkbox.
3. If you agree to Inrupt’s Terms of Service, click on the <mark style="background-color:blue;">**Sign up**</mark> button.
4. Click on the <mark style="background-color:blue;">**Sign up**</mark> link to create an account with Inrupt’s Identity Provider:
5. Fill in your username, email, and password.

{% hint style="info" %}
Your email address must be unique across both PodSpaces and PodSpaces AP. For example:

* You cannot use the same email to create multiple accounts on PodSpaces.
* You cannot use the same email to create multiple accounts on PodSpaces AP.
* You cannot use the same email to create an account on PodSpaces and an account on PodSpaces AP.
  {% endhint %}

2. Click <mark style="background-color:blue;">**Sign up**</mark>. You are sent a verification email.

{% hint style="warning" %}
If you entered an email address that has been used to create another account (i.e., not unique across both PodSpaces and PodSpaces AP), the verification email will not be sent and you will not be able to use the account.
{% endhint %}

3. Check your email for the verification email. Follow the instructions in the email to verify. Check your spam if you do not see the email in your inbox.
4. Once verified, return to click <mark style="background-color:blue;">**Continue**</mark> to go to the <mark style="background-color:blue;">**Sign in**</mark> page.
5. Enter your username and password, and click <mark style="background-color:blue;">**Sign in**</mark> to your account.\
   The screen displays the access required to continue.
6. To allow and continue, click <mark style="background-color:blue;">**Allow**</mark>.\
   The application displays your WebID and Pod Storage details:
   * WebID: **`https://id.inrupt.com/{username}`**
   * Pod Storage: **`https://storage.inrupt.com/{Pod Identifier}`**

### Additional Information

To get started writing applications for your Pod, see:

* [Java SDK](/sdk/java-sdk)
* [Javascript SDK](/sdk/javascript-sdk)


# Getting Started (PodSpaces AP)

Inrupt’s [PodSpaces AP (Asia-Pacific)](https://start.ap.inrupt.com/) is a hosted version of the [Enterprise Solid Server (ESS)](https://docs.inrupt.com/ess/latest/introduction/) . To manage access to resources stored in its Pods, Inrupt’s PodSpaces can use:

* [Access Control Policies (ACP)](/guides/access-control-policies#access-control-policy-acp)
* [Access Requests and Grants](/security/authorization/access-requests-grants)

### Sign Up/Create an Account

{% hint style="warning" %}
PodSpaces AP is currently available as Developer Preview. Do not use for production or storing sensitive/personal data.
{% endhint %}

1. Go to [PodSpaces AP](https://start.ap.inrupt.com/)
2. To create an account, you must agree to the Inrupt’s Terms of Service. To agree, select the checkbox.
3. If you agree to Inrupt’s Terms of Service, click on the <mark style="background-color:blue;">**Sign up**</mark> button.
4. Click on the <mark style="background-color:blue;">**Sign up**</mark> link to create an account with Inrupt’s Identity Provider:
5. Fill in your username, email, and password.

{% hint style="info" %}
Your email address must be unique across both PodSpaces and PodSpaces AP. For example:

* You cannot use the same email to create multiple accounts on PodSpaces.
* You cannot use the same email to create multiple accounts on PodSpaces AP.
* You cannot use the same email to create an account on PodSpaces and an account on PodSpaces AP.
  {% endhint %}

2. Click <mark style="background-color:blue;">**Sign up**</mark>. You are sent a verification email.

{% hint style="warning" %}
If you entered an email address that has been used to create another account (i.e., not unique across both PodSpaces and PodSpaces AP), the verification email will not be sent and you will not be able to use the account.
{% endhint %}

3. Check your email for the verification email. Follow the instructions in the email to verify. Check your spam if you do not see the email in your inbox.
4. Once verified, return to click <mark style="background-color:blue;">**Continue**</mark> to go to the <mark style="background-color:blue;">**Sign in**</mark> page.
5. Enter your username and password, and click <mark style="background-color:blue;">**Sign in**</mark> to your account.\
   The screen displays the access required to continue.
6. To allow and continue, click <mark style="background-color:blue;">**Allow**</mark>.\
   The application displays your WebID and Pod Storage details:
   * WebID: **`https://id.inrupt.com/{username}`**
   * Pod Storage: **`https://storage.ap.inrupt.com/{Pod Identifier}`**

### Additional Information

To get started writing applications for your Pod, see:

* [Java SDK](/sdk/java-sdk)
* [Javascript SDK](/sdk/javascript-sdk)


# Glossary

For questions around the concepts and terminology specific to Solid, refer to <https://solidproject.org/faqs> .

### Access Control List

An Access Control List (ACL) is a [Resource](#resource) that controls users’ level of access ( **`{ read: <boolean>, append: <boolean>, write: <boolean>, control: <boolean> }`** ) to a Resource. For example, it can list rules like “ **`https://jcaesar.solid.community/profile/card#me`** can read this Resource”, and “ **`https://cleopatra.solid.community/profile/card#me`** can read this Resource and its children”.

### Access Control Policies

One of the access control mechanisms that can be used with Solid. See [Access Control Policy (ACP)](/security/authorization/acp).

### Access Control Resource

Each Pod resource has an associated Access Control Resource (ACR) that contains the policies that determine access to the Pod resource.

![Each Pod resource (Container, RDF resource, non-RDF resource) has an associated Access Control Resource (ACR). ACRs are hosted on the Authorization server.](/files/PbUOCrnXnsrBbRUKzcZf)

The lifecycle of the ACR is bound to the lifecycle of the Pod resource; that is:

* When creating a resource, ESS creates a corresponding ACR.
* When deleting a resource, ESS deletes the corresponding ACR.

If a resource has no Policies that apply to it, the resource is inaccessible. However, the Pod owner can add new policies to provide access to the resource.

### Access Grant

A signed credential that can be used to access Resources stored in a Pod. See [Access Requests and Grants](/security/authorization/access-requests-grants) for more information.

### Access Request

A signed request that is requesting access to one or more Resources stored in a Pod. See [Access Requests and Grants](/security/authorization/access-requests-grants) for more information.

### Agent

A user. Agent typically refers to a person but could also refer to an organization, a bot, etc. An Agent is identified by a WebID.

### Access Modes

The types of access that have been granted: [Read Access](#read-access) , [Append Access](#append-access), [Write Access](#write-access).

### Access Management Application

A trusted application for managing access to data in a Pod. The application allows the user to respond to Access Requests, view granted access and revoke access.

### Append Access

Access to add data to the applicable Resource.

### Client

An application; also referred to as client application. Can be Web-based, server-based, CLI tools, etc.

### Client Identifier

A URI/[IRI](#iri) that uniquely identifies a client application. For Solid, the Client Identifier dereferences to a **`application/ld+json`** document.

For information on WebID-based authentication for client applications, see [The Client ID Document](/guides/identity-in-solid/the-client-id-document).

### Control Access

Access to view and manage who has access to the applicable Resource.

Applicable to the [ACL](#access-control-list) authorization system.

### Container

A special type of Resource that can contain other Containers as well as RDF Resources (SolidDatasets) or Non-RDF Resources. Technically, a Container is itself a SolidDataset.

A Container is analogous to a folder on your file system.

For example, given a Resource at **`https://cleopatra.solid.community/profile/card`** , both **`https://cleopatra.solid.community/profile/`** and **`https://cleopatra.solid.community/`** are Containers.

The URL for a Container ends with a slash `/` .

For more information, see[Structured Data](/reference/rdf/structured-data-rdf-resources).

### Default access

Rules defining [Access Modes](#access-modes) that apply not to the [Container](#Container) Resource directly, but are inherited by its children, their children if applicable, and so forth.

### Enterprise Solid Server (ESS)

Inrupt offers an enterprise-grade, production ready Solid Pod server called the Enterprise Solid Server. ESS' microservices architecture enables simple scaling, high performance, and support for highly available deployment configurations. ESS is part of Inrupt's Wallet Infrastructure.

### ESS Access Token

A signed [JSON Web Token](https://datatracker.ietf.org/doc/html/rfc7519) (JWT) issued by the [Platform Management Service](https://docs.inrupt.com/ess/latest/services/service-platform-management/token-exchange). It is the credential used to access all ESS services. ESS Access Tokens have a default TTL of 5 minutes and are included as a `Bearer` token in the `Authorization` header of requests. See [Authentication](/security/authentication#ess-access-token) for details.

### Extended Profile

An [RDF Resource](#RDF-Resource) , stored in an [Agent’s](#Agent) Pod, that contains data about the Agent. The extended profile is complementary to the [WebID Profile](#WebID-Profile) .

Unlike a [WebID Profile](#WebID-Profile) which, by definition, is publicly readable, an extended profile may or may not be publicly accessible (for reads or writes) based on the Agent’s discretion. That is, the Agent can specify access to the extended profile like any other resource in the Agent’s Pod.

For more information, see <https://solid.github.io/webid-profile/#extended-profile-documents> .

### Fallback ACL

If a [Resource](#Resource) does not have an explicit [Resource ACL](#Resource-ACL) of its own, the Fallback ACL is the [ACL](#ACL) of the [Container](#Container) closest to that Resource that does have its own explicit Resource ACL. Only the [Default access](#Default-access) rules in the Fallback ACL apply.

### IRI

Internationalized Resource Identifier. An IRI is similar to standard web URI but allows for internationalized characters such as the umlaut `Ä` , or the Greek letter `Δ` . See also [IRI Wikipedia page](https://en.wikipedia.org/wiki/Internationalized_Resource_Identifier).

IRI is similar to [URI](#uri) but uses a Universal Coded Character Set, whereas URI is limited to US-ASCII character set.

In the documentation, these terms are used interchangeably.

### ISO-8601

A global standard for representing time values.

### Mutual TLS

A method for mutual authentication in network connections, involving the presentation and verification of digital certificates.

### Non-RDF Resource

Any non-RDF binary or text file, such as **`.pdf`** , **`.jpeg`** , etc.

### Pod

Storage location for personal data. Users manage the access to data stored in their Pods.

### Resource

The data sent to you when you type a URL into a web browser. A resource can be an [RDF Resource](#RDF-Resource) or a [Non-RDF Resource](#Non-RDF-Resource).

### Resource Owner

One or more [agents](#agent) with [control access](#control-access) to resources in a [Pod](#pod).

### Resource ACL

The [ACL](#ACL) that applies to a given [Resource](#Resource). If none exists, the [Fallback ACL](#Fallback-ACL) applies.

### Read Access

Access to view the contents of the applicable [Resource](#Resource) .

### RDF Resource

A [Resource Description Framework (RDF)](https://www.w3.org/TR/rdf11-concepts/) document whose contents consists of statements that describe a some subject by its relationships and have the following form:

```turtle
<subject> <predicate> <object> .
```

For more information, see [RDF](/reference/rdf).

### SolidDataset

Representation of [RDF Resource](#RDF-Resource) as a set of [Things](#Thing) . For more information, see [Structured Data](/reference/rdf/structured-data-rdf-resources).

### Thing

A data entity, e.g., a person. A **`Thing`** is associated with a set of data or properties about the Thing, e.g., **`name`** , **`date of birth`** , **`address`** , etc.

A Thing is saved as part of a [SolidDataset](#SolidDataset) , where a `SolidDataset` is a set of `Things` . For more information, see [Structured Data](/reference/rdf/structured-data-rdf-resources).

### Turtle

An **extension of N-Triples**. In addition to the basic N-Triples syntax, Turtle introduces a number of syntactic shortcuts, such as support for namespace prefixes, lists and shorthands for datatyped literals. Turtle provides a trade-off between ease of writing, ease of parsing and readability. ([Source](https://www.w3.org/TR/rdf11-primer/#section-turtle))

### URI

Uniform Resource Identifier is the official name for those things you see on the Web that begin **`http:`** or **`mailto`**. For example, <http://www.w3.org/> is the URI for the home page of the World Wide Web consortium.

[IRI](#iri) is similar to URI but uses a Universal Coded Character Set, whereas URI is limited to US-ASCII character set.

In the documentation, these terms are used interchangeably.

### Verifiable Credential

Set of claims (i.e., the credential) that can be verified.

See <https://www.w3.org/TR/vc-data-model/#credentials>.

### Verifiable Presentation

Wrapper around one or more [Verifiable Credentials](#verifiable-credential).

Verifiable Presentation can also contains a subset of data from Verifiable Credentials or data synthesized from Verifiable Credentials.

See <https://www.w3.org/TR/vc-data-model/#presentations>.

### Web Access Control

One of the access control mechanisms that can be used with Solid, based on [Access Control Lists](#ACL) . See [Manage Access to Data (WAC)](https://docs.inrupt.com/security/authorization#note) .

### Wallet Storage

The storage mechanism of personal data for a wallet. Inrupt's system uses [Pods](#pods) for storage.

### WebID

A [URI](#uri)/[IRI](#iri) that uniquely identifies an [Agent](#Agent). The [Resource](#Resource) found at the [WebID](#WebID) can provide more information about the Agent.

For more information on WebID URL, see <https://www.w3.org/2005/Incubator/webid/spec/identity/#dfn-webid>.

### WebID Profile

The [RDF Resource](#rdf-resource) obtained when dereferencing the [WebID](#webid) . The WebID Profile may be stored separately from the Agent’s Pod.

For more information, see <https://solid.github.io/webid-profile/>.

### Write Access

Access to add (i.e. [append](#Append-Access)), update, and delete contents of [Resource](#Resource) . Granting Write access automatically grants [Append Access](#Append-Access) .

### Source Container

A Solid [Container](#Container) containing resources that serve as the data source for [View Resources](#view-resource) in a `VIEW_CONTAINER` type [View Binding](#view-binding).

### Source Resource

A Solid [Resource](#Resource) containing the original, unfiltered data that is filtered to create a [View Resource](#view-resource) through a [View Binding](#view-binding).

### View Binding

A configuration that connects a [View Definition](#view-definition) to [Source Resources](#source-resource) and specifies where the filtered [View Resources](#view-resource) are created. Can be type `VIEW_RESOURCE` (single resource) or `VIEW_CONTAINER` (all resources in a container).

### View Container

A Solid [Container](#Container) that holds [View Resources](#view-resource) created from a [Source Container](#source-container) through a `VIEW_CONTAINER` type [View Binding](#view-binding). View Resources maintain the same relative path structure as their Source Resources.

### View Definition

A reusable GraphQL-based configuration stored in the Data Views registry that defines how to filter JSON data. Consists of a GraphQL schema, query, and metadata (name, description, purpose).

### View Resource

A read-only Solid [Resource](#Resource) containing filtered JSON data from a [Source Resource](#source-resource), created by applying a [View Definition](#view-definition) through a [View Binding](#view-binding). View Resources automatically update when their source data changes.


# RDF

## Resource Description Framework

[Resource Description Framework (RDF)](https://www.w3.org/TR/rdf11-concepts/) is a framework for describing a resource, but describing the resource in a way that allows you to integrate with (i.e., “link to”) related data across the Web.

[RDF resource](/reference/glossary#rdf-resource) is a document/file whose contents consist of statements that describe a subject by its relationships and have the following form (also known as a triple):

```turtle
<subject> <predicate> <object> .
```

where:

* **`subject`** is the thing being described and is a URL.
* **`predicate`** is the descriptive property (e.g., name, height, size, etc.) of the thing and is a URL.
* **`object`** is the property value and is either a URL or a literal.

The predicate describes the relationship between the subject and the object. That is,

<figure><img src="/files/trIGvZAkyawmoUbHkVEI" alt=""><figcaption></figcaption></figure>

## Use of URLs

In RDF, the use of URLs allows for disambiguation of the terms:

* URLs provide global uniqueness.
* URLs can be looked up which can provide additional context (such as descriptions and additional information) and thereby may help reduce ambiguity.

For example, consider a Java class with a field named **`"title"`**. The string literal **`"title"`** may refer to a job title, an honorific (e.g., “Dr.”, etc.), a title of a book, etc. Determining which title definition applies depends on the context. The use of URLs can help address this ambiguity. For example:

* **`http://schema.org/title`** refers to a job title, and
* **`http://purl.org/dc/terms/title`** refers to a title of some resource (like a book, a course, etc.)

## Turtle

When expressing RDF triples in [Turtle (Terse RDF Triple Language) format](https://www.w3.org/TR/rdf11-primer/#section-turtle) :

* URLs are enclosed in angle brackets.
* Triple statements end with a period (**`.`**).

For example, the following statements are examples of Turtle:

```turtle
<https://storage.example.com/myRootContainer/expenses/20230306/teamLunchExpense> <https://schema.org/provider> "Example Restaurant" .
<https://storage.example.com/myRootContainer/expenses/20230306/teamLunchExpense>     <https://schema.org/purchaseDate>  "2023-03-07T00:00:00Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> ;
<https://storage.example.com/myRootContainer/expenses/20230306/teamLunchExpense>     <https://schema.org/provider>      "Example Restaurant" ;
<https://storage.example.com/myRootContainer/expenses/20230306/teamLunchExpense>     <https://schema.org/description>   "Team Lunch";
<https://storage.example.com/myRootContainer/expenses/20230306/teamLunchExpense>     <https://schema.org/category>      "Travel and Entertainment" ;
<https://storage.example.com/myRootContainer/expenses/20230306/teamLunchExpense>     <https://schema.org/priceCurrency> "USD" ;
<https://storage.example.com/myRootContainer/expenses/20230306/teamLunchExpense>     <https://schema.org/totalPrice>    "120"^^<http://www.w3.org/2001/XMLSchema#decimal> .
```

In Turtle, for statements with the same subject, you can avoid repeating the subject by combining the statements with a semicolon (**`;`**):

```turtle
<https://storage.example.com/myRootContainer/expenses/20230306/teamLunchExpense>
        <https://schema.org/purchaseDate>  "2023-03-07T00:00:00Z"^^<http://www.w3.org/2001/XMLSchema#dateTime> ;
        <https://schema.org/provider>      "Example Restaurant" ;
        <https://schema.org/description>   "Team Lunch";
        <https://schema.org/category>      "Travel and Entertainment" ;
        <https://schema.org/priceCurrency> "USD" ;
        <https://schema.org/totalPrice>    "120"^^<http://www.w3.org/2001/XMLSchema#decimal> .
```

For object values (can be a literal or a URL), you can append **`^^<URL of the data type>`** to the literal value to avoid ambiguity. For example, the following object value explicitly indicates that the value is a decimal and not a string or an integer literal.

```turtle
"120"^^<http://www.w3.org/2001/XMLSchema#decimal> .
```

## Additional Information

For more information on RDF and Turtle, see:

* [RDF 1.1 Primer: Triples](https://www.w3.org/TR/rdf11-primer/#section-triple).
* [RDF 1.1 Primer: Turtle format](https://www.w3.org/TR/rdf11-primer/#section-turtle).


# Structured Data

## Structured Data: Things, SolidDataset, and Containers

Structured data in this system uses the [Resource Description Framework (RDF)](https://www.w3.org/TR/rdf11-concepts/) format. Data is around entities called [Things](/reference/glossary#thing). A Thing represents any object or concept you want to store information about. For example, if you're storing course information, you might create a Thing for a textbook, with properties like `title` and `author`. The `author` could be another Thing with its own properties.

Things don't exist independently; they must be part of a [SolidDataset](/reference/glossary#soliddataset). A SolidDataset is a collection that holds multiple related Things together.

You can organize SolidDatasets using [Containers](/reference/glossary#container), which work like folders in a file system. Containers can hold SolidDatasets and other resources, including additional Containers nested inside them.

To continue with the course example, your Wallet Storage could have a `Container` named `fall2021/`; `fall2021/` contains another `Container` named `courses/`; `courses/` contains a `SolidDataset` that corresponds to the course `Writing101`; and the `Writing101` contains data about the `Things` (such as the books) for that course.

<figure><img src="/files/HiGbjnKJORTneGqjghLI" alt=""><figcaption></figcaption></figure>

### URL as Identifiers

Every Container, SolidDataset, and Thing has a unique URL that serves as its identifier.

* Container URLs always ends with a forward slash ( `/` ). This slash indicates that the URL points to a Container rather than a specific file (Thing) or dataset (SolidDataset).
* SolidDataset URLs are built from their location within the container hierarchy, followed by the SolidDataset's name.
* Thing URLs are also built from their location within the container hierarchy, followed by the SolidDataset they belong to, with a hash fragment (`#`) added before the name of the Thing itself.

Consider the following content in a Pod where the Pod URL is `https://storage.inrupt.com/{rootContainer}/`:

<figure><img src="/files/HiGbjnKJORTneGqjghLI" alt=""><figcaption></figcaption></figure>

<table><thead><tr><th width="151.5">Name</th><th width="133">Type</th><th>URL</th></tr></thead><tbody><tr><td><code>fall2021/</code></td><td>Container</td><td><code>https://storage.inrupt.com/{rootContainer}/fall2021/</code></td></tr><tr><td><code>courses/</code></td><td>Container</td><td><code>https://storage.inrupt.com/{rootContainer}/fall2021/courses/</code></td></tr><tr><td><code>Writing101</code></td><td>SolidDataset</td><td><code>https://storage.inrupt.com/{rootContainer}/fall2021/courses/Writing101</code></td></tr><tr><td><code>book1</code></td><td>Thing</td><td><code>https://storage.inrupt.com/{rootContainer}/fall2021/courses/Writing101#book1</code></td></tr><tr><td><code>book2</code></td><td>Thing</td><td><code>https://storage.inrupt.com/{rootContainer}/fall2021/courses/Writing101#book2</code></td></tr><tr><td><code>otherThing</code></td><td>Thing</td><td><code>https://storage.inrupt.com/{rootContainer}/fall2021/courses/Writing101#otherThing</code></td></tr></tbody></table>


# Vocabulary

<figure><img src="/files/Xye8Fws7pLSaVEijUw6I" alt=""><figcaption></figcaption></figure>

Vocabularies are collections of identifiers for (generally) related terms.

All data is associated with identifiers. That is, when you read data, you identify the data you want to read. Similarly, when you write data, you identify the data that you are writing.

In the Solid ecosystem, all data identifiers are [IRIs](/reference/glossary#iri):

1. IRIs are globally unique identifiers. Being globally unique prevents name clashes and allows for different interpretations of common concepts.\\

   For example, the concept of a **`Person`** is identified in Schema.org with the identifier **`https://schema.org/Person`**, and Schema.org’s interpretation of the Person concept is described as “A person (alive, dead, undead, or fictional).”.

   \
   The [Person Core Ontology](https://www.w3.org/ns/person), however, identifies the Person concept with the identifier **`https://www.w3.org/ns/person#Person`**, and their interpretation of the Person concept is described as “An individual person who may be dead or alive, but not imaginary.”

   \
   Using IRIs allows for the **unambiguous** differentiation between slightly different interpretations of common concepts, whereas simply using ‘Person’ as the identifier would lead to confusion when attempting to interoperate.
2. IRIs are dereferenceable, i.e., they can be looked up easily, such as by pasting them into the address bar of any browser.

   Providing meaningful descriptive information at an IRI helps with discovering and understanding the data identified by that IRI.

### Pre-existing Vocabularies

Many vocabularies (i.e., collections of terms identified with IRIs) already exist to identify various concepts (e.g., **`Organization`**, **`Person`**) and properties (e.g., **`address`** or **`the starting time of an event`**). The concepts and properties being identified may be general or highly specialized.

When possible, rather than creating your own vocabulary of terms/identifiers, choose from existing ones. This helps promote the use of shared/common terms, and therefore, interoperability.

#### Using Terms from Vocabularies

To define your data entities, you can use terms from any combination of vocabularies. That is, to save data for a person, you could use:

* **`http://schema.org/familyName`** as the identifier for the last name and
* **`http://xmlns.com/foaf/0.1/firstName`** as the identifier for the first name.

However, in practice, you are more likely to use the first and last name terms from the same vocabulary; e.g.,

* **`http://schema.org/familyName`** and **`http://schema.org/givenName`** or
* **`http://xmlns.com/foaf/0.1/lastName`** and **`http://xmlns.com/foaf/0.1/firstName`**.

Nevertheless, as previously mentioned, you can use terms from any combination of vocabularies.

For example, the following code snippet uses the **`solid-client`** function [getStringNoLocale](https://inrupt.github.io/solid-client-js/modules/thing_get.html#getstringnolocale) to return specific data items (identified by their IRI strings) from a data entity **`retrievedPerson`**.

```javascript
// ...

import {
  getStringNoLocale,   
} from "@inrupt/solid-client";

// ...

const lastName = getStringNoLocale(retrievedPerson, "http://schema.org/familyName");
const fname = getStringNoLocale(retrievedPerson, "http://xmlns.com/foaf/0.1/firstName");
```

#### Using Convenience Objects

To simplify the usage of pre-existing vocabularies, Inrupt’s vocabulary libraries provide convenience objects for many (but not all) common terms/identifiers you can use in your data entities:

<table data-header-hidden><thead><tr><th width="186.984375"></th><th></th></tr></thead><tbody><tr><td><a href="https://www.npmjs.com/package/@inrupt/vocab-common-rdf">vocab-common-rdf</a></td><td>For some common RDF-related vocabularies like <a href="http://www.w3.org/2000/01/rdf-schema">RDFS</a>, <a href="http://xmlns.com/foaf/spec/">FOAF</a>, <a href="http://www.w3.org/ns/ldp">LDP</a> or <a href="http://www.w3.org/2002/07/owl">OWL</a>.</td></tr><tr><td><a href="https://www.npmjs.com/package/@inrupt/vocab-solid">vocab-solid</a></td><td>For Solid-related vocabularies like <a href="https://www.w3.org/ns/solid/terms">Solid Terms</a> and <a href="http://www.w3.org/ns/pim/space">Workspace</a>.</td></tr><tr><td><a href="https://www.npmjs.com/package/@inrupt/vocab-inrupt-core">vocab-inrupt-core</a></td><td>For Inrupt specific vocabularies.</td></tr></tbody></table>

Convenience objects contain static constants for common identifiers used across Solid. Importing these classes obviates the need for developers to hard-code these identifiers in their code. Although you can use the IRI strings instead of the convenience objects, these objects represent many of the ideas and concepts that are useful in Solid itself as well as in Solid applications.

The convenience objects include the (IRI) values for each term so you don’t have to remember them or mistype them. The [getStringNoLocale](https://inrupt.github.io/solid-client-js/modules/thing_get.html#getstringnolocale) can accept either (IRI) strings or the convenience objects. As such, the previous example can be rewritten as follows:

```javascript
// ...

import {
  getStringNoLocale,   
} from "@inrupt/solid-client";

import { FOAF, SCHEMA_INRUPT, VCARD } from "@inrupt/vocab-common-rdf";


// ... 

const lastName = getStringNoLocale(retrievedPerson, SCHEMA_INRUPT.familyName);
const fname = getStringNoLocale(retrievedPerson, FOAF.firstName);
const role = getStringNoLocale(retrievedPerson, VCARD.role);
```

* **`FOAF`** provides convenience objects for the [Friend of a Friend Vocabulary](http://xmlns.com/foaf/0.1/). For example, the **`FOAF.firstName`** is a convenience object that includes the **`http://xmlns.com/foaf/0.1/firstName`** IRI.
* **`SCHEMA_INRUPT`** is Inrupt’s extension of the [schema.org Vocabulary](http://schema.org/). It provides convenience objects for a **subset** of terms from the schema.org Vocabulary, adding language tags/translations to labels and comments if missing from schema.org.

  By limiting the number of terms, **`SCHEMA_INRUPT`** aims to make working with select terms from Schema.org easier. Schema.org currently defines over 2,500 terms (see [Organisation of Schema.org](https://schema.org/docs/schemas.html)), whereas most applications (including Solid itself) only require specialized subsets of those terms. SCHEMA\_INRUPT, which consists of a small set of generally applicable terms, reduces noise, clutter and bundle sizes.

  If you require a Schema.org term not in SCHEMA\_INRUPT, you can use the term’s IRI string directly in your own code, create your own extension vocabulary, or request that Inrupt add that term to SCHEMA\_INRUPT.
* **`VCARD`** provides convenience objects for the [vCard Vocabulary](https://www.w3.org/2006/vcard/ns-2006.html). For example, the **`VCARD.role`** is a convenience object that includes the **`http://www.w3.org/2006/vcard/ns#role`** IRI.

<details>

<summary>Convenience Object Naming Scheme</summary>

The convenience object names, in general, match the term names in the original RDF unless the use of the term name results in an illegal variable name for the programming language. For example:

* 1:1 Correspondence

  For the VCARD vocabulary term **`bday`** (IRI **`http://www.w3.org/2006/vcard/ns#bday`**), the corresponding convenience object in the **`vocab-common-rdf`** library is **`VCARD.bday`**.
* Transform Illegal Property Name

  In JavaScript, variable names cannot contain hyphens **`-`**. As such, the Inrupt vocabulary libraries replaces the hyphens with underscores **`_`**. For example, the VCARD vocabulary term **`given_name`** (IRI **`http://www.w3.org/2006/vcard/ns#given-name`**) corresponds to **`VCARD.given_name`**.

If you receive an **`undefined`** error when using the convenience object from one of the libraries:

1. Check the term name.
2. Inrupt’s vocabularies contains many, but not all, the common identifiers. If your term is not included in the library, Inrupt’s libraries provide a namespace helper function **`NS()`** to help with the IRIs.

   That is, if VCARD vocabulary term **`someTermMissingFromLibrary`** with IRI **`http://www.w3.org/2006/vcard/ns#someTermMissingFromLibrary`**, you can use **`VCARD.NS("someTermMissingFromLibrary")`** instead of specifying the full IRI.

</details>

### Interoperability

Consider an example where you are saving your address and a property of the address is a zipcode or a postal code. If you save data for this property as **`zipcode`**, then applications must use **`zipcode`** when accessing this data. If others use **`zip`**, **`postalCode`**, or **`postcode`**, etc. as the identifier when storing their data, then applications that use the **`zipcode`** identifier cannot access their data.

The use of different identifiers for the same data can hinder interoperability. That is, to use an application that retrieves the same data from multiple data sources, the application must be updated to keep track of the various identifiers in order to access this data. Otherwise, the application would not be able to access the data if the data source is not using the expected identifier.

Rather than having to keep track of the varying identifiers across data sources, the use of the same identifier for the same data can help promote interoperability. This idea of coming to broad agreement on common identifiers is perhaps epitomized by Schema.org (from Google, Microsoft and Yahoo!), and is becoming increasingly common in more specialized fields, like biomedicine (e.g. BioPortal <https://bioportal.bioontology.org/ontologies>) and finance (e.g. FIBO <https://spec.edmcouncil.org/fibo/>).

See also:

* [solidproject.org: Well Known Vocabularies](https://solid.github.io/vocab/)

### Vocabularies vs. Data Schemas

Vocabularies provide terms that can be used to identify data. Vocabularies are not data schemas (or in RDF-parlance, [“shapes”](https://www.w3.org/2014/data-shapes/charter)). That is, unlike data schemas (e.g., JSON Schema, relational database schemas (i.e. Data Definition Language (DDL) or XML Schema) which enforce what properties must appear and can appear for a data entity, vocabularies impose no such restrictions.

Consider an example where you are storing data entities that represent people. To describe these data entities, you decide to use the **`http://schema.org/Person`** identifier from Schema.org vocabulary. That is, the data entity has a property **`RDF.type`** set to **`http://schema.org/Person`**.

Identifying the data entities as being of **`RDF.type`** **`http://schema.org/Person`** imposes no conditions about the data properties saved about a person. That is, although **`http://schema.org/Person`** lists properties/identifiers that are categorized/grouped under it, these place no restrictions on how you should or could describe a person; i.e.,

* The properties listed under **`http://schema.org/Person`** can be used to identify non-**`http://schema.org/Person`** data.
* Your data entity does not need to include all the properties under **`http://schema.org/Person`**. In fact, your entity does not need to include any of the properties listed under **`http://schema.org/Person`**. That is, you can identify the Person’s properties with <mark style="color:red;">**non**</mark>-**`http://schema.org/Person`** properties, even from other vocabularies. For example, you could decide that you want to define a person as a data entity with the following properties (from [Semantic Arts gist vocabulary](https://www.semanticarts.com/gist/)) only:
  * **`https://ontologies.semanticarts.com/gist/name`**
  * **`https://ontologies.semanticarts.com/gist/isIdentifiedBy`**
* Someone else may also identify their data entities as **`http://schema.org/Person`** but with completely different properties, e.g.:
  * **`https://schema.org/familyName`**,
  * **`https://schema.org/givenName`**, and
  * **`https://ontologies.semanticarts.com/gist/hasCommunicationAddress`**.

### Shapes

[Shapes](https://www.w3.org/2014/data-shapes/charter) define what properties must and can appear for a data entity; i.e., shapes, not vocabularies, constrain the data.

Similar to using a common vocabulary, using shared Shapes for data also promotes interoperability. For example, consider multiple applications that read and write data entities that represent people.

One application’s expected “shape” of a person includes the following properties:

* an **`RDF.type`** of **`http://schema.org/Person`**
* **`https://schema.org/familyName`**,
* **`https://schema.org/givenName`**,
* **`https://schema.org/email`**, and
* **`https://schema.org/telephone`**.

Another application’s expected “shape” of a person includes the following properties:

* an **`RDF.type`** of **`https://ontologies.semanticarts.com/gist/Person`**
* **`https://ontologies.semanticarts.com/gist/name`**
* **`https://ontologies.semanticarts.com/gist/isIdentifiedBy`**
* **`https://ontologies.semanticarts.com/gist/hasCommunicationAddress`**.

The two applications are not interoperable. That is, they cannot act upon the other’s data. But, if both applications used a common “shape”, which would also result in the use of the same vocabularies, then although developed separately, they would be able to act upon each other’s data.

For additional information on Shapes, see:

* [Shapes](https://www.w3.org/2014/data-shapes/charter)
* [Shapes Constraint Language (SHACL)](https://www.w3.org/TR/shacl/)
* [Shape Expressions (ShEx)](http://shexspec.github.io/primer/)


# Supported Versions


# Enterprise Solid Server

This describes the support phases for versions of ESS. The versions listed here are based on [Inrupt’s version maintenance and support policy](https://www.inrupt.com/maintenance-policy).

{% hint style="info" %}
Any versions not included in these tables are End of Life. New versions will be added to their respective sections when released.
{% endhint %}

| Versions | Released   | Phase        | EOL Date   |
| -------- | ---------- | ------------ | ---------- |
| 3.2.x    | 2026-07-24 | Full Support |            |
| 3.1.x    | 2026-06-29 | Full Support |            |
| 3.0.x    | 2026-05-27 | Full Support |            |
| 2.7.x    | 2026-01-13 | Deprecated   | 2027-01-13 |
| 2.6.x    | 2025-09-30 | Deprecated   | 2026-09-30 |
| 2.5.x    | 2025-06-26 | End of Life  | 2026-06-26 |
| 2.4.x    | 2025-05-13 | End of Life  | 2026-05-13 |
| 2.3.x    | 2024-12-12 | End of Life  | 2025-12-12 |


# Java Client Library

This describes the support phases for versions of Java Client Library. The versions listed here are based on [Inrupt’s version maintenance and support policy](https://www.inrupt.com/maintenance-policy).

{% hint style="info" %}
Any versions not included in these tables are End of Life. New versions will be added to their respective sections when released.
{% endhint %}

| Versions | Released   | Phase        | End of Phase | EOL Since  |
| -------- | ---------- | ------------ | ------------ | ---------- |
| 1.3.x    | 2024-12-13 | Full Support |              |            |
| 1.2.x    | 2024-09-20 | Deprecated   |              |            |
| 1.1.x    | 2023-11-29 | End of Life  |              | 2024-12-13 |
| 1.0.x    | 2023-07-17 | End of Life  |              | 2024-09-20 |


# JavaScript SDKs

This describes the support phases for versions of JavaScript SDKs. The versions listed here are based on [Inrupt’s version maintenance and support policy](https://www.inrupt.com/maintenance-policy).

{% hint style="info" %}
Any versions not included in these tables are End of Life. New versions will be added to their respective sections when released.
{% endhint %}

## Solid Client

| Versions | Released   | Phase        | End of Phase | EOL Since  |
| -------- | ---------- | ------------ | ------------ | ---------- |
| 2.1.x    | 2024-08-27 | Full Support |              |            |
| 2.0.x    | 2023-12-19 | Deprecated   |              |            |
| 1.30.x   | 2023-09-27 | End of Life  |              | 2024-09-27 |

## Solid Client Authn

| Versions | Released   | Phase        | End of Phase | EOL Since  |
| -------- | ---------- | ------------ | ------------ | ---------- |
| 2.5.x    | 2025-05-09 | Full Support |              |            |
| 2.4.x    | 2025-04-15 | Full Support |              |            |
| 2.3.x    | 2024-11-14 | Deprecated   | 2025-11-14   |            |
| 2.2.x    | 2024-05-03 | End of Life  |              | 2025-05-03 |
| 2.1.x    | 2023-03-14 | End of Life  |              | 2025-03-14 |
| 2.0.x    | 2023-12-20 | End of Life  |              | 2024-12-20 |

## Solid Client Access Grants

| Versions | Released   | Phase        | End of Phase | EOL Since  |
| -------- | ---------- | ------------ | ------------ | ---------- |
| 3.3.x    | 2025-04-23 | Full Support |              |            |
| 3.2.x    | 2024-12-26 | Full Support | 2025-06-26   |            |
| 3.1.x    | 2024-09-17 | Deprecated   | 2025-09-17   |            |
| 3.0.x    | 2023-12-22 | End of Life  |              | 2024-12-26 |
| 2.6.x    | 2023-09-18 | End of Life  |              | 2024-09-18 |

## Solid Client Notifications

| Versions | Released   | Phase        | End of Phase | EOL Since  |
| -------- | ---------- | ------------ | ------------ | ---------- |
| 3.0.x    | 2024-09-16 | Full Support |              |            |
| 2.0.x    | 2023-12-20 | Deprecated   |              |            |
| 1.3.x    | 2023-05-19 | End of Life  |              | 2024-09-16 |

## Solid Client VC

| Versions | Released   | Phase        | End of Phase | EOL Since  |
| -------- | ---------- | ------------ | ------------ | ---------- |
| 1.2.x    | 2024-12-17 | Full Support |              |            |
| 1.1.x    | 2024-09-10 | Deprecated   |              |            |
| 1.0.x    | 2023-12-21 | End of Life  |              | 2024-12-21 |

## Solid Client Errors

| Versions | Released   | Phase        | End of Phase | EOL Since |
| -------- | ---------- | ------------ | ------------ | --------- |
| 0.0.x    | 2024-07-08 | Full Support |              |           |


# Applications

This describes the support phases for versions of Inrupt's reference applications. The versions listed here are based on [Inrupt’s version maintenance and support policy](https://www.inrupt.com/maintenance-policy).

{% hint style="info" %}
Any versions not included in these tables are End of Life. New versions will be added to their respective sections when released.
{% endhint %}

## Data Wallet

The [Data Wallet](https://github.com/inrupt/solid-data-wallet) is an application that integrates with the Inrupt Data Wallet service.

| Versions | Released   | Phase       | End of Phase | EOL Since  |
| -------- | ---------- | ----------- | ------------ | ---------- |
| 1.0.x    | 2024-12-17 | End of Life |              | 2026-04-20 |

## Authorization Management Component

Authorization Management Component was a reference application that demonstrated the user management of Access Requests and Grants.

| Versions | Released   | Phase       | End of Phase | EOL Since  |
| -------- | ---------- | ----------- | ------------ | ---------- |
| 2.0.x    | 2023-12-20 | End of Life |              | 2026-02-01 |
| 1.3.x    | 2023-09-17 | End of Life |              | 2026-02-01 |


# ESS 3.2

ESS 3.2 is the current release of Inrupt's Enterprise Solid Server. It introduces the Search Service, an optional service that enables full-text and semantic search over Pod content.

## What's new in 3.2

**Search Service** — Hybrid (keyword + semantic), keyword-only, and semantic-only search over resources stored in Pods. Content is indexed automatically as Pods change — no manual sync required. See [Search Service](/ess/services/service-search) for details.

## Get started

{% content-ref url="/pages/Z9yv6BKnXsJhWxnctGne" %}
[Introduction](/ess/introduction)
{% endcontent-ref %}

{% content-ref url="/pages/lyslDhbKDqfla52MiAW6" %}
[Installation](/ess/installation)
{% endcontent-ref %}

{% content-ref url="/pages/JFaUMngJXo3z9G3u58dB" %}
[ESS Services](/ess/services)
{% endcontent-ref %}

{% content-ref url="/pages/lxV4bHSjZ2R7CogvRPdc" %}
[Administration](/ess/administration)
{% endcontent-ref %}

{% content-ref url="/pages/EqzaCu0cPpevpmaB3G52" %}
[Release Notes](/ess/releases)
{% endcontent-ref %}


# Introduction

The Inrupt Enterprise Solid Server (ESS) is an enterprise-grade data platform built on the [Solid Protocol](https://solidproject.org/TR/protocol). It gives individuals and organizations secure, interoperable storage — called [Pods](https://docs.inrupt.com/reference/glossary#pod) — where data is stored under the owner's control and shared on their terms.

## Storage

Pods are where users store their data. Each Pod is a standard HTTP resource server — clients create, read, update, and delete resources using standard HTTP methods. ESS supports the [Solid Protocol specification](https://solidproject.org/TR/protocol), so any Solid-compliant client can interact with Pod data without vendor-specific APIs.

For more information, see [Pod Storage Service](/ess/services/service-pod-management/service-pod-storage).

## Identity

ESS integrates with your existing enterprise identity infrastructure. Clients authenticate with an external OIDC-compliant Identity Provider (e.g., Okta, Azure AD, Ping Identity) and exchange the IdP's token for an ESS Access Token via the [Platform Management Service](/ess/services/service-platform-management/token-exchange). No proprietary identity broker is required. User provisioning — including account creation, WebID management, and storage allocation — is handled through the Platform Management API.

Each user has a [WebID](https://docs.inrupt.com/reference/glossary#webid) — a URI that uniquely identifies them across the Solid ecosystem. Pods and WebIDs are independent, allowing multiple Pods per identity and flexibility in how identities and storage are provisioned.

For more information, see [Token Exchange](/ess/services/service-platform-management/token-exchange).

## Access Control

ESS provides fine-grained access control through two complementary mechanisms:

* **Access Control Policies (ACP)** — Resource owners set policies that determine who can access their data and what operations are permitted.
* **Access Grants** — A consent-based mechanism where resource owners grant specific access to requestors. Access Grants are W3C Verifiable Credentials, providing a portable, auditable record of consent.

For more information, see [Authorization](https://docs.inrupt.com/security/authorization/) and [Access Requests and Grants](https://docs.inrupt.com/security/authorization/access-requests-grants/).

## Notifications

ESS notifies applications when resources change. The [Notification Delivery Service](/ess/services/service-notification/notification-delivery-service) pushes notifications to remote HTTPS endpoints, enabling event-driven architectures without polling.

For more information, see [Notification Services](/ess/services/service-notification).

## Security

ESS is designed for regulated industries and sensitive data:

* **Authentication** — Native IdP integration with short-lived access tokens (5-minute default TTL)
* **Authorization** — Fine-grained access control policies and consent-based Access Grants
* **Auditing** — Comprehensive audit trail of all data access and operations
* **Encryption** — Data protection in transit and at rest

For more information, see [Security](https://docs.inrupt.com/security/ess-security-faq).

## AI Agent Integration

ESS includes an [MCP Service](/ess/services/service-mcp) that enables AI agents and applications to securely access and manage personal data through the [Model Context Protocol](https://modelcontextprotocol.io/). AI agents operate within the same access control and consent framework as any other client — users grant access through Access Grants and can revoke it at any time.

For more information, see [MCP Service](/ess/services/service-mcp).

## Enterprise Grade

ESS is built for production deployments:

* **Microservices architecture** — Each service scales independently to meet demand
* **High availability** — Support for [highly available deployment configurations](/ess/installation/architecture)
* **Flexible deployment** — Standard enterprise deployment with optional [Solid interoperability services](/ess/services/advanced-configuration) when ESS needs to interoperate with other Solid servers
* **Monitoring** — Built-in [health checks](/ess/administration/health-checks), [metrics](/ess/administration/ess-metrics), and [centralized logging](/ess/administration/logging)
* **Support** — Up to 24/7 support with a commercial license. See [Inrupt Support Center](https://inrupt.atlassian.net/servicedesk/customer/portals).

For more information, see [Installation](/ess/installation) and [Administration](/ess/administration).


# Installation

Inrupt Enterprise Solid Server (ESS) is deployed using [Kustomize](https://github.com/kubernetes-sigs/kustomize) manifests on Kubernetes. Contact your Inrupt representative or Inrupt's [Business Development](https://www.inrupt.com/contact) team to obtain access.

{% hint style="info" %}
**PodSpaces (Developer Preview)** Inrupt provides hosted versions of the Enterprise Solid Server, eliminating the installation overhead. For more information, see [Inrupt PodSpaces](https://docs.inrupt.com/podspaces/)
{% endhint %}

## Prerequisites

Before installing ESS, ensure the following are available:

* **Kubernetes** 1.28 or later — at least three worker nodes with 4 CPU and 8 GB RAM each
* **cert-manager** installed in the cluster
* **PostgreSQL** — one database per ESS service: seven for a standard deployment, or five if you are not deploying the Solid OIDC Broker and WebID Service (see [Advanced Configuration](/ess/services/advanced-configuration)). The Search Service requires one additional database with the `pgvector` extension enabled. The databases can be hosted on a single PostgreSQL instance or on separate instances.
* **Kafka** message broker — three brokers recommended
* **S3-compatible object storage**
* **OpenSearch** — required only if you deploy the [Search Service](/ess/services/service-search)
* **Secrets management** solution (e.g., Vault, AWS Secrets Manager)
* **Cloudsmith registry token** — contact Inrupt to obtain registry credentials

{% hint style="warning" %}
ESS installation requires access to Inrupt's private Cloudsmith registry. Ensure your registry credentials are configured before proceeding.
{% endhint %}

For full operational deployment steps including Kubernetes manifests and Kustomize overlays, see the [ESS Kustomize repository](https://github.com/inrupt-customers/ess-kustomize-releases) (private; requires Inrupt customer access).

## Getting Access

* **Container Registry Entitlement Token** — pull ESS container images from Inrupt's private registry.
* **Terraform Repository** *(optional)* — Terraform blueprints for provisioning ESS infrastructure (IdP, PostgreSQL, Kafka, etc.) on AWS.
* **Kustomize Repository** — deployment manifests, components, and installation documentation. Can build on top of the Terraform-provisioned infrastructure, but Terraform is not required.

## Installation Steps

### 1. Provision Infrastructure *(optional)*

If you are setting up a completely new, standalone deployment and do not have existing infrastructure, the Terraform repository provides blueprints to provision the required infrastructure (Identity Provider, PostgreSQL databases, Kafka, object storage) on AWS. Follow the documentation in the repository to get started. If you are deploying ESS onto existing infrastructure, you can skip this step and provision the required components inline with your existing infrastructure and tooling.

### 2. Deploy ESS

The Kustomize repository contains a quickstart guide and detailed installation documentation. Follow the repository documentation to:

* Fork the repository and create your overlay
* Configure your Identity Provider, database, and secrets
* Deploy to your Kubernetes cluster

## Customization

Once deployed, see [Customize ESS](/ess/installation/customize-configurations) for guidance on customizing your deployment, including:

* [Start App and Approval Pages](/ess/installation/customize-configurations/customization-start-apps)
* [Logging and Auditing](/ess/installation/customize-configurations/customization-logging)
* [Security Customization](/ess/installation/customize-configurations/customization-security)


# Customize ESS

You can customize your ESS deployment using [Kustomize](https://github.com/kubernetes-sigs/kustomize) [overlays](https://kubernetes.io/docs/tasks/manage-kubernetes-objects/kustomization/#bases-and-overlays).

Inrupt provides access to a Git repository containing the Kustomize manifests for ESS. The repository is structured so that you fork it, create your own overlays, and pull upstream tags to upgrade.

## Repository Structure

The repository Inrupt provides has the following structure:

```
ess-kustomize-releases/
  base/                        # Service deployment manifests (managed by Inrupt)
  components/                  # Optional feature components (audit, security, scaling, etc.)
  generated/images/            # Image digest references
  overlays/examples/           # Example overlay configurations
  docs/                        # Quickstart and upgrade guides
  VERSION                      # Release metadata
  CHANGELOG.md                 # Release notes
```

* **`base/`** contains the Kubernetes manifests for each ESS service. These are managed by Inrupt and should not be modified directly.
* **`components/`** contains optional Kustomize components that you can include in your overlay to enable features such as audit sinks, Kafka encryption, HPA autoscaling, pod security standards, and more.
* **`overlays/examples/`** contains example overlay configurations that you can copy and customize for your environment.

## Creating Your Overlay

To customize ESS, create your own overlay by copying an example:

```bash
cp -r overlays/examples/production overlays/my-env
```

Your overlay's `kustomization.yaml` references the base service manifests and includes the components you need. For example:

```yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: ess

# Reference the base service manifests
resources:
  - ../../../base/ess-pod-storage
  - ../../../base/ess-pod-provision
  - ../../../base/authorization/authorization-acp
  - ../../../base/ess-openid
  - ../../../base/ess-webid
  - ../../../base/ess-start
  - ../../../base/ess-notification
  - ../../../base/platform/ess-platform-management
  - ../../../base/platform/ess-purger-service
  - ../../../base/ess-vc-issuer
  # ... additional services as needed

# Include optional components
components:
  - ../../../components/kafka-clients-sasl
  - ../../../components/hpa-autoscaling
  - ../../../components/secure-container-security-context
  # Image versions - MUST BE LAST
  - ../../../generated/images

# Environment-specific patches
patches:
  - path: my-patches.yaml
```

## Applying Your Customizations

{% hint style="info" %}
**Note**\
The installation and customization tutorials assume Infrastructure as Code (**`IaC`**) practice for managing the system and assumes the installation directory is under source control.
{% endhint %}

{% hint style="danger" %}
**Warning**

**CRITICAL SECURITY REQUIREMENT**

**NEVER commit files containing secrets such as** **`.env`** **or** **`JWT`** **to version control.** These files must be managed securely.

As part of updating the inputs for your deployment:

1. **Review** the template secret files
2. **Set strong secrets** for the values, such as strong passwords
3. **Store the secret securely** outside your repository using one of these methods:
   * Cloud secrets management service
   * Enterprise secrets vault solution
   * Kubernetes Secrets with encryption at rest
   * Secure file system with restricted access (development only)
4. **Configure your deployment** to retrieve credentials from your secure storage at runtime
5. **Add the secrets files to your** **`.gitignore`** **file immediately**
   {% endhint %}

Apply your overlay directly to the cluster:

```bash
kubectl apply -k overlays/my-env
```

{% hint style="info" %}
To preview changes before applying, run `kustomize build overlays/my-env` or `kubectl diff -k overlays/my-env`. Consider using a GitOps tool such as ArgoCD or Flux to automate deployments from your forked repository.
{% endhint %}

### Adding a Custom Patch

To customize a specific service, add a patch to the `patches` section of your overlay's `kustomization.yaml`. For example, to add a custom label to all resources:

1. Create an overlay file named **`labels.yaml`** in your overlay directory:

   ```yaml
   # labels.yaml
   apiVersion: builtin
   kind: LabelTransformer
   metadata:
     name: author
   labels:
     author: me
   fieldSpecs:
     - path: metadata/labels
       create: true
   ```
2. Reference it in your **`kustomization.yaml`**:

   ```yaml
   transformers:
     - labels.yaml
   ```

To target a specific service deployment, use the `target` field:

```yaml
patches:
  - target:
      kind: Deployment
      name: ess-pod-storage
    patch: |-
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: ess-pod-storage
      spec:
        replicas: 4
```

{% hint style="warning" %}
Ensure that your forked repository is **private**.
{% endhint %}

## Examples

The pages in this section contain examples for customizing your ESS deployment.

### Start App and Approval Pages

* [Use a Custom Start Application](/ess/installation/customize-configurations/customization-start-apps/use-custom-start-app)
* [Use a Custom Approval Template](/ess/installation/customize-configurations/customization-start-apps/customize-approval)

### Security

* [Set Authorization Client Allow List](/ess/installation/customize-configurations/customization-security/modify-authz-client-list)
* [Set Initial Pod Clients Allow List](/ess/installation/customize-configurations/customization-security/modify-pod-client-list)
* [Manage Token Issuer Allow/Deny Lists](/ess/installation/customize-configurations/customization-security/manage-identity-providers)
* [Use Official Certificate Authority](/ess/installation/customize-configurations/customization-security/use-production-lets-encrypt)
* [Add Custom Certificates to ESS Services](/ess/installation/customize-configurations/customization-security/add-custom-certs)

## Logging and Auditing

* [Use Non-JSON Formatted Logging](/ess/installation/customize-configurations/customization-logging/modify-log-format)
* [Update Log Level](/ess/installation/customize-configurations/customization-logging/modify-log-level)
* [Manage Auditing](/ess/installation/customize-configurations/customization-logging/manage-auditing)

## Pod Maintenance and Metrics

* [Modify Prune Configuration](/ess/installation/customize-configurations/customization-pod-maintenance/modify-prune)
* [Modify Storage Metrics Schedule](/ess/installation/customize-configurations/customization-pod-maintenance/modify-storage-metrics)

## General

* [Scale a Deployment Using Replicas](/ess/installation/customize-configurations/general/scale-a-deployment-using-replicas)
* [Use an External Service](/ess/installation/customize-configurations/general/use-an-external-service)
* [Remove Overlay Content](/ess/installation/customize-configurations/general/use-an-external-service)
* [Pin a Version](/ess/installation/customize-configurations/general/pin-a-version)

## Design Considerations

When designing your customizations, be aware that new features and services will arrive in updates to ESS. As such, consider the following when customizing:

1. **Be selective.**\
   Try to focus the customization on the specific objects you want to change. For example, specify the deployment name when scaling to 20 replicas.
2. **Use labels to select things by their purpose.**\
   A number of parts of the deployment have labels such as **`role:logging`** to help you choose things to customize.
3. **Use `merge` and `replace` behaviors to control what you consume.**\
   You can choose to extend an existing object, such as a **`ConfigMap`**, using **`merge`**. If you want to fully replace the original content, you can use **`replace`**.
4. **Use namespaces to separate distinct workloads**\
   For instance, you may be adding logging or certificate management. Consider putting those in other namespaces if they are cluster-wide and serve other workloads, not just ESS.\
   However, if you are adding a new web server that will work in tandem with ESS, then using the same namespace as ESS may be preferable.

## Additional Information

For more information on Kustomize, see [Declarative Management of Kubernetes Objects Using Kustomize](https://kubernetes.io/docs/tasks/manage-kubernetes-objects/kustomization/#overview-of-kustomize) .

* [Start App and Approval Pages](/ess/installation/customize-configurations/customization-start-apps)
* [Security](https://docs.inrupt.com/security/)
* [Logging and Auditing](/ess/installation/customize-configurations/customization-logging)
* [Pod Maintenance and Metrics](/ess/installation/customize-configurations/customization-pod-maintenance)
* [General](/ess/installation/customize-configurations/general)


# Start App and Approval Pages


# Use a Custom Start Application

ESS includes a default start application that allows users to sign up or login with the Identity Provider. You can replace the default start application with a [custom start application](/ess/services/advanced-configuration/service-start#ess-start-application). The [Platform Management service](/ess/services/service-platform-management/platform-management-api) provides the API for user provisioning (accounts, WebIDs, and storage).

To use a custom start application, update the following configuration with your application’s [Solid-OIDC Client ID](https://docs.inrupt.com/security/authentication#client-identifier-client-id) (e.g., **`https://myStart.example.com/appid/id`** ):

* [**`QUARKUS_OIDC_CLIENT_ID`**](/ess/services/advanced-configuration/service-start#quarkus_oidc_client_id) configuration for the Start service and
* [**`INRUPT_START_CLIENT_ID`**](/ess/services/advanced-configuration/service-webid#inrupt_start_client_id) and [**`INRUPT_WEBID_ALLOWED_CLIENT_IDS`**](/ess/services/advanced-configuration/service-webid#inrupt_webid_allowed_client_ids) configuration for the WebID service.

### Example Customization

The following example assumes a custom start application with the [Solid-OIDC Client ID](https://docs.inrupt.com/security/authentication#client-identifier-client-id) value **`https://myStart.example.com/appid/id`** . To use this application, instead of the default start app, update the following configuration options:

* For the Start service:
  * [**`QUARKUS_OIDC_CLIENT_ID`**](/ess/services/advanced-configuration/service-start#quarkus_oidc_client_id)
* For the WebID service:
  * [**`INRUPT_START_CLIENT_ID`**](/ess/services/advanced-configuration/service-webid#inrupt_start_client_id)
  * [**`INRUPT_WEBID_ALLOWED_CLIENT_IDS`**](/ess/services/advanced-configuration/service-webid#inrupt_webid_allowed_client_ids)

1. Go to your ESS installation directory:

   ```sh
   cd ${HOME}/ess
   ```
2. Create a **`start-app.yaml`** file with the following content:

   ```yaml
   apiVersion: apps/v1
   kind: Deployment
   metadata:
     name: ess-start
   spec:
     template:
       spec:
         containers:
         - env:
           - name: QUARKUS_OIDC_CLIENT_ID
             value: https://mystart.example.com/appid/id
           name: ess-start
   ```
3. Create a **`webid-service-start-app-conf.yaml`** file with the following content:

   ```yaml
   apiVersion: apps/v1
   kind: Deployment
   metadata:
     name: ess-webid
   spec:
     template:
       spec:
         containers:
         - env:
           - name: INRUPT_START_CLIENT_ID
             value: https://mystart.example.com/appid/id
           - name: INRUPT_WEBID_ALLOWED_CLIENT_IDS
             value: $(INRUPT_WEBID_CLIENT_ID),$(INRUPT_START_CLIENT_ID)
           name: ess-webid
   ```
4. Modify the **`kustomization.yaml`** (i.e., step 3 of the [Applying Your Customizations](/ess/installation/customize-configurations) procedure) to use **`start-app.yaml`** and **`webid-service-start-app-conf.yaml`**.\
   \
   Specifically, add the highlighted content to the **`kustomization.yaml`** file to the **`patches`** section:

{% hint style="info" %}
**Tip**\
If the **`patches`** key does not exist in **`kustomization.yaml`** , add the **`patches`** key as well.
{% endhint %}

<pre class="language-yaml"><code class="lang-yaml"># kustomization.yaml in your ESS installation directory

# ...  Preceding content omitted for brevity 
# ...

patches:
<strong>  - path: start-app.yaml
</strong><strong>  - path: webid-service-start-app-conf.yaml
</strong></code></pre>

5. Continue with the rest of the [Applying Your Customizations](/ess/installation/customize-configurations) procedure.


# Use a Custom Approval Template

During the login flow, ESS’ [Solid OIDC Broker Service](/ess/services/advanced-configuration/service-oidc) displays an approval page so that an application can receive the access/ID tokens.

For example, the following image shows the default approval page for the **My Example App**:

<figure><img src="/files/uO8yRl5MfMkD3V2jvA08" alt=""><figcaption></figcaption></figure>

## Qute Template

ESS’ [Solid OIDC Broker Service](/ess/services/advanced-configuration/service-oidc) uses the [Qute Templating Engine](https://quarkus.io/guides/qute-reference) to generate the approval page from a template. The template can use the following expressions for the client metadata and the requested scopes:

| **`{client.clientName}`** | The name of the application.               |
| ------------------------- | ------------------------------------------ |
| **`{client.clientId}`**   | The client identifier for the application. |
| **`{client.logoUri}`**    | The client logo.                           |
| **`{client.policyUri}`**  | The client privacy policy.                 |
| **`{client.tosUri}`**     | The client Terms of Service.               |
| **`{scopes}`**            | The request scopes.                        |

### Default Template

The default approval template:

* Clarifies the description of the requested scopes; i.e., the requested permissions;
* No longer displays the Inrupt logo on the top left;
* No longer displays an **About** section (which included links to the client’s privacy policy and terms of service, if set).

### Using a Custom Template

Instead of the default template, you can use your own template. To use your own template:

1. Add the new template file (and associated files such as a **`*.css`** file) to the [Solid OIDC Broker Service](/ess/services/advanced-configuration/service-oidc) container.
2. Update the [**`INRUPT_OPENID_APPROVAL_TEMPLATE_LOCATION`**](/ess/services/advanced-configuration/service-oidc#inrupt_openid_approval_template_location) property.

## Example Customization

The following configuration adds the new template (and associated files) to the [Solid OIDC Broker Service](/ess/services/advanced-configuration/service-oidc) container and updates the [**`INRUPT_OPENID_APPROVAL_TEMPLATE_LOCATION`**](/ess/services/advanced-configuration/service-oidc#inrupt_openid_approval_template_location) property to the path of the file in the container.

1. Go to your ESS installation directory:

   ```sh
   cd ${HOME}/ess
   ```
2. Create a directory with your template files and configuration.
   1. Create a new directory **`custom-approval/`** under the directory and switch to the new directory:

      ```
      mkdir custom-approval/ && cd custom-approval/
      ```
   2. In the directory, add your new template and any other files used in the template, such as stylesheet and/or images. In the example, assume the new template is named **`approval.html`** and uses **`styles.css`** and **`logo.svg`** . Add the three files to the **`custom-approval/`** directory.\
      3\. In the directory, create a **`kustomization.yaml`** with the following content:

      ```yaml
      ### custom-approval/kustomization.yaml
      apiVersion: kustomize.config.k8s.io/v1alpha1
      kind: Component
      ## Add the files to the configMapGenerator
      configMapGenerator:
        - name: ess-openid-custom-approval-files-configmap
          namespace: ess
          files:
            - ./approval.html
            - ./styles.css
            - ./logo.svg
      patches:
        ## For ess-openid:
        ## - create a Docker volume using the configMap 
        ## - mount the volume, specifying a path in the container 
        - target:
            kind: Deployment
            name: ess-openid
          patch: |-
            apiVersion: apps/v1
            kind: Deployment
            metadata:
              name: not-important
            spec:
              template:
                spec:
                  volumes:
                    - name: ess-openid-custom-approval-volume
                      configMap:
                        name: ess-openid-custom-approval-files-configmap
                  containers:
                    - name: ess-openid
                      volumeMounts:
                        - mountPath: /deployments/custom/approval/
                          name: ess-openid-custom-approval-volume
        ## For ess-openid:
        ## - Update the INRUPT_OPENID_APPROVAL_TEMPLATE_LOCATION to 
        ##   the mounted volume path + template html page
        - target:
            kind: Deployment
            name: ess-openid
          patch: |
            - op: add
              path: /spec/template/spec/containers/0/env/-
              value:
                name: INRUPT_OPENID_APPROVAL_TEMPLATE_LOCATION
                value: /deployments/custom/approval/approval.html
      ```

      \
      The customization specified in the file:

      1. Adds the template and associated files to a configMapGenerator named **`ess-openid-custom-approval-files-configMap`** .
      2. Creates a volume **`ess-openid-custom-approval-volume`** using the **`ess-openid-custom-approval-files-configMap`** configMap.
      3. Mounts the volume at the specified mount path. The mount path is the directory inside the container where you want to access the volume. This example uses **`/deployments/custom/approval/`** as the mount path. If you choose to use a different path, ensure you also update the [**`INRUPT_OPENID_APPROVAL_TEMPLATE_LOCATION`**](/ess/services/advanced-configuration/service-oidc#inrupt_openid_approval_template_location)configuration as well.
      4. Sets the [**`INRUPT_OPENID_APPROVAL_TEMPLATE_LOCATION`**](/ess/services/advanced-configuration/service-oidc#inrupt_openid_approval_template_location) to the template file in the mounted path. In this example, **`/deployments/custom/approval/approval.html`** .

3\. Go back to your ESS installation directory:

```sh
cd ${HOME}/ess
```

4\. Modify the **`kustomization.yaml`** (i.e., step 3 of the [Applying Your Customizations](/ess/installation/customize-configurations) procedure). Specifically, add the highlighted content (the **`custom-approval/`** component) to the **`kustomization.yaml`** file

<pre class="language-yaml"><code class="lang-yaml"># kustomization.yaml in your ESS installation directory
# ...  Preceding content omitted for brevity 
# ...
components:
  // ... Preceding contents of components omitted for brevity
<strong>  - custom-approval/
</strong></code></pre>

5\. Continue with the rest of the [Applying Your Customizations](/ess/installation/customize-configurations) procedure.

For additional configuration properties for the ESS Broker, see [Solid OIDC Broker Service](/ess/services/advanced-configuration/service-oidc).


# Logging and Auditing


# Use Non-JSON Formatted Logging

ESS services uses JSON as the default log message format.

{% hint style="info" %}
ESS services refer specifically to ESS code and not its dependencies. Dependencies (such as Kafka) use their own logging libraries and as such may use another format and have different fields.
{% endhint %}

Although JSON formatting allows the log messages to be enriched with details that non-JSON formatting does not allow, you can change the log format to non-JSON messages.

## Example Customization

To change log messages to non-JSON formatting, you can use the **`disable-json-logging-for-quarkus`** component.

{% hint style="info" %}
Note\
Non-JSON formatted messages do not contain all the details that JSON messages contain.
{% endhint %}

1. Go to your ESS installation directory:

   ```sh
   cd ${HOME}/ess
   ```
2. Modify the **`kustomization.yaml`** (i.e., step 3 of the [Applying Your Customizations](/ess/installation#applying-your-customizations) procedure).\
   Specifically, add the highlighted content to the **`components`** field in the **`kustomization.yaml`** file:

   <pre class="language-yaml"><code class="lang-yaml">
    # kustomization.yaml in your ESS installation directory
    # ...  Preceding content omitted for brevity 
    # ...
    components:
   <strong>   - ../release/ess/deployment/kubernetes/components/disable-json-logging-for-quarkus
   </strong><strong> 
   </strong> 
   </code></pre>
3. Continue with the rest of the [Applying Your Customizations](/ess/installation#applying-your-customizations) procedure.

{% hint style="info" %}
**Tip**\
To return to JSON-formatted logging, remove (or revert) the above changes.
{% endhint %}


# Update Log Level

ESS services support a subset of log levels supported by Quarkus:

* **`FATAL`**
* **`ERROR`**
* **`WARN`**
* **`INFO`** (Default level)
* **`DEBUG`**

By default, ESS’ log level is configured to **`INFO`** level:

* This outputs logs with severity level **`INFO`** and higher (i.e., outputs **`INFO`** , **`WARN`** , **`ERROR`** and **`FATAL`** levels).
* This excludes logs with security level below **`INFO`** (i.e., excludes **`DEBUG`** ).

As part of your infrastructure-as-code deployment, you may wish to control log levels of the deployments using a customization.

For example, when debugging an issue you may temporarily enable **`DEBUG`** level logs to get more granular information on system behavior.

To change a service’s log level, you can create an overlay to update the **`QUARKUS_LOG_LEVEL`** environment variable.

{% hint style="info" %}
ESS also supports changing the level of log messages using redaction. See [Logging Redaction](/ess/administration/logging/logging-redaction) for details.
{% endhint %}

## Example Debug Logging Customization File

You can use the following procedure to enable **`DEBUG`** level logging for **`pod-provisioning`** :

1. Go to your ESS installation directory:

   ```sh
   cd ${HOME}/ess
   ```
2. Modify the **`kustomization.yaml`** (i.e., step 3 of the [Applying Your Customizations](/ess/installation#applying-your-customizations) procedure).\
   Specifically, add the highlighted content to the **`kustomization.yaml`** file under the **`patches`** key:

{% hint style="info" %}
**Tip** If **`patches`** list does not exist in **`kustomization.yaml`** , add the key **`patches`** as well.
{% endhint %}

```yaml
# kustomization.yaml in your ESS installation directory
# ...  Preceding content omitted for brevity 
# ...
patches:
  - target:
      kind: Deployment
      name: ess-pod-provision
    patch: |-
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: ess-pod-provision
      spec:
        template:
          spec:
            containers:
              - env:
                - name: QUARKUS_LOG_LEVEL
                  value: DEBUG
                name: ess-pod-provision
```

3. Continue with the rest of the [Applying Your Customizations](/ess/installation#applying-your-customizations) procedure.

{% hint style="info" %}
**Tip**\
Remember to reset the log level when you’re finished debugging.
{% endhint %}


# Manage Auditing

Inrupt provides overlays for enabling and disabling [Auditing](https://docs.inrupt.com/security/auditing/).

## Change Auditing Destination

The ESS [Auditing service](/ess/services/service-auditing) can log to:

* **`sysout`** (default)
* Syslog
* [Microsoft Sentinel](https://azure.microsoft.com/en-us/services/microsoft-sentinel/#overview).

By default, the [Auditing](https://docs.inrupt.com/security/auditing/) sends audit events to **`sysout`**. To change destination, you can use the following steps:

{% tabs %}
{% tab title="Microsoft Sentinel" %}

1. Go to your ESS installation directory:

```sh
cd ${HOME}/ess
```

2\. Create a directory with your Syslog kustomization and configuration.\
a. Create a new directory **`audit-use-syslog/`** under your installation directory and switch to the new directory:

```sh
mkdir audit-use-syslog/ && cd audit-use-syslog/
```

b. Create a **`kustomization.yaml`** with the following content:

```yaml
---
apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Component

images:
  - name: docker.software.inrupt.com/inrupt-audit-logger
    newName: docker.software.inrupt.com/inrupt-audit-syslog
```

c. Create a **`sentinel-credentials.env`** to configure for integrating with Sentinel and update with your Sentinel values. See [Auditing Service: Sentinel Configuration](/ess/services/service-auditing#auditing-service-sentinel-configuration) for more information on the configuration options.

```yaml
# Update with your SENTINEL values
**`QUARKUS_REST_CLIENT_SENTINEL_API_URL`**=
**`INRUPT_AUDIT_SENTINEL_API_VERSION`**=
**`INRUPT_AUDIT_SENTINEL_SHARED_KEY`**=
**`INRUPT_AUDIT_SENTINEL_WORKSPACE_ID`**=
```

3. Go back to your ESS installation directory:

   ```sh
   cd ${HOME}/ess
   ```
4. Modify the **`kustomization.yaml`** (i.e., step 3 of the [Applying Your Customizations](/ess/installation#applying-your-customizations) procedure). Specifically, in the **`kustomization.yaml`** file, add the highlighted content to the **`component`** section:

   <pre class="language-yaml"><code class="lang-yaml"># kustomization.yaml in your ESS installation directory

   # ...  Preceding content omitted for brevity 
   # ...

   components:
     // ... Preceding contents of components omitted for brevity
   <strong>  - audit-use-sentinel/
   </strong></code></pre>
5. Continue with the rest of the [Applying Your Customizations](/ess/installation#applying-your-customizations) procedure.
   {% endtab %}

{% tab title="Syslog" %}

1. Go to your ESS installation directory:

```sh
cd ${HOME}/ess
```

2\. Create a directory with your Sentinel kustomization and configuration.\
a. Create a new directory **`audit-use-sentinel/`** under your installation directory and switch to the new directory:

```sh
mkdir audit-use-sentinel/ && cd audit-use-sentinel/
```

b. Create a **`kustomization.yaml`** with the following content:

```yaml
---
apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Component
secretGenerator:
  - name: audit-credentials
    behavior: create
    envs:
      - **`sentinel-credentials.env`**
images:
  - name: docker.software.inrupt.com/inrupt-audit-logger
    newName: docker.software.inrupt.com/inrupt-audit-sentinel
```

See also [Auditing Service: Syslog Configuration](/ess/services/service-auditing#auditing-service-syslog-configuration) for more information on the Syslog configuration options.

3. Go back to your ESS installation directory:

   ```sh
   cd ${HOME}/ess
   ```
4. Modify the **`kustomization.yaml`** (i.e., step 3 of the [Applying Your Customizations](/ess/installation#applying-your-customizations) procedure). Specifically, in the **`kustomization.yaml`** file, add the highlighted content to the **`component`** section:

   <pre class="language-yaml"><code class="lang-yaml"># kustomization.yaml in your ESS installation directory

   # ...  Preceding content omitted for brevity 
   # ...

   components:
     // ... Preceding contents of components omitted for brevity
   <strong>  - audit-use-syslog/
   </strong></code></pre>
5. Continue with the rest of the [Applying Your Customizations](/ess/installation#applying-your-customizations) procedure.
   {% endtab %}
   {% endtabs %}

{% hint style="info" %}
**Tip**\
By default, the Auditing service outputs to **`sysout`** . If you have changed the destination from the default **`sysout`** and would like to return to **`sysout`**, remove (or revert) the above changes for integrating the service with Syslog or Sentinel.
{% endhint %}

### Disable Auditing

By default, the [Auditing](https://docs.inrupt.com/security/auditing/) is enabled. To disable auditing, you can use the following steps:

{% hint style="info" %}
**Note**\
Disabling auditing stops the ESS services from publishing audit events; it does not stop the [Auditing service](/ess/services/service-auditing) . [Auditing service](/ess/services/service-auditing) continues to run even when auditing is disabled.
{% endhint %}

1. Go to your ESS installation directory:

   ```sh
   cd ${HOME}/ess
   ```
2. Modify the **`kustomization.yaml`** (i.e., step 3 of the [Applying Your Customizations](/ess/installation#applying-your-customizations) procedure).\
   Specifically, in the **`kustomization.yaml`** file, add the highlighted content to the **`component`** section:

   <pre class="language-yaml"><code class="lang-yaml">
    # kustomization.yaml in your ESS installation directory
    # ...  Preceding content omitted for brevity 
    # ...
    components:
      // ... Preceding contents of components omitted for brevity
   <strong>   - ../release/ess/deployment/kubernetes/components/audit/audit-off/
   </strong><strong> 
   </strong> 
   </code></pre>
3. Continue with the rest of the [Applying Your Customizations](/ess/installation#applying-your-customizations) procedure.

{% hint style="info" %}
**Tip**\
To re-enable the Auditing service, remove (or revert) the above changes to disable the Auditing service.
{% endhint %}

## Enable Resource Read Auditing

ESS supports auditing of *successful* [read resource operations](/ess/services/service-auditing#audit-events) (i.e., **`GET`** and **`HEAD`** operations on resources).

This feature is disabled by default. To enable, set [**`INRUPT_STORAGE_AUDIT_RESOURCE_READ_ENABLED`**](/ess/services/service-pod-management/service-pod-storage#inrupt_storage_audit_resource_read_enabled) to **`true`** .

{% hint style="warning" %}
**Important**\
When auditing of read operations is enabled, the total number of Audit events may increase substantially. Before enabling read operations auditing, consider allocating more compute and network resources to ESS.
{% endhint %}

1. Go to your ESS installation directory:

   ```sh
   cd ${HOME}/ess
   ```
2. Modify the **`kustomization.yaml`** (i.e., step 3 of the [Applying Your Customizations](/ess/installation#applying-your-customizations) procedure).\
   Specifically, in the **`kustomization.yaml`** file, add the highlighted content to the **`patches`** section:

{% hint style="info" %}
**Tip**

If the **`patches`** key does not exist in **`kustomization.yaml`** , add the **`patches`** key as well.
{% endhint %}

<pre class="language-yaml"><code class="lang-yaml"># kustomization.yaml in your ESS installation directory

# ...  Preceding content omitted for brevity 
# ...


patches:
<strong>  - target:
</strong><strong>      kind: Deployment
</strong><strong>      name: ess-pod-storage
</strong><strong>      namespace: ess
</strong><strong>    patch: |
</strong><strong>      - op: add
</strong><strong>        path: /spec/template/spec/containers/0/env/-
</strong><strong>        value:
</strong><strong>          name: INRUPT_STORAGE_AUDIT_RESOURCE_READ_ENABLED
</strong><strong>          value: "true"
</strong></code></pre>

3. Continue with the rest of the [Applying Your Customizations](/ess/installation#applying-your-customizations) procedure.


# Manage Application-Defined Metadata Propagation

ESS adds support for [application-defined metadata/properties](/ess/administration/application-defined-metadata) ; specifically, ESS adds support for [baggage HTTP header](https://www.w3.org/TR/baggage/) . These application-defined properties can be included in audit messages and log messages as well as returned as response headers. ESS further expands on this support by providing configuration to add non-baggage request headers to the baggage for propagation within its system.

{% hint style="info" %}
The client requests do not need to include a baggage header. If clients send only non-baggage request headers for application-defined properties (as determined by ESS configuration), ESS creates a baggage for propagation within its system. However, if a baggage request header exists, ESS adds the non-baggage requests headers (if any, as determined by ESS configuration) to the baggage, and propagates the enhanced baggage.
{% endhint %}

As part of its support for application-defined metadata propagation, ESS provides the following configurations to customize the propagation:

{% tabs %}
{% tab title="Allow Configurations" %}

| <p><strong><code>INRUPT\_REQUEST\_METADATA\_PROPAGATOR\_HEADER\_ALLOW</code></strong><br>Adds specified non-baggage request headers to the baggage for propagation (unless also specified in the corresponding <strong><code>\*\_DENY</code></strong> configuration); i.e., support propagation of non-baggage headers as application-defined properties. This configuration is case-<strong>insensitive</strong>.</p>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <p><br><strong><code>INRUPT\_REQUEST\_METADATA\_REFLECTOR\_HEADER\_ALLOW</code></strong><br>Determines which propagated properties can be returned as response headers (unless also specified in the corresponding <strong><code>\*\_DENY</code></strong> configuration). This configuration is case-<strong>sensitive</strong> to the entries in the propagated baggage.</p><div data-gb-custom-block data-tag="hint" data-style="info" class="hint hint-info"><p><strong>Tip</strong></p><ul><li>To return a propagated property that was added to the baggage via <strong><code>INRUPT\_REQUEST\_METADATA\_PROPAGATOR\_HEADER\_ALLOW</code></strong>, ensure that the cases of these properties match.</li><li>When returning application-properties as response headers, you may need to update <strong><code>QUARKUS\_HTTP\_CORS\_EXPOSED\_HEADERS</code></strong> to extend the list of <a href="https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_response_header">CORS-safelisted response headers</a></li></ul></div> |
| <p><br><strong><code>INRUPT\_LOGGING\_REQUEST\_METADATA\_ALLOW</code></strong><br>Determines which propagated properties can be included in associated <a href="/pages/tUGIdtSrD5Cju0vhWB9g#application-defined-metadata">log messages</a> (unless also specified in the corresponding <strong><code>\*\_DENY</code></strong> configuration). This configuration is case-<strong>sensitive</strong> to the propagated baggage entries.</p><div data-gb-custom-block data-tag="hint" data-style="info" class="hint hint-info"><p>To include a propagated property that was added to the baggage via <strong><code>INRUPT\_REQUEST\_METADATA\_PROPAGATOR\_HEADER\_ALLOW</code></strong>, ensure that the cases of these properties match.</p></div>                                                                                                                                                                                                                                                                                              |
| <p><strong><code>INRUPT\_AUDIT\_PRODUCER\_REQUEST\_METADATA\_ALLOW</code></strong><br>Determines which propagated properties can be included in associated <a href="/pages/og1zUWGs6znvuAmM2gM6#event-message-instrument-app-defined-metadata">audit events</a><br>(unless also specified in the corresponding <strong><code>\*\_DENY</code></strong> configuration). This configuration is case-<strong>sensitive</strong> to the propagated baggage entries.</p><div data-gb-custom-block data-tag="hint" data-style="info" class="hint hint-info"><p><strong>Tip</strong><br>To include a propagated property that was added to the baggage via <strong><code>INRUPT\_REQUEST\_METADATA\_PROPAGATOR\_HEADER\_ALLOW</code></strong>, ensure that the cases of these properties match.</p></div>                                                                                                                                                                                                                                              |
| {% endtab %}                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |

{% tab title="Deny Configurations" %}

| <p><strong><code>INRUPT\_REQUEST\_METADATA\_PROPAGATOR\_HEADER\_DENY</code></strong><br>Excludes specified non-baggage request headers from being added to the baggage.<br>This configuration is case-<strong>insensitive</strong>.</p>                                                                                                                                                                                                                                                                                                                                                           |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <p><br><strong><code>INRUPT\_REQUEST\_METADATA\_REFLECTOR\_HEADER\_DENY</code></strong><br>Excludes propagated properties from returning as response headers. This configuration is case-<strong>sensitive</strong> to the entries in the propagated baggage.</p><div data-gb-custom-block data-tag="hint" data-style="info" class="hint hint-info"><p><strong>Tip</strong><br>To exclude a propagated property that was added to the baggage via <strong><code>INRUPT\_REQUEST\_METADATA\_PROPAGATOR\_HEADER\_ALLOW</code></strong>, ensure that the cases of these properties match.</p></div>  |
| <p><strong><code>INRUPT\_LOGGING\_REQUEST\_METADATA\_DENY</code></strong><br>Excludes propagated properties from being included in associated log messages. This configuration is case-<strong>sensitive</strong> to the propagated baggage entries.</p><div data-gb-custom-block data-tag="hint" data-style="info" class="hint hint-info"><p><strong>Tip</strong><br>To exclude a propagated property that was added to the baggage via <strong><code>INRUPT\_REQUEST\_METADATA\_PROPAGATOR\_HEADER\_ALLOW</code></strong> , ensure that the cases of these properties match.</p></div>          |
| <p><strong><code>INRUPT\_AUDIT\_PRODUCER\_REQUEST\_METADATA\_DENY</code></strong><br>Determines which propagated properties are included in associated audit events. This configuration is case-<strong>sensitive</strong> to the propagated baggage entries.</p><div data-gb-custom-block data-tag="hint" data-style="info" class="hint hint-info"><p><strong>Tip</strong><br>To exclude a propagated property that was added to the baggage via <strong><code>INRUPT\_REQUEST\_METADATA\_PROPAGATOR\_HEADER\_ALLOW</code></strong> , ensure that the cases of these properties match.</p></div> |
| {% endtab %}                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |

{% tab title="Miscellaneous" %}
**`INRUPT_REQUEST_METADATA_PROPAGATOR_HEADER_OVERRIDES`**\
Determines, in cases of a property being defined both as a header and as a baggage entry, whether to keep the entry or update/override it with the header value.\
The default is to keep the baggage entry as is. See [Duplicate Property Definition](/ess/administration/application-defined-metadata#duplicate-property-definition) for more information.
{% endtab %}
{% endtabs %}

## Example Customization

The following example configuration updates:

* **`INRUPT_REQUEST_METADATA_PROPAGATOR_HEADER_ALLOW`** to include the client request **`x-correlation-id`** , **`x-request-id`** , and **`my-client-version`** headers as baggage entries.
* **`INRUPT_LOGGING_REQUEST_METADATA_ALLOW`** to include the propagated **`x-correlation-id`** , **`x-request-id`** , and **`my-client-version`** in the associated log messages.
* **`INRUPT_AUDIT_PRODUCER_REQUEST_METADATA_ALLOW`** to include the propagated **`x-correlation-id`** , **`x-request-id`** , and **`my-client-version`** in the associated audit events.
* **`INRUPT_LOGGING_REQUEST_METADATA_ALLOW`** to return the propagated **`x-correlation-id`** and **`x-request-id`** as response headers (and not **`my-client-version`**).

{% hint style="info" %}
**Tip** **`INRUPT_LOGGING_REQUEST_METADATA_ALLOW`** , **`INRUPT_AUDIT_PRODUCER_REQUEST_METADATA_ALLOW`** , and **`INRUPT_REQUEST_METADATA_REFLECTOR_HEADER_ALLOW`** are case- **sensitive** to the entries in the propagated baggage. If the propagated baggage includes properties from **`INRUPT_REQUEST_METADATA_PROPAGATOR_HEADER_ALLOW`** configuration, ensure that the cases of those properties match.
{% endhint %}

1. Go to your ESS installation directory:

   ```sh
   cd ${HOME}/ess
   ```
2. Modify the **`kustomization.yaml`** (i.e., step 3 of the [Applying Your Customizations](/ess/installation/customize-configurations#applying-your-customizations) procedure).\
   Specifically, add the highlighted content to the **`kustomization.yaml`** file under the **`patches`** key:

{% hint style="info" %}
**Tip** If **`patches`** list does not exist in **`kustomization.yaml`** , add the key **`patches`** as well.
{% endhint %}

```yaml
# kustomization.yaml in your ESS installation directory
# ...  Preceding content omitted for brevity 
# ...
patches:
  - target:
      kind: Deployment
      labelSelector: quarkus=true
    patch: |-
      - op: add
        path: /spec/template/spec/containers/0/env/-
        value:
          #Adds the following request headers to the `baggage` for propagation
          name: INRUPT_REQUEST_METADATA_PROPAGATOR_HEADER_ALLOW
          value:"x-correlation-id,x-request-id,my-client-version"
      - op: add
        path: /spec/template/spec/containers/0/env/-
        value:
          #Return the following propagated properties as response headers
          name: INRUPT_REQUEST_METADATA_REFLECTOR_HEADER_ALLOW
          value: "x-correlation-id,x-request-id"
      - op: add
        path: /spec/template/spec/containers/0/env/-
        value:
          #Include the following propagated properties in log messages
          name: INRUPT_LOGGING_REQUEST_METADATA_ALLOW
          value: "x-correlation-id,x-request-id,my-client-version,"
      - op: add
        path: /spec/template/spec/containers/0/env/-
        value:
          #Include the following propagated properties in audit events
          name: INRUPT_AUDIT_PRODUCER_REQUEST_METADATA_ALLOW
          value: "x-correlation-id,x-request-id,my-client-version"

```

{% hint style="info" %}
**Tip** When returning application-defined properties as response headers, you may need to update **`QUARKUS_HTTP_CORS_EXPOSED_HEADERS`** to extend the list of [CORS-safelisted response headers](https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_response_header) .
{% endhint %}

3. Continue with the rest of the [Applying Your Customizations](/ess/installation/customize-configurations#applying-your-customizations) procedure.


# Security Customization


# Set Authorization Client Allow List

The [Authorization Service](/ess/services/service-authorization) uses its [**`INRUPT_AUTHORIZATION_CLIENT_ID_ALLOW_LIST`**](/ess/services/service-authorization#inrupt_authorization_client_id_allow_list) option to specify which applications can access [Access Control Resources (ACRs)](https://docs.inrupt.com/security/authorization/acp#access-to-acrs) . Only the clients associated with the [Client IDs](https://docs.inrupt.com/security/authentication#client-identifier-client-id) in the list can modify the ACRs (i.e., modify access policies for resources).

{% hint style="danger" %}
**Disambiguation**\
Both [Authorization Service](/ess/services/service-authorization) and [Pod Storage Service](/ess/services/service-pod-management/service-pod-storage) have an **`INRUPT_AUTHORIZATION_CLIENT_ID_ALLOW_LIST`** setting.

<mark style="color:red;">**Only**</mark> the [Authorization Service](/ess/services/service-authorization) setting affects which clients are allowed.

The [Pod Storage Service](/ess/services/service-pod-management/service-pod-storage) is for [Discovery](/ess/services/service-pod-management/service-pod-storage#discovery) purposes only. As such, the [setting](/ess/services/service-pod-management/service-pod-storage#inrupt_authorization_client_id_allow_list) in [Pod Storage Service](/ess/services/service-pod-management/service-pod-storage) should reflect the values set in the Authorization Service’s.
{% endhint %}

{% hint style="info" %}
ESS also uses the [Authorization Service](/ess/services/service-authorization)'s [**`INRUPT_AUTHORIZATION_CLIENT_ID_ALLOW_LIST`**](/ess/services/service-authorization#inrupt_authorization_client_id_allow_list) to create the [initial ACP policies for a new Pod](https://docs.inrupt.com/security/authorization/acp#initial-acp-policies).

ESS uses the [Authorization Service](/ess/services/service-authorization)'s [**`INRUPT_AUTHORIZATION_DEFAULT_ACR_CLIENT_ID_ALLOW_LIST`**](/ess/services/service-authorization#inrupt_authorization_default_acr_client_id_allow_list) , if set, for the initial policies. But if [**`INRUPT_AUTHORIZATION_DEFAULT_ACR_CLIENT_ID_ALLOW_LIST`**](/ess/services/service-authorization#inrupt_authorization_default_acr_client_id_allow_list) is unset, ESS uses the [Authorization Service](/ess/services/service-authorization)'s [**`INRUPT_AUTHORIZATION_CLIENT_ID_ALLOW_LIST`**](/ess/services/service-authorization#inrupt_authorization_client_id_allow_list) instead.

To set [**`INRUPT_AUTHORIZATION_DEFAULT_ACR_CLIENT_ID_ALLOW_LIST`**](/ess/services/service-authorization#inrupt_authorization_default_acr_client_id_allow_list), see [Set Initial Pod Clients Allow List](/ess/installation/customize-configurations/customization-security/modify-pod-client-list).
{% endhint %}

## Example Customization

The following customization updates:

* [**`INRUPT_AUTHORIZATION_CLIENT_ID_ALLOW_LIST`**](/ess/services/service-authorization#inrupt_authorization_client_id_allow_list) for [Authorization Service](/ess/services/service-authorization) and
* the corresponding [**`INRUPT_AUTHORIZATION_CLIENT_ID_ALLOW_LIST`**](/ess/services/service-pod-management/service-pod-storage#inrupt_authorization_client_id_allow_list) for [Pod Storage Service](/ess/services/service-pod-management/service-pod-storage) (for [Discovery](/ess/services/service-pod-management/service-pod-storage#discovery) only).

1. Go to your ESS installation directory:

   ```sh
   cd ${HOME}/ess
   ```
2. Create a **`authz-client-id-allow-list.yaml`** file with the following content:

   ```javascript
   apiVersion: apps/v1
   kind: Deployment
   metadata:
     name: ess-authorization-acp
   spec:
     template:
       spec:
         containers:
         - env:
           - name: INRUPT_AUTHORIZATION_CLIENT_ID_ALLOW_LIST
             value: https://myApp.example.com/appid
           name: ess-authorization-acp
   ```
3. Create a **`podconfig-client-id-allow-list.yaml`** file with the following content:

   ```javascript
   apiVersion: apps/v1
   kind: Deployment
   metadata:
     name: ess-pod-storage
   spec:
     template:
       spec:
         containers:
         - env:
           - name: INRUPT_AUTHORIZATION_CLIENT_ID_ALLOW_LIST
             value: https://myApp.example.com/appid
           name: ess-pod-storage
   ```
4. Modify the **`kustomization.yaml`** (i.e., step 3 of the [Applying Your Customizations](/ess/installation/customize-configurations) procedure) to use **`authz-client-id-allow-list.yaml`** and **`podconfig-client-id-allow-list.yaml`** .\
   Specifically, add the highlighted content to the **`kustomization.yaml`** file to the **`patches`** section:

{% hint style="info" %}
**Tip**

If the **`patches`** key does not exist in **`kustomization.yaml`** , add the key **`patches`** as well.
{% endhint %}

```
<pre class="language-yaml"><code class="lang-yaml">
# kustomization.yaml in your ESS installation directory
# ...  Preceding content omitted for brevity 
# ...
patches:
<strong>  - path: authz-client-id-allow-list.yaml
  - path: podconfig-client-id-allow-list.yaml
</strong>
</code></pre>
```

5. Continue with the rest of the [Applying Your Customizations](/ess/installation/customize-configurations) procedure.


# Set Initial Pod Clients Allow List

The [default ACP policies for a new Pod](https://docs.inrupt.com/security/authorization/acp#initial-acp-policies) states that for an agent whose WebID matches the Pod owner and is using an application whose ClientID matches a value listed in the policy, that agent is allowed Read and Write access.

[Authorization Service](/ess/services/service-authorization) uses its [**`INRUPT_AUTHORIZATION_DEFAULT_ACR_CLIENT_ID_ALLOW_LIST`**](/ess/services/service-authorization#inrupt_authorization_default_acr_client_id_allow_list) configuration to initialize the client matcher portion of the initial policies.

{% hint style="info" %}
**Note** [**`INRUPT_AUTHORIZATION_DEFAULT_ACR_CLIENT_ID_ALLOW_LIST`**](/ess/services/service-authorization#inrupt_authorization_default_acr_client_id_allow_list) only affects the initial policies during Pod creation. Once the initial policies have been created, any change to the list has no effect on existing policies.
{% endhint %}

If [**`INRUPT_AUTHORIZATION_DEFAULT_ACR_CLIENT_ID_ALLOW_LIST`**](/ess/services/service-authorization#inrupt_authorization_default_acr_client_id_allow_list) is unset, ESS uses the **Authorization service’s** [**`INRUPT_AUTHORIZATION_CLIENT_ID_ALLOW_LIST`**](/ess/services/service-authorization#inrupt_authorization_client_id_allow_list) instead. See [Set Authorization Client Allow List](/ess/installation/customize-configurations/customization-security/modify-authz-client-list) for details on configuring.

## Example Customization

The following customization updates [**`INRUPT_AUTHORIZATION_DEFAULT_ACR_CLIENT_ID_ALLOW_LIST`**](/ess/services/service-authorization#inrupt_authorization_default_acr_client_id_allow_list) .

1. Go to your ESS installation directory:

   ```sh
   cd ${HOME}/ess
   ```
2. Create a **`authz-default-acr-client-id-allow-list.yaml`** file with the following content:

   ```javascript
   apiVersion: apps/v1
   kind: Deployment
   metadata:
     name: ess-authorization-acp
   spec:
     template:
       spec:
         containers:
         - env:
           - name: INRUPT_AUTHORIZATION_DEFAULT_ACR_CLIENT_ID_ALLOW_LIST
             value: https://myPodApp.example.com/appid
           name: ess-authorization-acp
   ```
3. Modify the **`kustomization.yaml`** (i.e., step 3 of the [Applying Your Customizations](/ess/installation/customize-configurations#applying-your-customizations) procedure) to use **`authz-default-acr-client-id-allow-list`** .\
   Specifically, add the highlighted content to the **`kustomization.yaml`** file to the **`patches`** section:

{% hint style="info" %}
**Tip**

If the **`patches`** key does not exist in **`kustomization.yaml`** , add the key **`patches`** as well.
{% endhint %}

<pre class="language-yaml"><code class="lang-yaml"># kustomization.yaml in your ESS installation directory

# ...  Preceding content omitted for brevity 
# ...

patches:
<strong>  - path: authz-default-acr-client-id-allow-list.yaml
</strong></code></pre>

4\. Continue with the rest of the [Applying Your Customizations](/ess/installation/customize-configurations#applying-your-customizations) procedure.


# Manage Token Issuer Allow/Deny Lists

{% hint style="info" %}
**ESS 3.0 Change**

The [Platform Management Service](/ess/services/service-platform-management) is the only service that accepts external OIDC tokens. All other ESS services exclusively trust ESS Access Tokens issued by the [Platform Management service](/ess/services/service-platform-management/token-exchange). Configure the allow/deny lists on the Platform Management service to control which Identity Providers are trusted.
{% endhint %}

The Platform Management Service can be configured with **`INRUPT_JWT_ISSUER_ALLOW_LIST`** and **`INRUPT_JWT_ISSUER_DENY_LIST`** to manage which external Identity Providers are trusted to issue tokens that can be exchanged for ESS Access Tokens.

## How the Lists Work

* If **`INRUPT_JWT_ISSUER_ALLOW_LIST`** is set, the Platform service only accepts tokens from issuers in the list (subject to the deny list).
* If **`INRUPT_JWT_ISSUER_ALLOW_LIST`** is unset, the Platform service accepts tokens from all issuers (subject to the deny list).
* If **`INRUPT_JWT_ISSUER_DENY_LIST`** is set, the Platform service rejects tokens from those issuers, even if they appear in the allow list. The deny list always takes precedence.

## Example: Update `INRUPT_JWT_ISSUER_ALLOW_LIST`

To restrict the Platform service to only accept tokens from specific Identity Providers:

1. Go to your ESS installation directory:

   ```sh
   cd ${HOME}/ess
   ```
2. Modify the **`kustomization.yaml`** (i.e., step 3 of the [Applying Your Customizations](/ess/installation/customize-configurations) procedure).\
   Specifically, add the highlighted content to the **`kustomization.yaml`** file to the **`patches`** section:

   <pre class="language-yaml"><code class="lang-yaml">
    # kustomization.yaml in your ESS installation directory
    # ...  Preceding content omitted for brevity 
    # ...
    patches:
   <strong>   - target:
   </strong><strong>       kind: Deployment
   </strong><strong>       name: ess-platform-management
   </strong><strong>     patch: |
   </strong><strong>       - op: add
   </strong><strong>         path: /spec/template/spec/containers/0/env/-
   </strong><strong>         value:
   </strong><strong>           name: INRUPT_JWT_ISSUER_ALLOW_LIST
   </strong><strong>           value: "https://login.example.com,https://accounts.google.com"
   </strong><strong> 
   </strong> 
   </code></pre>
3. Continue with the rest of the [Applying Your Customizations](/ess/installation/customize-configurations) procedure.

## Example: Update `INRUPT_JWT_ISSUER_DENY_LIST`

To block specific Identity Providers from being used for token exchange:

1. Go to your ESS installation directory:

   ```sh
   cd ${HOME}/ess
   ```
2. Modify the **`kustomization.yaml`** (i.e., step 3 of the [Applying Your Customizations](/ess/installation/customize-configurations) procedure).\
   Specifically, add the highlighted content to the **`kustomization.yaml`** file to the **`patches`** section:

   <pre class="language-yaml"><code class="lang-yaml">
    # kustomization.yaml in your ESS installation directory
    # ...  Preceding content omitted for brevity 
    # ...
    patches:
   <strong>   - target:
   </strong><strong>       kind: Deployment
   </strong><strong>       name: ess-platform-management
   </strong><strong>     patch: |
   </strong><strong>       - op: add
   </strong><strong>         path: /spec/template/spec/containers/0/env/-
   </strong><strong>         value:
   </strong><strong>           name: INRUPT_JWT_ISSUER_DENY_LIST
   </strong><strong>           value: "https://untrusted-idp.example.com"
   </strong><strong> 
   </strong> 
   </code></pre>
3. Continue with the rest of the [Applying Your Customizations](/ess/installation/customize-configurations) procedure.


# Use Official Certificate Authority

In production, ESS should run with certificates from an official Certificate Authority (CA) for all external facing services rather than self-signed certificates.

## Example Customization

The following customization example uses Let’s Encrypt as the Certificate Authority. Specifically, the customization directs all your Ingress resources to use Let’s Encrypt.

1. Go to your ESS installation directory:

   ```sh
   cd ${HOME}/ess
   ```
2. Modify the **`kustomization.yaml`** (i.e., step 3 of the [Applying Your Customizations](/ess/installation/customize-configurations#applying-your-customizations) procedure).\
   Specifically, add the highlighted content to the **`kustomization.yaml`** file under the **`patches`** key:

   {% hint style="info" %} **Tip** If the **`patches`** key does not exist in **`kustomization.yaml`** , add the key **`patches`** as well. {% endhint %}

   ```yaml
   ```

`kustomization.yaml in your ESS installation directory... Preceding content omitted for brevity...`

` patches:`` `` `**`- target: kind: Ingress patch: |- - op: replace path: /metadata/annotations/cert-manager.io~1issuer value: letsencrypt-prod`**

3. Continue with the rest of the [Applying Your Customizations](/ess/installation/customize-configurations#applying-your-customizations) procedure.


# Add Custom Certificates to ESS Services

In some cases, you may need to add custom certificates to the ESS services’ trust store. For example, you may need to add custom certificates to allow ESS services to communicate with services that do not use typical certificate authorities.

{% hint style="warning" %}
**Warning**\
The following procedure modifies [initContainers](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/) for your Kubernetes pods and may have far-reaching impact. Exercise care when using the following procedure.
{% endhint %}

## Example Customization

The following kustomization uses the Inrupt-provided **`load-custom-cert.yaml`** to add a custom certificate (named **`custom.crt`** in the example) from a **`ConfigMap`** when pods start running.

1. Obtain the **`load-custom-cert.yaml`** from the `components/` directory in the Inrupt-provided Kustomize manifests repository.
2. Copy the **`load-custom-cert.yaml`** to the ESS installation directory.

   ```sh
   cp openid-custom-certificate/load-custom-cert.yaml ${HOME}/ess/
   ```

   If saving to a directory different from the ESS installation directory, update the path to **`load-custom-cert.yaml`** in the **`kustomization.yaml`** step below.
3. Go to your ESS installation directory:

   ```sh
   cd ${HOME}/ess
   ```
4. Save your custom certificate in a file named **`custom.crt`** .
5. Modify the **`kustomization.yaml`** (i.e., step 3 of the [Applying Your Customizations](/ess/installation/customize-configurations#applying-your-customizations) procedure).\
   Specifically, add the highlighted content to the **`kustomization.yaml`** file under the **`patches`** key and **`configMapGenerator`** key:

{% hint style="info" %}
**Tip**

If **`patches`** key does not exist in **`kustomization.yaml`** , add the **`patches`** key as well.

If **`configMapGenerator`** key does not exist in **`kustomization.yaml`** , add the **`configMapGenerator`** key as well.
{% endhint %}

<pre><code># kustomization.yaml in your ESS installation directory

# ...  Preceding content omitted for brevity
# ...

patches:
<strong>  - path: load-custom-cert.yaml
</strong><strong>    target:
</strong><strong>      kind: Deployment
</strong><strong>      name: ess-openid
</strong>
configMapGenerator:
<strong>  - name: custom-certificate
</strong><strong>    namespace: ess
</strong><strong>    files:
</strong><strong>      - custom.crt
</strong></code></pre>

6. Continue with the rest of the [Applying Your Customizations](/ess/installation/customize-configurations#applying-your-customizations) procedure.


# Configure SSL/TLS connection to databases

Enforcing SSL/TLS on the connection to the databases ensures the communication between the database server and the clients is encrypted, which is a recommended practice.

## Database infrastructure configuration

The specific database server configuration is out of scope of the ESS configuration. Depending on the database infrastructure you chose to support your ESS deployment, configuration details may vary.

Once SSL/TLS is enabled, obtain the Certificate Authority (CA) public root certificate bundle in PEM format. Clients need this certificate bundle to encrypt traffic to the database server.

## Mounting the database CA bundle on the containers

The ESS kustomizer includes a component that consumes the CA certificate bundle and mounts it on the containers that need to access the database. Here is how to enable it:

1. Defined a `Secret` in your kustomization named `database-ca-bundle`, with an item named `database-ca-bundle.pem` that contains the CA certificate bundle from your database server.

```yaml
secretGenerator:
  - name: database-ca-bundle
    files:
      - database-ca-bundle.pem
```

2. Include `../release/ess/deployment/kubernetes/components/add-databases-ca/` in your root `kustomization.yaml` file

This will result in the database bundle being mounted on the path `/opt/cacerts/db/database-ca-bundle.pem`.

## Configuring the JDBC URL

In order for your client to enforce SSL/TLS, the following parameters need to be added to the JDBC URLs that are configured as part of the Postgres credentials: `ssl=true&sslmode=verify-full&sslrootcert=/opt/cacerts/db/database-ca-bundle.pem`.


# Configure SSL/TLS connection to Kafka

Enforcing SSL/TLS on the connection to Kafka ensures the communication between the brokers and the clients is encrypted, which is a recommended practice.

## Kafka cluster configuration

The specific Kafka cluster configuration is out of scope of the ESS configuration. Depending on the Kafka provider you chose to support your ESS deployment, configuration details may vary.

After enabling SSL/TLS on your Kafka cluster, obtain the Certificate Authority (CA) public root certificate bundle in PEM format. Clients need this certificate bundle to encrypt traffic to the cluster.

Some Kafka providers use public trust repository as their CA. For major cloud providers, these public CAs are often included in the JVM's default trust store, such as [AWS's MSK](https://docs.aws.amazon.com/msk/latest/developerguide/msk-encryption.html#msk-encryption-in-transit) or [GCP Managed Kafka](https://cloud.google.com/managed-service-for-apache-kafka/docs/overview#encryption). If your provider's CA is already part of the default JVM trust store, you can skip the manual import steps below.

## Adding the Kafka cluster CA to the JVM trust store

If your Kafka provider's CA certificate bundle is not part of the default JVM trust store, you need to add it manually so that clients can trust the cluster's certificate upon connection.

See [Add Custom Certificates to ESS Services](/ess/installation/customize-configurations/customization-security/add-custom-certs) for intructions.

## Configuring the clients to use SSL

The ESS kustomizer includes a component that configures the Kafka clients to use SSL.

To enable SSL for Kafka clients:

1. Ensure your Kafka cluster is configured to support SSL
2. Ensure the cluster's certificate is available in the JVM trust store
3. Add `../release/ess/deployment/kubernetes/components/kafka-clients-ssl/` to your root `kustomization.yaml` file

{% hint style="warning" %}
If the Kafka cluster is not configured to support SSL or the cluster's certificate is not available in the JVM trust store before modifying the `kustomization.yaml` file, the deployment will fail.
{% endhint %}


# Pod Maintenance and Metrics


# Modify Prune Configuration

ESS includes a [Prune](/ess/services/service-pod-management/service-pod-storage#prune-hard-delete-feature) feature to perform hard delete (i.e., permanently delete):

* soft-deleted resources (i.e., files marked as deleted) and
* orphan data (i.e., data that are no longer referenced by metadata).

Specifically, Prune consists of two [Kubernetes CronJobs](https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/) :

* [One](/ess/services/service-pod-management/service-pod-storage#pruning-soft-deleted-resources) to delete “prunable” resources. Prunable resources are resources that have been marked for deletion (i.e., soft-deleted) and are past their [**`INRUPT_STORAGE_PRUNE_RETENTION_WINDOW`**](/ess/services/service-pod-management/service-pod-storage#inrupt_storage_prune_retention_window) .
* [One](/ess/services/service-pod-management/service-pod-storage#pruning-orphan-data) to delete orphan data.

You can use Kustomize to modify the two CronJobs.

## Example Customizations

### Configure CronJob to Prune Soft-Deleted Resources

{% hint style="warning" %}
**Important**\
Pruning operations may negatively affect performance. If possible, schedule the CronJob to run at times when you can minimize its impact.
{% endhint %}

The provided overlays are configured to:

* Use a job schedule of every 30 minutes.
* Use a [**`INRUPT_STORAGE_PRUNE_RETENTION_WINDOW`**](/ess/services/service-pod-management/service-pod-storage#inrupt_storage_prune_retention_window) of **`P3D`** (3 days).
* Use [**`INRUPT_STORAGE_PRUNE_PRUNABLE_BATCH_SIZE`**](/ess/services/service-pod-management/service-pod-storage#inrupt_storage_prune_prunable_batch_size) of **`10000`** .
* Use [**`INRUPT_STORAGE_PRUNE_ORPHAN_BATCH_SIZE`**](/ess/services/service-pod-management/service-pod-storage#inrupt_storage_prune_orphan_batch_size) of **`0`** . <mark style="background-color:red;">**Do not modify for pruning soft-deleted resources**</mark>**.**
* Use the default [**`COM_INRUPT_STORAGE_METADATA_JDBC_CONNECTIONLIMITER_OPENCONNECTION_TIMEOUT_VALUE`**](/ess/services/service-pod-management/service-pod-storage#com_inrupt_storage_metadata_jdbc_connectionlimiter_openconnection_timeout_value) of **`5000`** milliseconds.

If instead you wish to schedule the job to run every day at midnight ( **`0 0 * * *`** ) and to decrease the [**`INRUPT_STORAGE_PRUNE_RETENTION_WINDOW`**](/ess/services/service-pod-management/service-pod-storage#inrupt_storage_prune_retention_window) to 2 days **`P2D`** :

1. Go to your ESS installation directory:

   ```sh
   cd ${HOME}/ess
   ```
2. Create a **`kustomize-prune-resources.yaml`** file with the following content:

   ```javascript
   apiVersion: batch/v1
   kind: CronJob
   metadata:
     name: ess-prune-prunable
   spec:
     jobTemplate:
       spec:
         template:
           spec:
             containers:
             - env:
               - name: INRUPT_STORAGE_PRUNE_RETENTION_WINDOW
                 value: P2D
               name: ess-prune-prunable
     schedule: '0 0 * * *'
   ```
3. Modify the **`kustomization.yaml`** (i.e., step 3 of the [Applying Your Customizations](/ess/installation#applying-your-customizations) procedure) to use **`kustomize-prune-resources.yaml`** .\
   Specifically, add the highlighted content to the **`kustomization.yaml`** file to the **`patches`** section:

{% hint style="info" %}
**Tip**

If **`patches`** key does not exist in **`kustomization.yaml`** , add the **`patches`** key as well.
{% endhint %}

<pre class="language-yaml"><code class="lang-yaml"># kustomization.yaml in your ESS installation directory

# ...  Preceding content omitted for brevity 
# ...

patches:
<strong>  - path: kustomize-prune-resources.yaml
</strong></code></pre>

4\. Continue with the rest of the [Applying Your Customizations](/ess/installation#applying-your-customizations) procedure.

### Configure CronJob to Prune Orphan Data

{% hint style="warning" %}
**Important**\
Pruning operations may negatively affect performance. If possible, schedule the job to run at times when you can minimize its impact.
{% endhint %}

The provided overlays are configured to:

* Use a job schedule of every 30th minutes from 10 minutes past the hour through 50 minutes past the hour.
* Use a [**`INRUPT_STORAGE_PRUNE_RETENTION_WINDOW`**](/ess/services/service-pod-management/service-pod-storage#inrupt_storage_prune_retention_window) of **`P3D`** (3 days). **Does not affect the pruning of orphan data.**
* Use [**`INRUPT_STORAGE_PRUNE_PRUNABLE_BATCH_SIZE`**](/ess/services/service-pod-management/service-pod-storage#inrupt_storage_prune_prunable_batch_size) of **`0`** . **Do not modify for pruning orphan data.**
* Use [**`INRUPT_STORAGE_PRUNE_ORPHAN_BATCH_SIZE`**](/ess/services/service-pod-management/service-pod-storage#inrupt_storage_prune_orphan_batch_size) of **`80000`** .
* Use the default [**`COM_INRUPT_STORAGE_METADATA_JDBC_CONNECTIONLIMITER_OPENCONNECTION_TIMEOUT_VALUE`**](/ess/services/service-pod-management/service-pod-storage#com_inrupt_storage_metadata_jdbc_connectionlimiter_openconnection_timeout_value) of **`5000`** milliseconds.

If instead you wish to increase the [**`INRUPT_STORAGE_PRUNE_ORPHAN_BATCH_SIZE`**](/ess/services/service-pod-management/service-pod-storage#inrupt_storage_prune_orphan_batch_size) to **`140000`** and [**`COM_INRUPT_STORAGE_METADATA_JDBC_CONNECTIONLIMITER_OPENCONNECTION_TIMEOUT_VALUE`**](/ess/services/service-pod-management/service-pod-storage#com_inrupt_storage_metadata_jdbc_connectionlimiter_openconnection_timeout_value) to **`10000`** milliseconds.

1. Go to your ESS installation directory:

   ```sh
   cd ${HOME}/ess
   ```
2. Create a **`kustomize-prune-orphans.yaml`** file with the following content:

   ```javascript
   apiVersion: batch/v1
   kind: CronJob
   metadata:
     name: ess-prune-orphans
   spec:
     jobTemplate:
       spec:
         template:
           spec:
             containers:
             - env:
               - name:  INRUPT_STORAGE_PRUNE_ORPHAN_BATCH_SIZE
                 value: "140000"
               - name:  COM_INRUPT_STORAGE_METADATA_JDBC_CONNECTIONLIMITER_OPENCONNECTION_TIMEOUT_VALUE
                 value: "10000"
               name: ess-prune-orphans
   ```
3. Modify the **`kustomization.yaml`** (i.e., step 3 of the [Applying Your Customizations](/ess/installation#applying-your-customizations) procedure) to use **`kustomize-prune-orphans.yaml`** .\
   Specifically, add the highlighted content to the **`kustomization.yaml`** file to the **`patches`** section:

{% hint style="info" %}
**Tip**

If **`patches`** key does not exist in **`kustomization.yaml`** , add the **`patches`** key as well.
{% endhint %}

<pre class="language-yaml"><code class="lang-yaml"># kustomization.yaml in your ESS installation directory

# ...  Preceding content omitted for brevity 
# ...

patches:
<strong>  - path: kustomize-prune-orphans.yaml
</strong></code></pre>

4. Continue with the rest of the [Applying Your Customizations](/ess/installation#applying-your-customizations) procedure.


# Modify Storage Metrics Schedule

ESS includes a [Storage Metrics](/ess/services/service-pod-management/service-pod-storage#storage-metrics) feature to gather the following metrics:

* The total number of Pods
* The number of Pods that have been “Created” (where the provision has been confirmed)
* The number of Pods that have been “Deleted”(marked for deletion; i.e., soft-deleted).

Specifically, Storage Metrics runs as a [Kubernetes CronJobs](https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/).

You can use Kustomize to modify the schedule of when the job runs.

## Example Customizations

{% hint style="warning" %}
**Important**\
Gathering Storage Metrics operations may negatively impact the performance of your system. If possible, schedule the CronJob to run at times when you can minimize its impact.
{% endhint %}

The provided overlays are configured to run every 2 minutes.

If instead you wish to schedule the job to run every day at midnight ( **`0 0 * * *`** ):

1. Go to your ESS installation directory:

   ```sh
   cd ${HOME}/ess
   ```
2. Create a **`kustomize-storage-metrics.yaml`** file with the following content:

   ```yaml
   apiVersion: batch/v1
   kind: CronJob
   metadata:
     name: ess-storage-metrics
   spec:
     schedule: '0 0 * * *'
   ```
3. Modify the **`kustomization.yaml`** (i.e., step 3 of the [Applying Your Customizations](/ess/installation#applying-your-customizations) procedure) to use **`kustomize-storage-metrics.yaml`** .\
   Specifically, add the highlighted content to the **`kustomization.yaml`** file to the `patches` section:

{% hint style="info" %}
**Tip**

If **`patches`** key does not exist in **`kustomization.yaml`** , add the **`patches`** key as well.
{% endhint %}

```yaml
# kustomization.yaml in your ESS installation directory

# ...  Preceding content omitted for brevity 
# ...

patches:
  - path: kustomize-storage-metrics.yaml
```

4\. Continue with the rest of the [Applying Your Customizations](/ess/installation#applying-your-customizations) procedure.

See also [Administration: Storage Metrics](/ess/administration/ess-metrics#storage-metrics)


# General




---

[Next Page](/llms-full.txt/1)

