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:
- npm
- pnpm
- yarn
npm install did-session
pnpm add did-session
yarn add did-session
Then, install the appropriate blockchain wallet module:
- npm
- pnpm
- yarn
# For Ethereum accounts
npm install @didtools/pkh-ethereum
# For Solana accounts
npm install @didtools/pkh-solana
# For Ethereum accounts
pnpm add @didtools/pkh-ethereum
# For Solana accounts
pnpm add @didtools/pkh-solana
# For Ethereum accounts
yarn add @didtools/pkh-ethereum
# For Solana accounts
yarn 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)
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.
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
Modal
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:
Next Steps
With authenticated users, now you can move to:
- Setting up Data Interactions
- Being able to use mutations with ComposeDB client