npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@amrshbib/react-native-hmac

v0.0.1

Published

Native HMAC-SHA256 signer for React Native. The signing secret stays in native code; the JS layer never holds it. Plug-and-play helpers for socket.io, fetch, and axios.

Readme

react-native-hmac

Native HMAC-SHA256 signer for React Native. Returns a fresh, replay-resistant signature you can attach to any transport — Socket.IO, fetch / axios, SignalR, raw WebSocket, gRPC-Web, MQTT — anywhere you need to prove that a request came from your app.

The signing secret lives only in compiled native resources (Android string resource / iOS Info.plist). The JS layer never holds it, so it cannot be extracted from the Hermes/JSC bundle.

  • One synchronous function. getSignature() returns { signature, timestamp, version } — no Promise, no await. HMAC over the small canonical payload completes in under a millisecond, so the cost of blocking the JS thread is negligible.
  • Transport-agnostic. Zero coupling to any specific networking library.
  • Native signing. javax.crypto.Mac on Android, CryptoKit on iOS.
  • Zero JS crypto dependencies. No crypto-js, no react-native-quick-crypto.
  • Zero secret exposure to JS. No react-native-config, no .env bundling.
  • Replay-resistant. Every signature includes a fresh timestamp.

Requires Hermes or on-device JSC. The native method is declared as a blocking synchronous bridge call. It does not work in the Chrome remote JS debugger. It works fine in Hermes (default in modern RN), in the Hermes/JSC debugger, and on real devices.


Quick start

yarn add react-native-hmac
cd ios && pod install
import { getSignature } from "react-native-hmac";

const { signature, timestamp, version } = getSignature();
// → { signature: "ab12…", timestamp: 1716220000000, version: "v1" }

That's the whole client-side API. No await, no promise, no setup beyond configuring the native secret once (below).


Configure the native secret (one-time per app)

Generate the secret:

openssl rand -hex 32

Android — one line

Add to android/local.properties (already in .gitignore):

HMAC_SECRET=...your-generated-secret...

That's it. The library's own build.gradle reads this value at build time and injects it as the Android string resource react_native_hmac_secret, which is merged into your APK's resources.arsc. Nothing to add to your app's build.gradle.

Alternative sources (checked in order):

  1. -PHMAC_SECRET=... Gradle property — handy for CI.
  2. HMAC_SECRET environment variable.
  3. HMAC_SECRET key in android/local.properties.

To override per-flavor or per-build-type, declare your own resValue with the same name in android/app/build.gradle — app-level resValues win over library-level ones during Android's resource merge.

iOS

  1. Create ios/Secrets.xcconfig (gitignored):

    REACT_NATIVE_HMAC_SECRET = ...your-generated-secret...
  2. Reference it from your target's xcconfig.

  3. Add to ios/<YourApp>/Info.plist:

    <key>ReactNativeHmacSecret</key>
    <string>$(REACT_NATIVE_HMAC_SECRET)</string>

Recipes — same getSignature(), any transport

Socket.IO

import io from "socket.io-client";
import { getSignature } from "react-native-hmac";

const socket = io(url, {
  auth: (cb) => cb(getSignature()),
});

The function form of auth is called by socket.io-client on every (re)connect, so the timestamp is always fresh.

Fetch

import { getSignature } from "react-native-hmac";

const { signature, timestamp, version } = getSignature();
const res = await fetch(url, {
  headers: {
    "x-hmac-signature": signature,
    "x-hmac-timestamp": String(timestamp),
    "x-hmac-version": version,
  },
});

Axios (request interceptor)

import axios from "axios";
import { getSignature } from "react-native-hmac";

const api = axios.create({ baseURL: "https://api.example.com" });
api.interceptors.request.use((config) => {
  const { signature, timestamp, version } = getSignature();
  config.headers["x-hmac-signature"] = signature;
  config.headers["x-hmac-timestamp"] = String(timestamp);
  config.headers["x-hmac-version"] = version;
  return config;
});

SignalR

import { HubConnectionBuilder } from "@microsoft/signalr";
import { getSignature } from "react-native-hmac";

const connection = new HubConnectionBuilder()
  .withUrl(url, {
    accessTokenFactory: () => {
      const { signature, timestamp } = getSignature();
      return `${timestamp}.${signature}`;
    },
  })
  .build();

Raw WebSocket

