Skip to content

User identity

In this section, we're going to focus on identity. Specifically, we're going to create user identities using private-keys. The Textile Hub supports public-key infrastructure (PKI) allowing your app to support many user identity providers based on PKI (e.g. Metamask, 3Box, uPort, Blockstack) or derive your own. In most of our examples, we'll use a simple, platform agnostic keypair identity based on ed2559 and extending the Noble ed2559 library.

Key-based identity access

Your application can grant users access to your Hub APIs very easily. When doing so, the Hub can also help you verify that the users are who they claim to be using encryption. The general flow is as follows:

  1. A user attempts to sign-in by providing their public key.
  2. Your app creates a one-time challenge for the users.
  3. The user signs the challenge with their private key to generate credentials.
  4. Your app verifies the credentials.

Below, we will simplify these steps by using the Hub's helpful token generation endpoint that also does credential verification.

Generating an identity

In this example, we'll use an identity based on an ed2559 signature scheme and made available through the @textile/hub library.

Install dependency

npm install --save @textile/hub

Generating Identities

You can use the PrivateKey utility to generate random new identities (private and public keys) and later, to sign challenges to prove private key ownership.

import {PrivateKey} from '@textile/hub';

async function example () {
   /** Random new identity */
   const identity = await PrivateKey.fromRandom()

   /** Convert to string. */
   const identityString = identity.toString()

   /** Restore an identity object from a string */
   const restored = PrivateKey.fromString(identityString)
}

All of the instances above are different representations of the same user generated by PrivateKey.fromRandom(). Each instance holds a different copy of the user's private-key and therefore should remain private between your app and your user.

Caching user identity

You can add simple client-side caching to store a user's identity in the browser and restore it when the user returns to the app.

Warning

localStorage isn't guaranteed and may be cleared by the browser, the system, or the users. Even more important, localStorage isn't a secure place to store secrets. You should provide alternative storage mechanisms if maintaining identity (and therefore data ownership and access) over time is important.

import {PrivateKey} from '@textile/hub';

const getIdentity = async (): Promise<PrivateKey> => {
  /** Restore any cached user identity first */
  const cached = localStorage.getItem("user-private-identity")
  if (cached !== null) {
    /** Convert the cached identity string to a PrivateKey and return */
    return PrivateKey.fromString(cached)
  }
  /** No cached identity existed, so create a new one */
  const identity = await PrivateKey.fromRandom()
  /** Add the string copy to the cache */
  localStorage.setItem("identity", identity.toString())
  /** Return the random identity */
  return identity
}

Signing transactions

The PrivateKey object contains a signing method, allowing your app to now sign arbitrary bytes for your users. You can create your own identity verification endpoint or use the Hub's token endpoint to verify the credentials.

import {PrivateKey} from '@textile/hub';

async function sign (identity: PrivateKey) {
   const challenge = Buffer.from('Sign this string');

   const credentials = identity.sign(challenge);

   return credentials
}

Next steps

Time to setup your app in development mode.

Advanced identity providers

Public key provider

The below describe two generic identity interfaces used. You can import these interfaces from @textile/hub.

// Read more https://textileio.github.io/js-hub/docs/hub.public
interface Public {
  verify(data: Buffer, sig: Buffer): Promise<boolean>
}

// Read more https://textileio.github.io/js-hub/docs/hub.identity
interface Identity {
  sign(data: Buffer): Promise<Buffer>
  public: Public
}

Identity here represents any entity capable of signing a message. This is a simple public key infrastructure inspired interface that similarly requires the implementer to be capable of returning an associated public key for verification. In many cases, the Identity will just be a private key, but callers can use any setup that suits their needs. A default implementation based on Noble ed2559 library but many developers will want to use alternative identity provides, such as 3box/Ceramic, Fortmatic, and existing private/public keypair, or a web3 provider such as Metamask. Textile Hub also provides email-based identities.

Metamask

One trick with the above workflow is that you need to help your users store and recover their private keys. You could do this with your user model stored over an API. Alternatively, you can use any keypair manager, such as Metamask. There are a few steps to generate a Textile compatible identity from the Metamask API.

We've provided an example using Metamask here.

In the above example, we allow you to generate a new ed25519 key for your users based on their Ethereum address in Metamask. This key is deterministic, meaning that as long as your user maintains access to their address in Metamask and their account secret, they can recover the same ed25519 key. This is handy if you don't want to store your user's private keys outside the app or use unreliable storage (e.g., local storage in browsers). It does this as follows:

  1. Prompt the user for a new secret. The secret helps ensure that another app cannot easily trick the user into generating their ed25519 key.
  2. The app then creates a unique string that contains a hashed version of the secret, the app name, and some additional text. The app can regenerate this string at any time in the future if the user supplies their secret.
  3. The unique string is then signed with the user's Ethereum address, using the Metamask API.
  4. The resulting signature is used to generate an ed25519 key that can be used as an identity with all Textile APIs.

There are some benefits to this approach over using the ethereum address directly. An obvious one is that there is no public association between an ethereum address and the ed25519 key. This approach will allow users to create many keys without needing to worry about people association all their app use with a single address.

3Box

Another good starting point is to use the 3Box SDK. 3Box manages a cluster of nodes that web3 users where can push small bits of information. 3Box provides some helpful abstractions that integrate with Metamask and can help you create, manage, and recover keys for your users. In this approach, a user with a 3Box identity can use that identity to create and track Buckets or Threads generated on Textile.

We've provided an example using 3Box here.

Info

As of writing this, 3Box doesn't have Typescript typings available.

import { PrivateKey } from '@textile/hub'

const Box = require("3box");

const getIdentity = async (): Promise<PrivateKey> => {
 const box = await Box.create((window as any).ethereum)
 const [address] = await (window as any).ethereum.enable()
 await box.auth([], { address })
 const space = await box.openSpace('io-textile-3box-docs')
 await box.syncDone
 let identity: PrivateKey
 try {
   var storedIdent = await space.private.get("ed25519-identity")
   if (storedIdent === null) {
     throw new Error('No identity')
   }
   identity = await PrivateKey.fromString(storedIdent)
   return identity
 } catch (e) {
   try {
     identity = await PrivateKey.fromRandom()
     const identityString = identity.toString()
     await space.private.set("ed25519-identity", identityString)
   } catch (err) {
     return err.message
   }
 }
}