User identities¶
This section will focus on how to create user identities with private-keys.
The Textile Hub supports public-key infrastructure (PKI), which allows your app to integrate different PKI-based user identity providers (e.g. Metamask, 3Box, uPort, Blockstack) or roll your own.
Most of our examples use a simple, platform-agnostic keypair identity based on ed25519 and by extending the Noble ed25519 library.
Key-based identity access¶
The Hub can help verify that users are who they claim to be by using encryption. The general flow is as follows:
- A user attempts to sign-in by providing their public key.
- Your app creates a one-time challenge for the users.
- The user signs the challenge with their private key to generate credentials.
- Your app verifies the credentials.
Below, we'll 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 ed25519 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 a secure place to store secrets. localStorage
access is also not guaranteed and may be cleared by the browser, the system, or the users. 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("user-private-identity", identity.toString());
/** Return the random identity */
return identity;
};
Signing transactions¶
The PrivateKey
object contains a signing method that allows your app to 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;
}
Advanced identity providers¶
Public key provider¶
The code below shows two generic identity interfaces. You can import these interfaces from @textile/hub
.
// Read more https://textileio.github.io/js-textile/docs/hub.public
interface Public {
verify(data: Buffer, sig: Buffer): Promise<boolean>;
}
// Read more https://textileio.github.io/js-textile/docs/hub.identity
interface Identity {
sign(data: Buffer): Promise<Buffer>;
public: Public;
}
Identity
here represents any entity capable of signing a message.
It requires that the implementer is capable of returning an associated public key for verification. This interface design was inspired by public key infrastructure.
In many cases, the Identity
will just be a private key, but callers can use any setup that suits their needs.
The default implementation is based on the Noble ed25519 library but many developers will want to use alternative identity provides, such as 3box/Ceramic, Fortmatic, and existing private/public key-pair, or a web3 provider such as Metamask.
Textile Hub also provides email-based identities.
Metamask¶
One issue with the above workflow is that you need to help users store and recover their private keys.
You could do this with your user model stored over an API. Alternatively, you can use any key-pair 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:
- It prompts the user for a new
secret
. The secret helps ensure that another app cannot easily trick the user into generating their ed25519 key. - 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.
- The unique string is then signed with the user's Ethereum address by using the Metamask API.
- 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.
3Box¶
Another good starting point is with 3Box SDK. 3Box manages a cluster of nodes where web3 users 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 the time of writing, November 16, 2020, 3Box does not 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
}
}
}
Next steps¶
Time to setup your app in development mode.