Review & Spec creation for Blockstack Authentication

In the spirit of getting started early on 2018’s spring cleaning, we’re going to conduct a review of the Blockstack Authentication with an eye towards fixing any outstanding issues before codifying it into a more formal specification.

I propose a process something like this:

  • Review the authentication process and token format
  • Discuss any issues that come up and decide whether or not we should address them at this time
  • Assign a person to address each issue
  • Iterate on and accept their changes
  • Assign person to create draft of specification document
  • Comment/revision period on specification document

Outstanding issues

Authentication Token formats

Below, I’ve included commented versions of the authentication request and response tokens as they are in blockstack.js 0.15.0 for those who might not be familiar with the contents of these structures.

Authentication Request Format

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.json file - must be hosted on app origin
    redirect_uri, // url to which browser redirects user on auth approval - must be hosted on app origin
    version, // version tuple
    do_not_include_profile, // a boolean flag asking browser 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
  }

Authentication Response Format

  const responsePayload = {
  jti, // UUID
  iat, // JWT creation time in seconds
  exp, // JWT expiration time in seconds
  iss, // legacy decentralized identifier (string prefix + identity address)
  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, // blockstack id 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
}

Next Steps

We can discuss this plan in either the next weekly meeting or next engineering meeting. In the meantime, if there are some issues you have with authentication that are not on the list above, please comment on this topic!

4 Likes

+1 This could be summarized as, “Better error messages.”

Ex. alerting the user to update their browser if it is out of date. I was unable to authenticate with an app this weekend because my browser was out of date, and I had no idea. With the version being submitted in the request, this would be a way to alert the user instead of just an “Invalid Sign In Request” message.

2 Likes

Hi! I’m working on the Rocket.Chat integration bounty. Some of the stuff you’re proposing would have saved a bit of teeth pulling for me over the last week. Though it’s a pretty eccentric platform anyway, so the auth isn’t all to blame :smiley:

I just wanted to say this sounds good, nothing to add except for a mention I also commented on the ticket @larry put up.

1 Like

This is great @larry! Thanks for putting it together! Having this spec will enable other language implementations and much more flexibility in using Blockstack signin!

This process looks like a good plan to me —
One more issue related to:

Passing token by other methods to excessive URI length problems with servers: https://github.com/blockstack/blockstack.js/issues/284

In addition to investigating other methods to pass the token, we should also stress reducing the size of the token in the spec creation. A lot of the size issue is my fault – the encrypted strings are re-encoded several times – but we should think about fixing that.

2 Likes

A couple questions:

  1. Is the responsePayload.username field allowed to be a subdomain?

