mirror of https://github.com/lukechilds/docs.git
Browse Source
- Add Stacks.js references to "Build app" nav - Move BNS and Gaia to new protocols section in same nav - Remove Stacks Connect page, merging contents into authentication guidefeat/build-apps
Mark Hendrickson
4 years ago
6 changed files with 227 additions and 305 deletions
Before Width: | Height: | Size: 25 KiB |
@ -1,285 +0,0 @@ |
|||||
--- |
|
||||
title: Stacks Connect |
|
||||
description: Open protocol for connecting apps built with Stacks |
|
||||
images: |
|
||||
large: /images/pages/authentication.svg |
|
||||
sm: /images/pages/authentication-sm.svg |
|
||||
--- |
|
||||
|
|
||||
Stacks Connect is an open protocol for connecting apps built with the Stacks blockchain, such as consumer apps with authenticators and wallets. |
|
||||
|
|
||||
## Authentication flow |
|
||||
|
|
||||
For an application developer, the application flow is similar to the typical client-server flow used by centralized sign in services (for example, OAuth). However, with Stacks Connect, the authentication flow happens entirely client-side. |
|
||||
|
|
||||
An app and authenticator, such as [the Stacks Wallet](https://blockstack.org/wallet), communicate during the authentication flow by passing back and forth two tokens. The requesting application sends the authenticator an `authRequest` token. Once a user approves a sign-in, the authenticator responds to the application with an `authResponse` token. These tokens are <a href="https://jwt.io/" target="\_blank">JSON Web Tokens</a>, and they are passed via URL query strings. |
|
||||
|
|
||||
![](/images/app-sign-in.png) |
|
||||
|
|
||||
When a user chooses to authenticate a decentralized application, it calls the `doOpenAuth()` method which sends an `authRequest` to the authenticator. Stacks auth passes the token in via a URL query string in the `authRequest` parameter: |
|
||||
|
|
||||
`https://app.blockstack.org/#/sign-up?authRequest=j902120cn829n1jnvoa...` |
|
||||
|
|
||||
When the authenticator receives the request, it generates an (`authResponse`) token to the application using an _ephemeral transit key_ . The ephemeral transit key is just used for the particular instance of the application, in this case, to sign the `authRequest`. The application stores the ephemeral transit key during the request generation. The public portion of the transit key is passed in the `authRequest` token. The authenticator uses the public portion of the key to encrypt an _app-private key_ which is returned via the `authResponse`. |
|
||||
|
|
||||
During sign in, the authenticator generates the app-private key from the user's _identity-address private_ key and the application's `appDomain`. The app private key serves three functions: |
|
||||
|
|
||||
- It is used to create the credentials that give an app access to the Gaia storage bucket for that specific app. |
|
||||
- It is used in the end-to-end encryption of files stored for the app in the user's Gaia storage. |
|
||||
- It serves as a cryptographic secret that apps can use to perform other cryptographic functions. |
|
||||
|
|
||||
Finally, the app private key is deterministic, meaning that for a given user ID and domain name, the same private key is generated each time. |
|
||||
|
|
||||
## Scopes |
|
||||
|
|
||||
Scopes define the permissions requested by an app for granting during authentication. |
|
||||
|
|
||||
Apps may request any of the following scopes: |
|
||||
|
|
||||
| Scope | Definition | |
|
||||
| -------------- | ------------------------------------------------------------------------------------ | |
|
||||
| `store_write` | Read and write data to the user's Gaia hub in an app-specific storage bucket. | |
|
||||
| `publish_data` | Publish data so that other users of the app can discover and interact with the user. | |
|
||||
|
|
||||
The permissions scope should be specified through the [`AppConfig`](https://blockstack.github.io/stacks.js/classes/appconfig.html) |
|
||||
object. If no `scopes` array is provided to the `redirectToSignIn` or `makeAuthRequest` functions, the default is to request `['store_write']`. |
|
||||
|
|
||||
## Manifest file |
|
||||
|
|
||||
Decentralized apps have a manifest file. This file is based on the [W3C web app manifest specification](https://w3c.github.io/manifest/). |
|
||||
The following is an example manifest file. |
|
||||
|
|
||||
```json |
|
||||
{ |
|
||||
"name": "Todo App", |
|
||||
"start_url": "http://todos.blockstack.org", |
|
||||
"description": "A simple todo app build on Stacks", |
|
||||
"icons": [ |
|
||||
{ |
|
||||
"src": "http://todos.blockstack.org/logo.png", |
|
||||
"sizes": "400x400", |
|
||||
"type": "image/png" |
|
||||
} |
|
||||
] |
|
||||
} |
|
||||
``` |
|
||||
|
|
||||
The Stacks Wallet retrieves the manifest file from the app during the authentication process and displays the |
|
||||
information in it such as the app `name` and to the user during sign in. The location of the app manifest file is specific |
|
||||
in the authentication request token and **must** be on the same origin as the app requesting authentication. |
|
||||
|
|
||||
The manifest file **must** have [Cross-origin resource sharing (CORS) headers](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) |
|
||||
that allow the manifest file to be fetched from any arbitrary source. This usually means returning a header like this: |
|
||||
|
|
||||
``` |
|
||||
Access-Control-Allow-Origin: * |
|
||||
``` |
|
||||
|
|
||||
How you implement CORS depends in part on which platform/service you use to serve your application. For example, Netlify |
|
||||
and Firebase have two different ways of configuring CORS. Consult your vendor documentation for more information. |
|
||||
|
|
||||
## Key pairs |
|
||||
|
|
||||
Stacks Auth makes extensive use of public key cryptography. Blockstack uses ECDSA with the `secp256k1` curve. The |
|
||||
following sections describe the three public-private key pairs used in the authentication process: |
|
||||
|
|
||||
- how they're generated |
|
||||
- where they're used |
|
||||
- to whom the private key is disclosed |
|
||||
|
|
||||
### Transit private key |
|
||||
|
|
||||
The transit private is an ephemeral key that is used to encrypt secrets that |
|
||||
need to be passed from the authenticator to the decentralized app during the |
|
||||
authentication process. It is randomly generated by the app at the beginning of |
|
||||
the authentication response. |
|
||||
|
|
||||
The public key that corresponds to the transit private key is stored in a single |
|
||||
element array in the `public_keys` key of the authentication request token. The |
|
||||
authenticator encrypts secret data such as the app private key using this |
|
||||
public key and sends it back to the app when the user signs in to the app. The |
|
||||
transit private key signs the app authentication request. |
|
||||
|
|
||||
### Identity address private key |
|
||||
|
|
||||
The identity address private key is derived from the user's keychain phrase and |
|
||||
is the private key of the Stacks username that the user chooses to use to sign in |
|
||||
to the app. It is a secret owned by the user and never leaves the user's |
|
||||
instance of the authenticator. |
|
||||
|
|
||||
This private key signs the authentication response token for an app to indicate that the user approves sign in to that app. |
|
||||
|
|
||||
### App private key |
|
||||
|
|
||||
The app private key is an app-specific private key that is generated from the |
|
||||
user's identity address private key using the `domain_name` as input. It is |
|
||||
deterministic in that for a given Stacks username and `domain_name`, the same |
|
||||
private key is generated each time. |
|
||||
|
|
||||
The app private key is securely shared with the app on each authentication, encrypted by the authenticator with the transit public key. |
|
||||
|
|
||||
## JSON Web Token signatures |
|
||||
|
|
||||
Both the `authRequest` and the `authResponse` tokens are [JSON Web Tokens](https://jwt.io/), and they are passed via URL query strings. |
|
||||
|
|
||||
Stacks authentication tokens are based on the [RFC 7519 OAuth JSON Web Token (JWT)](https://tools.ietf.org/html/rfc7519) |
|
||||
with additional support for the `secp256k1` curve used by Bitcoin and many other |
|
||||
cryptocurrencies. |
|
||||
|
|
||||
This signature algorithm is indicated by specifying `ES256K` in the token's |
|
||||
`alg` key, specifying that the JWT signature uses ECDSA with the secp256k1 |
|
||||
curve. Stacks auth provide both [JavaScript](https://github.com/blockstack/jsontokens-js) |
|
||||
and |
|
||||
[Ruby](https://github.com/blockstack/ruby-jwt-blockstack/tree/ruby-jwt-blockstack) |
|
||||
JWT libraries with support for this signing algorithm. |
|
||||
|
|
||||
-> The Stacks JWT implementation is different from other implementations because of the underlying cryptography we employ. There are libraries in [JavaScript](https://github.com/blockstack/jsontokens-js) and [Ruby](https://github.com/blockstack/ruby-jwt-blockstack) available on the Blockstack Github to allow you to work with these tokens. |
|
||||
|
|
||||
### Example: authRequest payload schema |
|
||||
|
|
||||
```jsx |
|
||||
const requestPayload = { |
|
||||
jti, // UUID |
|
||||
iat, // JWT creation time in seconds |
|
||||
exp, // JWT expiration time in seconds |
|
||||
iss, // legacy decentralized identifier generated from transit key |
|
||||
public_keys, // single entry array with public key of transit key |
|
||||
domain_name, // app origin |
|
||||
manifest_uri, // url to manifest file - must be hosted on app origin |
|
||||
redirect_uri, // url to which the authenticator redirects user on auth approval - must be hosted on app origin |
|
||||
version, // version tuple |
|
||||
do_not_include_profile, // a boolean flag asking authenticator to send profile url instead of profile object |
|
||||
supports_hub_url, // a boolean flag indicating gaia hub support |
|
||||
scopes, // an array of string values indicating scopes requested by the app |
|
||||
}; |
|
||||
``` |
|
||||
|
|
||||
### Example: authResponse payload schema |
|
||||
|
|
||||
```jsx |
|
||||
const responsePayload = { |
|
||||
jti, // UUID |
|
||||
iat, // JWT creation time in seconds |
|
||||
exp, // JWT expiration time in seconds |
|
||||
iss, // legacy decentralized identifier (string prefix + identity address) - this uniquely identifies the user |
|
||||
private_key, // encrypted private key payload |
|
||||
public_keys, // single entry array with public key |
|
||||
profile, // profile object or null if passed by profile_url |
|
||||
username, // Stacks username (if any) |
|
||||
core_token, // encrypted core token payload |
|
||||
email, // email if email scope is requested & email available |
|
||||
profile_url, // url to signed profile token |
|
||||
hubUrl, // url pointing to user's gaia hub |
|
||||
version, // version tuple |
|
||||
}; |
|
||||
``` |
|
||||
|
|
||||
## Decode authRequest |
|
||||
|
|
||||
To decode the token and see what information it holds: |
|
||||
|
|
||||
1. Copy the `authRequest` string from the URL. |
|
||||
|
|
||||
<img src="/images/copy-authRequest.png" alt="" /> |
|
||||
|
|
||||
2. Navigate to [jwt.io](https://jwt.io/). |
|
||||
3. Paste the full token there. |
|
||||
|
|
||||
The output should look similar to below: |
|
||||
|
|
||||
```json |
|
||||
{ |
|
||||
"jti": "f65f02db-9f42-4523-bfa9-8034d8edf459", |
|
||||
"iat": 1555641911, |
|
||||
"exp": 1555645511, |
|
||||
"iss": "did:btc-addr:1ANL7TNdT7TTcjVnrvauP7Mq3tjcb8TsUX", |
|
||||
"public_keys": ["02f08d5541bf611ded745cc15db08f4447bfa55a55a2dd555648a1de9759aea5f9"], |
|
||||
"domain_name": "http://localhost:8080", |
|
||||
"manifest_uri": "http://localhost:8080/manifest.json", |
|
||||
"redirect_uri": "http://localhost:8080", |
|
||||
"version": "1.3.1", |
|
||||
"do_not_include_profile": true, |
|
||||
"supports_hub_url": true, |
|
||||
"scopes": ["store_write", "publish_data"] |
|
||||
} |
|
||||
``` |
|
||||
|
|
||||
The `iss` property is a decentralized identifier or `did`. This identifies the user and the user name to the application. The specific `did` is a `btc-addr`. |
|
||||
|
|
||||
## User profiles |
|
||||
|
|
||||
Profile data is stored using Gaia on the user's selected storage provider. An example of a `profile.json` file URL using |
|
||||
default provided storage: |
|
||||
|
|
||||
``` |
|
||||
https://gaia.blockstack.org/hub/1EeZtGNdFrVB2AgLFsZbyBCF7UTZcEWhHk/profile.json |
|
||||
``` |
|
||||
|
|
||||
Follow these steps to create and register a profile for a BNS username (`identifier`): |
|
||||
|
|
||||
1. Create a JSON profile object |
|
||||
2. Split up the profile into tokens, sign the tokens, and put them in a token file |
|
||||
3. Create a zone file that points to the web location of the profile token file |
|
||||
|
|
||||
```jsx |
|
||||
"account": [ |
|
||||
{ |
|
||||
"@type": "Account", |
|
||||
"service": "twitter", |
|
||||
"identifier": "naval", |
|
||||
"proofType": "http", |
|
||||
"proofUrl": "https://twitter.com/naval/status/12345678901234567890" |
|
||||
} |
|
||||
] |
|
||||
``` |
|
||||
|
|
||||
## Create a profile |
|
||||
|
|
||||
```jsx |
|
||||
const profileOfNaval = { |
|
||||
'@context': 'http://schema.org/', |
|
||||
'@type': 'Person', |
|
||||
name: 'Naval Ravikant', |
|
||||
description: 'Co-founder of AngelList', |
|
||||
}; |
|
||||
``` |
|
||||
|
|
||||
## Sign a profile as a single token |
|
||||
|
|
||||
```jsx |
|
||||
import { wrapProfileToken, Person } from '@stacks/profiles'; |
|
||||
|
|
||||
const privateKey = 'e546ba96ee34220287d0c177418011addf8d71b32fb81ae8e33a1d7510fa5d0d01'; |
|
||||
|
|
||||
const person = new Person(profileOfNaval); |
|
||||
const token = person.toToken(privateKey); |
|
||||
const tokenFile = [wrapProfileToken(token)]; |
|
||||
``` |
|
||||
|
|
||||
## Verify an individual token |
|
||||
|
|
||||
```jsx |
|
||||
import { verifyProfileToken } from '@stacks/profiles'; |
|
||||
|
|
||||
try { |
|
||||
const decodedToken = verifyProfileToken(tokenFile[0].token, publicKey); |
|
||||
} catch (e) { |
|
||||
console.log(e); |
|
||||
} |
|
||||
``` |
|
||||
|
|
||||
## Recover a profile from a token file |
|
||||
|
|
||||
```jsx |
|
||||
const recoveredProfile = Person.fromToken(tokenFile, publicKey); |
|
||||
``` |
|
||||
|
|
||||
## Validate profile schema |
|
||||
|
|
||||
```jsx |
|
||||
const validationResults = Person.validateSchema(recoveredProfile); |
|
||||
``` |
|
||||
|
|
||||
### Transaction signing |
|
||||
|
|
||||
TBD: info on transaction signing protocol |
|
Loading…
Reference in new issue