Skip to content

Add did:nostr SSO and Schnorr (NIP-98) authentication #4

@melvincarvalho

Description

@melvincarvalho

Summary

Add support for Nostr-based authentication to JSS, enabling both:

  1. Single Sign-On (SSO): Login with Nostr keys to obtain a WebID/session
  2. Direct Authorization: Use did:nostr:pubkey URIs directly in ACL files

This brings decentralized, passwordless authentication using Schnorr signatures (secp256k1) to Solid.

Background & Research

Relevant Specifications

Spec Description URL
NIP-98 HTTP Auth using Nostr events (kind 27235) https://nips.nostr.com/98
NIP-07 Browser extension API (window.nostr) https://nips.nostr.com/7
NIP-97 Login with Nostr (proposed) nostr-protocol/nips#1042
did:nostr DID method for Nostr pubkeys https://nostrcg.github.io/did-nostr/
HTTP Schnorr Auth W3C CG spec for Schnorr HTTP auth https://nostrcg.github.io/http-schnorr-auth/
Nostr-OIDC Adapting Solid-OIDC for Nostr https://melvincarvalho.github.io/nostr-oidc/

Existing Implementations

Browser Extensions (Client-side signing)

  • nos2x - Chrome extension implementing NIP-07
  • Alby - Lightning + Nostr wallet with NIP-07
  • nostr-keyx - Uses OS keychain/YubiKey for key storage

Proposed Implementation

Phase 1: Direct NIP-98 Authorization (ACL-based)

Goal: Accept Authorization: Nostr <base64-event> header for resource access

Flow:

Client                          JSS
  |                              |
  |-- Authorization: Nostr ... ->|
  |                              | 1. Decode base64 event
  |                              | 2. Verify Schnorr signature (BIP-340)
  |                              | 3. Validate: kind=27235, timestamp, URL, method
  |                              | 4. Extract pubkey → did:nostr:pubkey
  |                              | 5. Check ACL for did:nostr:pubkey
  |<-- 200 OK -------------------|

ACL Example:

@prefix acl: <http://www.w3.org/ns/auth/acl#>.

<#nostrUser>
    a acl:Authorization;
    acl:agent <did:nostr:63fe6318dc58583cfe16810f86dd09e18bfd76aabc24a0081ce2856f330504ed>;
    acl:accessTo </shared/data>;
    acl:mode acl:Read, acl:Write.

Changes Required:

  1. Add nostr-tools dependency
  2. Create src/auth/nostr.js with NIP-98 validation
  3. Update src/auth/token.js:getWebIdFromRequestAsync() to detect Nostr scheme
  4. Return did:nostr:pubkey as the agent identifier (ACL checker already handles this!)

Phase 2: SSO with WebID Linking

Goal: Login with Nostr to get a session, optionally create/link a WebID

Two Sub-flows:

A. Nostr → Existing WebID (Account Linking)

User has both a Nostr key AND a JSS account. Link them:

  1. User logs in normally (username/password)
  2. User links Nostr pubkey in settings
  3. Account stores nostrPubkey field
  4. WebID profile gets owl:sameAs or foaf:account:
{
  "@id": "https://example.com/alice/#me",
  "owl:sameAs": "did:nostr:63fe6318dc58583..."
}
  1. Future logins can use NIP-98 to authenticate

B. Nostr-First Registration (New Users)

User has only a Nostr key, wants a pod:

  1. User visits /idp/nostr-login
  2. Browser extension signs NIP-98 event
  3. JSS verifies signature, extracts pubkey
  4. If pubkey not linked → offer to create account:
    • Auto-generate username from npub (e.g., npub1abc...nostr_abc123)
    • Create pod with WebID containing owl:sameAs did:nostr:...
  5. Issue session token

WebID Profile with Nostr Identity:

{
  "@context": {
    "nostr": "https://w3id.org/nostr/vocab#",
    "owl": "http://www.w3.org/2002/07/owl#"
  },
  "@id": "https://example.com/alice/#me",
  "@type": "foaf:Person",
  "foaf:name": "alice",
  "owl:sameAs": "did:nostr:63fe6318dc58583cfe16810f86dd09e18bfd76aabc24a0081ce2856f330504ed",
  "nostr:pubkey": "63fe6318dc58583cfe16810f86dd09e18bfd76aabc24a0081ce2856f330504ed"
}

Phase 3: Enhanced Integration (Optional)

  • Relay Discovery: Query Nostr relays for kind 0 (profile metadata) to populate WebID
  • Bidirectional Linking: Add WebID to Nostr profile's alsoKnownAs field
  • NIP-97 Support: Implement /.well-known/nostr/nip97 endpoint

Technical Details

NIP-98 Event Structure

{
  "id": "<sha256-hash>",
  "pubkey": "63fe6318dc58583cfe16810f86dd09e18bfd76aabc24a0081ce2856f330504ed",
  "created_at": 1682327852,
  "kind": 27235,
  "tags": [
    ["u", "https://example.com/resource"],
    ["method", "GET"],
    ["payload", "<sha256-of-body>"],  // optional, for POST/PUT
    ["webid", "https://example.com/alice/#me"]  // optional
  ],
  "content": "",
  "sig": "<schnorr-signature>"
}

Validation Requirements (per spec)

  1. kind MUST be 27235
  2. created_at within ±60 seconds
  3. u tag MUST match absolute request URL
  4. method tag MUST match HTTP method
  5. sig MUST be valid secp256k1 Schnorr signature (BIP-340)
  6. Optional: payload tag matches SHA-256 of request body

did:nostr Format

did:nostr:63fe6318dc58583cfe16810f86dd09e18bfd76aabc24a0081ce2856f330504ed
         └─────────────────────────────────────────────────────────────────┘
                              64-char lowercase hex pubkey

Files to Modify/Create

File Change
package.json Add nostr-tools dependency
src/auth/nostr.js NEW - NIP-98 validation logic
src/auth/token.js Add Nostr scheme detection in getWebIdFromRequestAsync()
src/idp/accounts.js Add nostrPubkey field to accounts
src/idp/index.js Add /idp/nostr-login route
src/idp/views.js Add Nostr login page with NIP-07 integration
src/webid/profile.js Add owl:sameAs/nostr:pubkey to profile generation

Effort Estimate

Phase Effort Priority
Phase 1 (ACL auth) 2-3 hours High
Phase 2a (Account linking) 2 hours Medium
Phase 2b (Nostr-first registration) 3 hours Medium
Phase 3 (Enhanced) 4+ hours Low

Security Considerations

  • Replay Protection: 60-second timestamp window
  • URL Binding: Event signed with exact URL prevents cross-site use
  • Key Security: Private keys never leave client (NIP-07 extensions)
  • HTTPS Required: Except for .onion services

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    nostrNostr relay, did:nostr auth, NIP-related

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions