Using browser.ssi
browser.ssi
is the underlying API that is more powerful than window.ssi. It does everything window.ssi does, and more:
- Can read all the credentials (if the user self-sovereignly does allow)
- Can read the user’s setting states (as long as there are no privacy and security issues)
- Can integrate element APIs to create any flow you want
Instead, this API is limited where you can use. It would be assumed to be built by Built-in API or Experimental API of Firefox based on WebExtensions, so you can access through your web extension.
Basically, this API can be called in background script, but possibly content script and devtools as well. You can check what scope an browser.ssi has in your using implementation by looking at ssi’s manifest file. See for example here.
Set up
To use this API, you declare in the permissions
property in your WebExtension’s manifest.json
file. For example:
// manifest.json
{
"manifest_version": 2,
"name": "Example Web Extension",
"permissions": ["ssi"],
}
If you want the subset of the specific protocol, please add the one with ssi
together.
// manifest.json
{
"permissions": ["ssi", "ssi.nostr"],
}
Basic usage
The ssi declared in the permissions property in your manifest file is injected into the global object browser
.
await browser.ssi.searchCredentialsWithoutSecret({
protocolName,
credentialName,
})
Search store
Example of a full search for SSI store service. The return values are array of ssi.Credential.
const credentials = await browser.ssi.searchCredentialsWithoutSecret(
1,
{
primary: false // explicitly set primary to false.
}
)
store.set(credentials.map(credential => doSomething(credential)))
In fact, the results returned are filtered by the user’s preferences, privacy and security reasons.
To get the current public key of the user:
const credentials = await browser.ssi.searchCredentialsWithoutSecret(
1,
{
protocolName: "nostr",
credentialName: "nsec",
primary: true,
}
)
const publicKey = credentials[0].identifier // format is "npub1..."
Request a task related to the key
You can sign and encrypt/decrypt using the internal key currently set as primary for a specific protocol.
const signature = await browser.ssi.nostr.sign(message, { type: "signEvent" })
You should always read the public key without using cache just before sign/encrypt/decrypt, as users may change their primary key without notifying you.
Get user settings
You will get what you need. The return value is ssi.nostr.Preference.
const preferences = await browser.ssi.nostr.getPrefs()
store.set(preferences)
Receive notifications when user settings status changes
You can listen to this as a special event.
const onPrimaryChangedCallback = async () => {
// Get the new primary key. If without authorization, auth dialog will be prompted.
const credentials = await browser.ssi.searchCredentialsWithoutSecret({
protocolName: "nostr",
credentialName: "nsec",
primary: true
})
const publicKey = decodeNpub(credentials[0].identifier)
// Send the message to the contents
const tabs = await browser.tabs.query()
for (const tab of tabs) {
browser.tabs
.sendMessage(tab.id, {
action: "accountChanged",
args: { publicKey },
})
}
}
browser.ssi.nostr.onPrimaryChanged.addListener(onPrimaryChangedCallback)
At first glance it looks similar to a normal EventListener, but it may be useful to know that it is a different mechanism to cross IPC.
Authorization
It’s always the libra between the UX and security for powerful features. Just like the history of camera and notification permissions, this one requires careful considerations and is one of the topics we explore the most: Authorization flow.
Currently, the combination of the trusted site method and password authentication is implemented. This is a two step method and the basic flow is as follows:
The trusted site that the user has explicitly declared trust for by specifying the app’s URL is the top level. If that is NG, a prompt of the browser’s primary password or OS account password authentication is presented to the user. However, this is the self-sovereign browser, so, if both settings are explicitly disabled, it is accepted the user has self-sovereignly chosen that the app does not need the user’s consent to use their key, and the request will be allowed through unconditionally.
This can be done below.
// Authorization will be performed using the secret currently set as primary
// within the specified protocol name and credential name.
const permitted = await browser.ssi.askConsent(
1,
"nostr", // protocol name
"nsec", // credential name
{
// A text description displayed on Auth dialog. Base title (kind such as sign/encrypt
// and site URL) is generated by the system, so add additional information as needed.
caption: "Offer from ABC Company"
}
)
if (permitted) {
// Go to next
}