npm package: @flarcos/kiota-authentication-gnap
Description: GNAP (RFC 9635) authentication provider for Kiota-generated API clients with RFC 9421 HTTP Message Signatures
Author: flarcos
Latest version: 0.1.1

# @flarcos/kiota-authentication-gnap

GNAP ([RFC 9635](https://www.rfc-editor.org/rfc/rfc9635.html)) authentication provider for [Microsoft Kiota](https://github.com/microsoft/kiota)-generated API clients.

Enables Kiota-generated SDKs to authenticate with APIs that use the Grant Negotiation and Authorization Protocol (GNAP), such as [Open Payments](https://openpayments.dev/).

## Features

- ✅ **Kiota `AuthenticationProvider` interface** — drop-in integration
- ✅ **RFC 9635 GNAP** — full grant lifecycle (request, continue, rotate, revoke)
- ✅ **RFC 9421 HTTP Message Signatures** — Ed25519 request signing
- ✅ **RFC 9530 Content-Digest** — SHA-256 body integrity
- ✅ **RFC 7638 JWK Thumbprint** — automatic key ID derivation
- ✅ **Token caching** — in-memory store with TTL expiration
- ✅ **Interactive grants** — pluggable `InteractionHandler` for redirect flows

## Installation

```bash
npm install @flarcos/kiota-authentication-gnap @microsoft/kiota-abstractions
```

## Quick Start

```typescript
import { GnapAuthenticationProvider } from "@flarcos/kiota-authentication-gnap";
import { FetchRequestAdapter } from "@microsoft/kiota-http-fetchlibrary";
import fs from "node:fs";

// Create the GNAP auth provider
const authProvider = new GnapAuthenticationProvider({
  authServerUrl: "https://auth.wallet.example/.gnap",
  privateKey: fs.readFileSync("private-key.pem", "utf-8"),
  clientIdentifier: "https://wallet.example/alice",
  accessRights: [
    { type: "incoming-payment", actions: ["create", "read"] },
    { type: "outgoing-payment", actions: ["create", "read"] },
    { type: "quote", actions: ["create", "read"] },
  ],
});

// Use with a Kiota-generated client
const adapter = new FetchRequestAdapter(authProvider);
// const client = new OpenPaymentsClient(adapter);
```

## Usage with Open Payments

Open Payments uses GNAP for authorization. Here's how to create a payment:

```typescript
import { GnapAuthenticationProvider } from "@flarcos/kiota-authentication-gnap";

const authProvider = new GnapAuthenticationProvider({
  // The receiving wallet's auth server
  authServerUrl: "https://auth.interledger-test.dev/",
  // Your Ed25519 private key (PEM format)
  privateKey: process.env.PRIVATE_KEY!,
  // Your wallet address URL
  clientIdentifier: "https://ilp.interledger-test.dev/alice",
  // Request access to create incoming payments
  accessRights: [
    {
      type: "incoming-payment",
      actions: ["create", "read", "complete"],
      identifier: "https://ilp.interledger-test.dev/bob",
    },
  ],
});
```

### Interactive Grants (with user consent)

For operations that require user interaction (e.g., outgoing payments), provide an `InteractionHandler`:

```typescript
import type { InteractionHandler, InteractionResult } from "@flarcos/kiota-authentication-gnap";

class MyInteractionHandler implements InteractionHandler {
  async handleRedirect(redirectUrl: string, nonce: string): Promise<InteractionResult> {
    // Open browser for user to approve
    console.log(`Please visit: ${redirectUrl}`);

    // Wait for callback and return the interact_ref
    return {
      interactRef: "received-interact-ref",
      hash: "received-hash",
    };
  }
}

const authProvider = new GnapAuthenticationProvider({
  authServerUrl: "https://auth.interledger-test.dev/",
  privateKey: process.env.PRIVATE_KEY!,
  clientIdentifier: "https://ilp.interledger-test.dev/alice",
  accessRights: [
    {
      type: "outgoing-payment",
      actions: ["create", "read"],
      identifier: "https://ilp.interledger-test.dev/alice",
    },
  ],
  interact: {
    start: ["redirect"],
    finish: {
      method: "redirect",
      uri: "http://localhost:3000/callback",
      nonce: crypto.randomUUID(),
    },
  },
  interactionHandler: new MyInteractionHandler(),
});
```

## API Reference

### `GnapAuthenticationProvider`

Main class implementing Kiota's `AuthenticationProvider`.

#### Constructor Options

| Option | Type | Required | Description |
|--------|------|----------|-------------|
| `authServerUrl` | `string` | ✅ | Authorization Server grant endpoint |
| `privateKey` | `string` | ✅ | Ed25519 private key (PEM format) |
| `clientIdentifier` | `string` | ✅ | Client identifier (wallet address URL) |
| `accessRights` | `GnapAccessRight[]` | ✅ | Access rights to request |
| `interact` | `object` | ❌ | Interaction mode configuration |
| `interactionHandler` | `InteractionHandler` | ❌ | Handler for interactive grants |
| `tokenStore` | `GnapTokenStore` | ❌ | Token cache (defaults to in-memory) |
| `allowedHosts` | `string[]` | ❌ | Restrict auth to specific hosts |

#### Methods

- `authenticateRequest(request)` — Authenticate a Kiota request (called automatically)
- `rotateToken()` — Manually rotate the current access token
- `revokeToken()` — Revoke the current access token

### Standalone Utilities

```typescript
import {
  createHttpSignature,     // RFC 9421 signing
  computeContentDigest,    // RFC 9530 body hash
  computeJwkThumbprint,    // RFC 7638 key ID
  loadPrivateKey,          // PEM → KeyObject
} from "@flarcos/kiota-authentication-gnap";
```

## How It Works

When `authenticateRequest` is called, the provider:

1. **Checks the token cache** for a valid, non-expired GNAP access token
2. **Negotiates a new grant** if no cached token exists (POST to AS)
3. **Handles interaction** if the AS requires user consent (via `InteractionHandler`)
4. **Sets the `Authorization: GNAP <token>` header** on the request
5. **Computes `Content-Digest`** (SHA-256) for requests with a body
6. **Signs the request** with RFC 9421 HTTP Message Signatures (Ed25519)

## Standards Compliance

| Standard | Description | Coverage |
|----------|-------------|----------|
| [RFC 9635](https://www.rfc-editor.org/rfc/rfc9635.html) | GNAP Core Protocol | §2 Grant Request, §3 Response, §5 Continuation, §6 Token Management |
| [RFC 9421](https://www.rfc-editor.org/rfc/rfc9421.html) | HTTP Message Signatures | §2 Signature Base, §3 Creating Signatures (Ed25519) |
| [RFC 9530](https://www.rfc-editor.org/rfc/rfc9530.html) | Content-Digest | SHA-256 digest |
| [RFC 7638](https://www.rfc-editor.org/rfc/rfc7638.html) | JWK Thumbprint | SHA-256 thumbprint for key identification |

## Project Structure

```
src/
├── index.ts                      # Public exports
├── gnapAuthenticationProvider.ts  # Kiota AuthenticationProvider implementation
├── gnapAccessTokenProvider.ts    # GNAP grant lifecycle
├── gnapTokenStore.ts             # In-memory token cache
├── httpMessageSigner.ts          # RFC 9421 HTTP Message Signatures
├── contentDigest.ts              # RFC 9530 Content-Digest
├── keyManagement.ts              # Ed25519 key utilities
├── types.ts                      # Type definitions
└── errors.ts                     # Error classes
```

## License

Apache-2.0

## Acknowledgements

This package was created as part of the [Interledger Foundation Open Payments SDK Grant Program](https://interledger.org/grant/open-payments-sdk).