This blog is now an archive. Find content from Hiro here and Stacks news and announcements here.

Blockstack authentication server-side — node.js

Guest post by George Bennett of Zinc.work

This post was originally published on Medium.com by George Bennett and is republished with permission.

The Blockstack authentication flow enables users to authenticate themselves in an entirely client-side fashion. This way, users can port their identity and private data without the need for third party authentication.

But… what if you need to authenticate with a server? Users may need to authenticate with third parties in order to access services that they trust. Luckily, Blockstack has all the methods required to support this use case.

Client set-up

The first half of the flow works exactly the same as the client-only flow described in the Blockstack guide. Your app will generate an authRequestwith the necessary scopes and redirect your user to the blockstack browser. Your user will return to the callback URL with an authResponse JWT which contains all the information needed to authenticate the user. You’ll need to POST the authResponse to your server for authentication.

Server set-up

The authResponse token contains all the information needed to authenticate your user including a unique identifier, email address and the URL needed to fetch the user’s profile data.

  • First, we can use the blockstack.verifyAuthResponse() method to verify that the authResponse is valid
  • Then, decode the JWT to reveal the user’s unique identifier, email and profile URL
  • Then, perform a GET request to the profile URL to retrieve the user’s name
** FOR DEMONSTRATION PURPOSES ONLY **
import request from "request-promise"
import { verifyAuthResponse } from "blockstack"
import { decodeToken } from "jsontokens"
async function blockstackAuth(req: Request, res: Response) { 
  const LOOKUP_URL = "https://core.blockstack.org/v1/names"
  const authResponse = req.body.authResponse
  let token: any
  try {
    const valid = await verifyAuthResponse(authResponse, LOOKUP_URL)
    token = decodeToken(authResponse).payload
    if (!valid || !token) { 
      throw new Error("invalid authResponse.")
    }
  } 
  catch (e) {
    return res.status(400).send(e)
  }
  const userJSON = await request(token.profile_url)
  const userToken = JSON.parse(userProfile)[0].decodedToken
  const userData = userToken.payload.claim
  const userUniqueIdentifier = token.iss
  const userEmail = token.email
  const userFullName = userData.name
  
  // Some logic relating to the now authenticated user e.g. sign-up
  res.status(200).send({ message: "all good!" })
}

Catch — clock skew 

Time sensitive JWT authentication can suffer from clock skew, whereby the issuer’s time and the authenticator’s time is out of sync.

If you find that a blockstack authResponse is deemed invalid by your server, it might be because your server’s time is behind that of the server which issued the authResponse. The blockstack.verifyAuthResponse() method will deem the futuristic issuance time as invalid.

To resolve this, at Zinc, we omitted the issuance validation of the blockstack.verifyAuthResponse() method in favour of a custom method which allowed for time discrepancy (or “leeway” by the JWT standard). We made use of the Blockstack component functions to re-construct our own authResponse validation:

// blockstack.js methods 
await blockstack.isExpirationDateValid(authResponse)
await blockstack.doSignaturesMatchPublicKeys(authResponse)
await blockstack.doPublicKeysMatchIssuer(authResponse)
await blockstack.doPublicKeysMatchUsername(authResponse, LOOKUP_URL)
// custom issuance date validation method
await isIssuanceDateValid(authResponse, {leeway: 30})
George Bennet

George Bennet

Zinc is a work identity platform that shares Blockstack’s vision for decentralisation and privacy. We’ve entered the app mining programme and written about our experience adding the blockstack authentication flow below. Find out more: zinc.work