Hivesigner for Mobile and Desktop Apps: PKCE Sign-In, a Faster SDK Release and Tighter Checks

Words
863
Reading
4 min
10m
Listen to this post
~6 min

Few months ago we shipped the rebuilt Hivesigner. This update is mostly for the people who build on it: app developers who want their users to sign in with Hivesigner from a phone or desktop app, not only from a website. If you only use Hivesigner to log in to Hive apps, nothing changes for you except that more apps can now offer it safely.

Sign in from a mobile or desktop app with PKCE

Until now, Hivesigner's code flow assumed your app had a server that could keep a client secret. A mobile or desktop app cannot keep a secret: anything shipped inside an app can be read out of it. So native apps were left with weaker options.

Hivesigner now supports PKCE (Proof Key for Code Exchange), the standard way for an app without a secret to use the code flow. It works like this:

  1. Before each sign-in, your app makes a random value, the verifier, and keeps it in memory. It sends Hivesigner only a fingerprint of it, the challenge.

  2. The user approves on Hivesigner as usual. Hivesigner signs a code that contains the challenge, so nobody can swap or remove it afterwards.

  3. Your app exchanges the code for an access token, sending the verifier. The token API checks that the verifier matches the challenge.

A code that someone catches on its way back to your app is useless to them, because the verifier never travels through the browser. Each PKCE code can be exchanged once, within ten minutes.

PKCE is also what lets a native app come back on its own scheme, such as myapp://callback. Hivesigner accepts a custom scheme callback only for a code request with a valid challenge, and it never sends a token to one. Register the callback on your app account like any other. Where you can, use a verified App Link or Universal Link instead, which the phone opens only in your app.

A native app gets an access token and no refresh token, since refreshing needs the secret it does not have. When the token expires, the user signs in again, which takes one tap for an app they already approved.

One app account, a website and an app

Many projects have both: a website with a server that holds the client secret, and a mobile app that cannot. They can now share one app account. On your app's profile page on hivesigner.com, tick Let this app's native app exchange PKCE codes without the secret. Your website keeps exchanging codes with its secret and gets refresh tokens. Your app exchanges PKCE codes with its verifier alone.

An account that has a secret and does not tick that box keeps needing the secret for every exchange, PKCE or not. That keeps PKCE from weakening an existing server-side integration.

The SDK does the work

The hivesigner package on npm has the helpers built in from version 4.1:

import { Client, createPkcePair } from 'hivesigner';

const client = new Client({
  app: 'myapp',
  callbackURL: 'myapp://callback',
  responseType: 'code',
  scope: ['posting'],
});

const { codeVerifier, codeChallenge } = await createPkcePair();
const url = client.getLoginURL(state, undefined, { codeChallenge });
// open url, then on myapp://callback?code=CODE:
const tokens = await client.exchangeCode(code, codeVerifier);

Set responseType: 'code', since the challenge only applies to the code flow.

Version 4.2 adds a timeout option. Pass timeout in milliseconds when you create the client, or call client.setTimeout(ms). A call that gets no answer in time fails with a TimeoutError instead of waiting forever, and the request is cancelled. With no timeout set, nothing changes.

Releases now go to npm automatically from the repository, with a signed provenance statement that ties each package to the exact source and build that produced it. The package also got smaller: it ships only the built library, not the sources and test files that earlier versions carried along.

Tighter checks on the token API

A few rules got stricter. If your integration follows the documentation, none of them affect you.

  • A client secret that is sent is always checked. Whatever its value, even an empty string, a request that sends a secret must send the right one, even when PKCE alone would be enough.

  • IP allowlists check the real address. An app account can restrict its code exchanges to a list of addresses in allowed_ips. That check now sees the actual address of the server calling the API and cannot be satisfied by a header the caller writes. If you use an allowlist, list every address your server can call from, IPv6 included. An allowlist that is not a list of addresses is treated as matching nothing.

  • A code is not a session. A code with a PKCE challenge is accepted only at the token exchange, never as a token anywhere else. If your own service accepts your app's codes directly, without the exchange, refuse any code that carries a challenge: only the exchange checks the verifier. Ecency's own services already do.

The documentation

Everything above is written up at hivesigner.com/docs. Start with PKCE for mobile and desktop apps for the full flow, the table of what Hivesigner checks and the exact format of a signed code. The OAuth2, REST API and SDK pages cover the rest. The SDK's own README now points to those pages too.

Hivesigner is open source: the web app, the API and the SDK. Issues and pull requests are welcome.

Build a Hive app your users can sign in to from anywhere. Start at hivesigner.com/docs.

Hivesigner for Mobile and Desktop Apps: PKCE Sign-In, a Faster SDK ... | Ecency