import { getSignature } from "react-native-hmac";

const { signature, timestamp } = getSignature();
const ws = new WebSocket(`${url}?ts=${timestamp}&sig=${signature}`);

Reusable signer with bound claims

import { createSigner } from "react-native-hmac";

const signWithTenant = createSigner({ claims: { tenantId: "t_42" } });

const a = signWithTenant();  // fresh timestamp, same claim bound
const b = signWithTenant();  // fresh timestamp, same claim bound

Startup health-check

import { isConfigured } from "react-native-hmac";

if (!isConfigured()) {
  // The native secret wasn't wired up in this build.
}

API

getSignature(options?: SignOptions): Signature
createSigner(options?: SignOptions): () => Signature
isConfigured(): boolean
buildPayloadString(timestamp: number, claims?): string

class HmacNotLinkedError      // app not rebuilt
class HmacSecretMissingError  // native resource not populated
class HmacSignError           // anything else

Everything is synchronous. Errors are thrown synchronously — wrap in try/catch if you want graceful degradation; otherwise let them propagate so missing configuration fails loud and early.

Options

type SignOptions = {
  /** Bound INTO the HMAC. The verifier must receive the same keys/values
   *  and recompute the canonical payload to validate. */
  claims?: Record<string, string | number | boolean>;
};

type Signature = {
  signature: string;   // lowercase hex
  timestamp: number;   // epoch ms
  version: "v1";
};

Canonical payload

The native side hashes one of:

v1:<timestamp>
v1:<timestamp>:<sortedJsonClaims>

Claims are JSON-stringified with keys sorted alphabetically and no whitespace — any conformant JSON encoder reproduces the exact bytes, so any verifier on the other end can recompute the same string.


Verifying signatures (any backend / any language)

The canonical payload format is trivial to reproduce. Node.js example:

const { createHmac, timingSafeEqual } = require("node:crypto");

function canonicalPayload(timestamp, claims) {
  if (!claims || Object.keys(claims).length === 0) return `v1:${timestamp}`;
  const sorted = {};
  for (const k of Object.keys(claims).sort()) sorted[k] = claims[k];
  return `v1:${timestamp}:${JSON.stringify(sorted)}`;
}

function verify({ secret, signature, timestamp, claims, windowMs = 60_000 }) {
  if (Math.abs(Date.now() - timestamp) > windowMs) return false;
  const expected = createHmac("sha256", secret)
    .update(canonicalPayload(timestamp, claims))
    .digest("hex");
  if (expected.length !== signature.length) return false;
  return timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(signature, "hex"));
}

Use the same secret on both sides — the value in android/local.properties on the device build must equal process.env.HMAC_SECRET (or wherever) on your backend.

Always check the timestamp is within a small window of now (60 s is a sensible default) to prevent replay.


Security FAQ

Q. Why not just use crypto-js and react-native-config? Because both put the secret in your JS bundle. react-native-config injects .env values at build time as JS constants; crypto-js then needs the secret in memory. An attacker with the APK can run strings index.android.bundle | grep. This library keeps the secret in native resources and computes HMAC inside Kotlin/Swift — JS never sees the key, even at runtime.

Q. Can the secret still be extracted? On a rooted device with Frida, an attacker can hook Mac.doFinal() and observe the key in process memory. This is unsolvable for any pure client-side scheme. Mitigations: root/jailbreak detection, certificate pinning, server-side anomaly detection, periodic server-driven secret rotation.

Q. Why HMAC and not JWT or asymmetric signatures? HMAC is symmetric — one shared secret, simpler ops, ~10× faster signing than ECDSA. If you also need per-user identity, layer a JWT on top of an HMAC-signed request.

Q. Why is the API synchronous? Doesn't that block the JS thread? The native bridge call is declared as a blocking synchronous method. HMAC over a tiny payload completes in well under a millisecond, so the thread is unblocked before a single frame is dropped. The benefit is a dramatically cleaner API — no async contagion through your code just because you wanted to sign a request.

Q. Does this work in Expo Go? No — Expo Go does not include arbitrary native modules. Use a development build (eas build --profile development) or the bare workflow.

Q. Does this work in the Chrome remote JS debugger? No — synchronous native calls aren't supported in the Chrome debugger. Use Hermes inspector or on-device debugging. (Most modern RN setups use Hermes by default.)


License

MIT