Should app trust be based on app origin or app domain? (We currently use web browser origin but call the field domain_name

  1. Should the spec guarantee that that domain_name never changes for an app, even if the app’s DNS name changes?

For blockstack.ts I formalized the code into the following JSON schema:

Auth Request

{
	"$schema": "http://json-schema.org/schema#",
	"id": "AuthRequest.json",
	"type": "object",
	"properties": {
		"jti": {
			"description": "UUID for this JWT",
			"type": "string"
		},
		"iat": {
			"description": "JWT creation time in seconds",
			"type": "number"
		},
		"exp": {
			"description": "JWT expiration time in seconds",
			"type": "number"
		},
		"iss": {
			"description": "Legacy decentralized identifier generated from transit key",
			"type": "string"
		},
		"public_keys": {
			"description": "Single entry array with public key of transit key",
			"type": "array",
			"items": {
				"type": "string"
			}
		},
		"domain_name": {
			"description": "App origin",
			"type": "string"
		},
		"manifest_uri": {
			"description": "URL to manifest.json file - must be hosted on app origin",
			"type": "string"
		},
		"redirect_uri": {
			"description": "URL to which the browser redirects user on auth approval - must be hosted on app origin",
			"type": "string"
		},
		"version": {
			"description": "Version tuple",
			"type": "string"
		},
		"do_not_include_profile": {
			"description": "Flag asking browser to send profile URL instead of profile object",
			"type": "boolean"
		},
		"supports_hub_url": {
			"description": "Flag indicating Gaia hub support",
			"type": "boolean"
		},
		"scopes": {
			"description": "Requested scopes",
			"type": "array",
			"items": {
				"type": "string"
			}
		}
	},
	"additionalProperties": false,
	"required": [
		"do_not_include_profile",
		"domain_name",
		"exp",
		"iat",
		"iss",
		"jti",
		"manifest_uri",
		"public_keys",
		"redirect_url",
		"scopes",
		"supports_hub_url",
		"version"
	]
}

Auth Response

{
	"$schema": "http://json-schema.org/schema#",
	"id": "AuthResponse.json",
	"type": "object",
	"properties": {
		"jti": {
			"description": "UUID for this JWT",
			"type": "string"
		},
		"iat": {
			"description": "JWT creation time in seconds",
			"type": "number"
		},
		"exp": {
			"description": "JWT expiration time in seconds",
			"type": "number"
		},
		"iss": {
			"description": "Legacy decentralized identifier (string prefix + identity address)",
			"type": "string"
		},
		"private_key": {
			"description": "Encrypted private key payload",
			"type": "string"
		},
		"public_keys": {
			"description": "Single entry array with public key",
			"type": "array",
			"items": {
				"type": "string"
			}
		},
		"profile": {
			"description": "Profile object or null if passed by profile_url",
			"allOf": [{ "$ref": "../../profile/schema/Profile.json" }]
		},
		"username": {
			"description": "Blockstack id username (if any)",
			"type": "string"
		},
		"core_token": {
			"description": "Encrypted core token payload",
			"type": "string"
		},
		"email": {
			"description": "Email if email scope is requested & email available",
			"type": "string"
		},
		"profile_url": {
			"description": "URL to signed profile token",
			"type": "string"
		},
		"hubUrl": {
			"description": "URL pointing to user's Gaia hub",
			"type": "string"
		},
		"version": {
			"description": "Version tuple",
			"type": "string"
		}
	},
	"additionalProperties": false,
	"required": [
		"jti",
		"iat",
		"exp",
		"iss",
		"private_key",
		"public_keys",
		"profile",
		"username",
		"core_token"
	]
}

Though we still need to go through the properties for narrowing down which are needed and which not. Maybe also for case consistency? (hubUrl vs hub_url).
Also we can fix the version to a specific value with JSON schema (might be what we want here).


Also for reference the generated TypeScript interfaces:

Auth Request
export interface AuthRequestJson {
	/**
	 * UUID for this JWT
	 */
	jti: string;
	/**
	 * JWT creation time in seconds
	 */
	iat: number;
	/**
	 * JWT expiration time in seconds
	 */
	exp: number;
	/**
	 * Legacy decentralized identifier generated from transit key
	 */
	iss: string;
	/**
	 * Single entry array with public key of transit key
	 */
	public_keys: string[];
	/**
	 * App origin
	 */
	domain_name: string;
	/**
	 * URL to manifest.json file - must be hosted on app origin
	 */
	manifest_uri: string;
	/**
	 * URL to which the browser redirects user on auth approval - must be hosted on app origin
	 */
	redirect_uri?: string;
	/**
	 * Version tuple
	 */
	version: string;
	/**
	 * Flag asking browser to send profile URL instead of profile object
	 */
	do_not_include_profile: boolean;
	/**
	 * Flag indicating Gaia hub support
	 */
	supports_hub_url: boolean;
	/**
	 * Requested scopes
	 */
	scopes: string[];
}
Auth Response
/**
 * This file was automatically generated by json-schema-to-typescript.
 * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
 * and run json-schema-to-typescript to regenerate this file.
 */

export interface AuthResponseJson {
	/**
	 * UUID for this JWT
	 */
	jti: string;
	/**
	 * JWT creation time in seconds
	 */
	iat: number;
	/**
	 * JWT expiration time in seconds
	 */
	exp: number;
	/**
	 * Legacy decentralized identifier (string prefix + identity address)
	 */
	iss: string;
	/**
	 * Encrypted private key payload
	 */
	private_key: string;
	/**
	 * Single entry array with public key
	 */
	public_keys: string[];
	/**
	 * Profile object or null if passed by profile_url
	 */
	profile: ProfileJson;
	/**
	 * Blockstack id username (if any)
	 */
	username: string;
	/**
	 * Encrypted core token payload
	 */
	core_token: string;
	/**
	 * Email if email scope is requested & email available
	 */
	email?: string;
	/**
	 * URL to signed profile token
	 */
	profile_url?: string;
	/**
	 * URL pointing to user's Gaia hub
	 */
	hubUrl?: string;
	/**
	 * Version tuple
	 */
	version?: string;
}
export interface ProfileJson {
	'@context': string;
	'@type': string;
	'@id': string;
	[k: string]: any;
}