Skip to main content

User Sessions

Create authenticated sessions for users with great UX.

About Sessions


  • Sessions provide a familiar, web2-like authentication experience for Ceramic apps where a user signs-in once for a timebound session and then interacts without needing to manually approve every transaction
  • A durable root Ceramic account (did:pkh) is generated based on the user’s blockchain wallet
  • The root account generates a temporary Ceramic account (did:key) for each app with tightly-scoped permissions that only lives for a period of time in the user’s browser

Installation


First, install the did-sessions library:

pnpm add did-session

Then, install the appropriate blockchain wallet module:

# For Ethereum accounts
pnpm add @didtools/pkh-ethereum

# For Solana accounts
pnpm add @didtools/pkh-solana

Authorization


Ethereum Wallets

Authorize with an Ethereum account using @didtools/pkh-ethereum:

import { DIDSession } from 'did-session'
import { EthereumWebAuth, getAccountId } from '@didtools/pkh-ethereum'
import { ComposeClient }from '@composedb/client'

const ethProvider = // import/get your web3 eth provider
const addresses = await ethProvider.request({ method: 'eth_requestAccounts' })
const accountId = await getAccountId(ethProvider, addresses[0])
const authMethod = await EthereumWebAuth.getAuthMethod(ethprovider, accountId)

const compose = new ComposeClient()

const session = await DIDSession.get(accountId, authMethod, { resources: compose.resources})
compose.setDID(session.did)

Solana Wallets

Authorize with a Solana account using @didtools/pkh-solana:

import { DIDSession } from 'did-session'
import { SolanaWebAuth, getAccountIdByNetwork } from '@didtools/pkh-solana'
import { ComposeClient }from '@composedb/client'

const solProvider = // import/get your Solana provider (ie: window.phantom.solana)
const address = await solProvider.connect()
const accountId = getAccountIdByNetwork('mainnet', address.publicKey.toString())
const authMethod = await SolanaWebAuth.getAuthMethod(solProvider, accountId)

const compose = new ComposeClient()

const session = await DIDSession.get(accountId, authMethod, { resources: compose.resources})
compose.setDID(session.did)
tip

Additional chain support is continually being added. You can find the link to each chain and its docs below.

Scopes


In the last line of the above examples, you see a resources array. This is effectively a scope of permission that the user is assigning. In ComposeDB, these resources are the models you’ve included in your composite.

The compose client offers a simple getter compose.resources that formats all model streamIDs in your composite for did-session. You can then pass this as a configuration option.

The compose.resources is an array of URI-formatted streamIDs of models, for example:

;[
'ceramic://*?model=kjzl6hvfrbw6c5ajfmes842lu09vjxu5956e3xq0xk12gp2jcf9s90cagt2god9',
'ceramic://*?model=kjzl6hvfrbw6c99mdfpjx1z3fue7sesgua6gsl1vu97229lq56344zu9bawnf96',
]

Session Lifecycle

Additional helper functions are available to help you manage a session lifecycle and the user experience.

// Check if authorized or created from existing session string
didsession.hasSession

// Check if session expired
didsession.isExpired

// Get resources session is authorized for
didsession.authorizations

// Check number of seconds till expiration, may want to re auth user at a time before expiration
didsession.expiresInSecs

Complete Example

A typical usage pattern is to store sessions in localstorage, when a user loads your app you can load an existing session or create a new one. When you start making mutations with the client instance, you should make sure that the session is not expired.

caution

LocalStorage is used for illustrative purposes here and may not be best for your app, as there is a number of known issues with storing secret material in browser storage. The session string allows anyone with access to that string to make writes for that user for the time and resources that session is valid for. How that session string is stored and managed is the responsibility of the application.

import { DIDSession } from 'did-session'
import type { AuthMethod } from '@didtools/cacao'
import { EthereumWebAuth, getAccountId } from '@didtools/pkh-ethereum'
import { ComposeClient }from '@composedb/client'

const ethProvider = // import/get your web3 eth provider
const addresses = await ethProvider.request({ method: 'eth_requestAccounts' })
const accountId = await getAccountId(ethProvider, addresses[0])
const authMethod = await EthereumWebAuth.getAuthMethod(ethProvider, accountId)

const compose = new ComposeClient()

const session = await DIDSession.get(accountId, authMethod, { resources: compose.resources})
compose.setDID(session.did)

// pass ceramic instance where needed, ie glaze
// ...

// Before mutations, check if a session is still valid, if expired, create new
if (session.isExpired) {
const session = await DIDSession.get(accountId, authMethod, { resources: compose.resources})
compose.setDID(session.did)
}

// perform mutations, continue to use compose client

When authenticating, the user is prompted with a human-readable wallet modal that explains what they’re giving permissions for. To accomplish this integration, the DID Session library supports Sign-In With X, a chain agnostic authorization standard. Sign-In With Ethereum is shown below:

Wallet Modal

Next Steps


With authenticated users, now you can move to:

  1. Setting up Data Interactions
  2. Being able to use mutations with ComposeDB client