{"id":19178042,"url":"https://github.com/cmdruid/crypto-sessions","last_synced_at":"2025-09-09T19:04:22.779Z","repository":{"id":65557516,"uuid":"580230051","full_name":"cmdruid/crypto-sessions","owner":"cmdruid","description":"Encrypted, pubkey based sessions and API requests. No cookies, storage, or state management required. Just some modern key cryptography!","archived":false,"fork":false,"pushed_at":"2023-04-11T23:41:49.000Z","size":260,"stargazers_count":7,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-05-07T20:43:52.353Z","etag":null,"topics":["api","e2e","ecdsa","encryption","fetch","middleware","schnorr","session-management","signatures"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cmdruid.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-12-20T03:15:37.000Z","updated_at":"2024-07-03T14:56:29.000Z","dependencies_parsed_at":"2024-10-06T16:42:37.328Z","dependency_job_id":"584e1ee7-12c3-45b0-92db-5a608289b16d","html_url":"https://github.com/cmdruid/crypto-sessions","commit_stats":{"total_commits":38,"total_committers":1,"mean_commits":38.0,"dds":0.0,"last_synced_commit":"dc48725554011584928893f957d63a5380c16ce0"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cmdruid%2Fcrypto-sessions","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cmdruid%2Fcrypto-sessions/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cmdruid%2Fcrypto-sessions/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cmdruid%2Fcrypto-sessions/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cmdruid","download_url":"https://codeload.github.com/cmdruid/crypto-sessions/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252954143,"owners_count":21830895,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","e2e","ecdsa","encryption","fetch","middleware","schnorr","session-management","signatures"],"created_at":"2024-11-09T10:36:18.814Z","updated_at":"2025-05-07T20:44:17.236Z","avatar_url":"https://github.com/cmdruid.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Crypto Sessions\n\nSecure end-to-end client sessions \u0026 API calls. No cookies, storage, or state management required. Just modern key cryptography!\n\n## Description\n\nThe goal of this project is to demo the use of private/public key pairs for user authentication, rather than session cookies or passwords.\n\nThe advantage of this approach is that user authentication is done in real-time. On-boarding is immediate and registration can be optional (though spam / sybil attacks are still an issue).\n\nThe disadvantage of this approach is that key management is now pushed into the hands of the user / client, which is a much different paradigm. Browsers handle this responsibility poorly (XSS attacks anyone?), and the loss / leaking of passwords is still an issue.\n\nA side-benefit of this approach is that all requests/responses are cryptgraphically signed end-to-end, and data payloads are encrypted end-to-end as well.\n\nThe session middleware is also simplified to just checking the signature on each request. This is contrast to auth providers that involve handling tokens and intermediary requests (which can be hijacked).\n\n## This is dumb. Why change things?\n\nIt is a fun experiment! Plus, using public keys for identity works well with cryptocurrency and other emerging protocols (like nostr? ;-)).\n\nThere's also a severe lack of tooling when it comes to client-side key management. DIDs show some promise, but so far there is nothing material.\n\nBut I like to experiement, and state-less user sessions sounds like it would be fun for small projects. Maybe it will help push further development and tooling for self-custody of keys. Let me know what you think!\n\n## How to Use\n\nThis library is designed to work in both Nodejs and browser environments. This library should also work within Cloudflare workers (but I have not tried it yet).\n\nPackages are available on NPM as **@cmdcode/crypto-sessions** with CDN support:\n```html\n\u003cscript src=\"https://unpkg.com/@cmdcode/crypto-sessions\"\u003e\u003c/script\u003e\n```\n\nThe library contains three main components: \n\n  1. A fetch client (for sending requests to a server)\n  2. Session handler (can be used client \u003c-\u003e server or standalone p2p)\n  3. Server middleware (for receiving requests from a client).\n\n### Fetch Client\n\nFor traditional client requests, this library provides a `SecureFetch` method that works as a drop-in replacement for the `fetch()` method API.\n\n```ts\nimport { SecureFetch } from '@cmdcode/crypto-sessions'\n\n/* Returns a secure fetch method for making requests. */\n\nconst fetch = new SecureFetch(\n  // Used for encrypting traffic end-to-end.\n  serverPubkey    : string | Uint8Array,\n  // Used for signing each request.\n  clientSecretKey : string | Uint8Array,\n  // Extends the default Request object.\n  options? : SecureFetchOptions\n)\n\n/* Options for initializing SecureFetch. */\n\ninterface SecureFetchOptions {\n  hostname? : string,    // Provides a default hostname to prepend to requests.\n  fetcher?  : Function,  // Use to set a custom fetcher method. Defaults to fetch.\n  verbose?  : boolean,   // Dumps a copy of all outgoing requests to console. useful for debugging!\n  ...Request             // All Request options will work here, and act as defaults for each request.\n}\n```\nThe new method should act as a drop-in replacement for fetch, and can be configured using the typical `Request` API. \n\n```ts\nconst res : SecureResponse = await fetch(\n  'http://localhost:3000/api/endpoint',\n  { \n    headers : { 'key' : 'value' },\n    method  : 'GET' | 'POST',\n    body    : { content: 'hello world!' }\n  }\n)\n```\nThe returned `Response` object will also be similar, but include a few extra fields.\n\n```ts\n// The response will look slightly different.\ninterface SecureResponse {\n  ...Response  // Typical Response object.\n  data? : any  // Decrypted data returned from the server, if any.\n  err?  : any  // Authentication errors returned from the client, if any.\n}\n```\n\n### Session Handler\n\nUnderneath the hood, the `SecureFetch` client is using your keys to create a new `CryptoSession` object. This object is used to sign and encrypt requests to the server, plus decrypt and verify the server response.\n\nt\n\n```ts\n// Example import of the CryptoSession object.\nimport { CryptoSession } from '@cmdcode/crypto-sessions'\n\n/* Create a client \u003c-\u003e peer CryptoSession instance.\n * Your peer must also configure a matching instance.\n */\nconst session = new CryptoSession(\n  // Used for encrypting traffic end-to-end.\n  peerPublickey : string | Uint8Array,\n  // Used for signing each request.\n  yourSecretKey : string | Uint8Array\n)\n\n// Encode a data payload to send outbound.\nconst { \n  token   : string, // Base64urlEncode(clientPubKey + signature)\n  payload : string  // Base64urlEncode(encrypted(payload))\n} = await session.encode(data : string | object)\n\n// Decode and verify an incoming payload.\nconst { data, isValid } = await session.decode(token, payload)\n```\n\nWhen sending a request, `yourSecretKey` is used to perform the following:\n  * For `GET` : Hash + sign the full URL of the request (including query string).\n  * For `POST`: Hash + sign the contents of req.body.\n  * For `POST`: Encrypt the contents of req.body using the peerPublicKey.\n  * Provide a token (pubkey + signature) for decryption and verification.\n\nThe `peerPublicKey` is used to encrypt the outgoing payload, plus decrypt and verify the returned response.\n\nYou can use the `encode` and `decode` methods on the `CryptoSession` object to establish end-to-end signed and encrypted connection with another peer (ex. via websockets or nostr :-)), or your can use it to authenticate with a traditional HTTP server via a middleware function.\n\nFull API of the `CryptoSession` object:\n\n### Server Middleware\n\nFor convenience, this package includes a generic middleware function, plus a wrapper for **Express** `(req, res, next)` and **NextJs** `(req, res)` style servers.\n\n```ts\nimport { useCryptoAuth } from '@cmdcode/crypto-sessions'\n\n/* We have to set some environment variables first. */\n\n// The hostname of your server. The client\n// will include your hostname when they sign.\nprocess.env.CRYPTO_SESSION_HOST\n\n// The secret key to use for establishing sessions.\nprocess.env.CRYPTO_SESSION_KEY\n\n/* Example of a generic middleware function. */\n\nexport async function useMiddleWare(\n  req: Request, res: Response, next: NextFunction\n) {\n  try {\n    // Apply middleware\n    await useCryptoAuth(req, res)\n    // Return next function.\n    return next()\n  } catch (err) {\n    // Catch any errors, return failed response.\n    console.log(err)\n    return res.status(401).end()\n  }\n}\n```\n\nThe `useCryptoAuth` middleware will check `req.headers.authorization` for any tokens, then use it to create a `CryptoSession` that decrypts and verifies the request.\n\nOnce verified, the `CryptoSession` instance is stored in `req.session`, with linked response methods stored in `res.secure`.\n\n```ts\n// Example express API endpoint.\napp.get('http://localhost:3001/api/hello?name=world!', async (\n  req : Request, \n  res : Response\n) : =\u003e {\n\n  // A helper boolean is provided for checking authentication status.\n  req.isAuthorized = true | false\n\n  // You have access to the client/server CryptoSession object.\n  const {\n    peerKey       // The public key of the client.\n    peerHex       // Hex-encoded string of the peerKey.\n    pubKey        // The public key of your server.\n    sharedSecret  // The shared secret between the client \u003c-\u003e server.\n    sharedHash    // The sha256 hash of the shared secret.\n  } = req.session\n\n  // You also have access to all CryptoSession methods.\n  req.session\n    .encode()   // Same encoding method as above.\n    .decode()   // Same decoding method as above.\n    .sign()     // Provides a signature for the provided payload.\n    .verify()   // Verifies a payload signature and key.\n    .encrypt()  // Encrypt an outgoing payload using current CryptoSession.\n    .decrypt()  // Decrypt an incoming payload using current CryptoSession.\n\n  // In addition, you have access to secure response methods.\n  // Use these methods to send a secured response to the client.\n  res.secure\n    .send(data: string, status: number) =\u003e Promise\u003cResponse\u003e\n    .json(data: object, status: number) =\u003e Promise\u003cResponse\u003e\n})\n```\n\nExample of using `useAuthWithExpress` middleware method with Express.\n\n```ts\n// Example import of the middleware.\nimport { useAuthWithExpress } from '@cmdcode/crypto-sessions'\n\n// Example configuration of the express server.\nconst app = express()\n\napp.use(express.urlencoded())\napp.use(express.json())\napp.use(useAuthWithExpress)\n\napp.listen(3000)\n```\n\nExample of using `useAuthWithNext` middleware method with NextJs.\n\n```ts\n// Example import of the middleware.\nimport { useAuthWithNext } from '@cmdcode/crypto-sessions'\n\n// Example Next API method.\nasync function helloAPI(\n  req : NextApiRequest, \n  res : NextApiResponse\n) {\n  const { name } = req.query\n  return res.secure.json({ message: `Hello ${name}!` })\n}\n\n// The middleware is used to wrap the method export.\nexport default useAuthWithNext(helloAPI)\n```\n\n## Cryptography\n\nThis library uses the Secp256k1 curve for cryptography, ECDH for shared secret derivation, AES-CBC for encryption, and Schnorr for signatures.\n\n## Improvements / TODO\n\n* Write better tests for the edge cases.\n* Clean up the zod schemas and add better error messages.\n* Overall more verbose error checking for failed authentication.\n* Add support for key tweaking and tweak verification.\n\n## Questions / Issues\nFeel free to ask questions and submit issues. All are welcome!\n\n## Contributing\nLooking for contributors. Feel free to contribute!\n\n## Resources\n\nThis project aims to be very light-weight. ith minimal dependencies.\n\n**@noble/secp256k1**  \nImplementation of secp256k1 in Javascript.  \nhttps://github.com/paulmillr/noble-secp256k1\n\n**zod**  \nRun-time schema validation with static type inference.  \nhttps://github.com/colinhacks/zod\n\n**Crypto-Utils**  \nUtility library that wraps WebCrypto and @noble/secp.  \nhttps://github.com/cmdruid/crypto-utils\n\n**Buff-Utils**  \nUtility library for working with byte arrays.  \nhttps://github.com/cmdruid/bytes-utils\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcmdruid%2Fcrypto-sessions","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcmdruid%2Fcrypto-sessions","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcmdruid%2Fcrypto-sessions/lists"}