diff --git a/public/llms/complete/llms-full.txt b/public/llms/complete/llms-full.txt index 18946b5..d757bb6 100644 --- a/public/llms/complete/llms-full.txt +++ b/public/llms/complete/llms-full.txt @@ -1,6 +1,6 @@ # Aggregated Ethereum Identity Context - Full -> Updated at: 22:35 05/28/25 +> Updated at: 21:35 08/27/25 # docs.efp.app llms-full.txt @@ -23837,7 +23837,7 @@ Issued At: 2023-01-30T00:00:00.000Z ## Implementations -The team at [SpruceID](https://login.xyz) has done a phenomenal job writing plug-and-play utilities that let you easily integrate ethereum-based authentication into your project. +The team at [SIWE](https://siwe.xyz) has done a phenomenal job writing plug-and-play utilities that let you easily integrate ethereum-based authentication into your project. Whether you are using [Next.js](todo-link), [React](todo-link), or [OpenID Connect](todo-link). In addition to the above SIWE has been integrated into [connectkit](todo-link) & more. @@ -36930,2477 +36930,6149 @@ contract DummyNonCCIPAwareResolver is IExtendedResolver, ERC165 { ``` --- - -# docs.login.xyz llms.txt +# docs.siwe.xyz llms.txt > Offering resources and guidance for integrating Sign-In with Ethereum, enhancing user control over digital identities in web applications, while promoting best practices and supporting community involvement within the Ethereum ecosystem. -Sign-In with EthereumYour Keys, Your IdentifierNextQuickstart GuideLast updated 3 years ago +> Updated at: 21:35 08/27/25 -Your Keys, Your Identifier +# ⭐ Deployment Guide -Last updated 3 years ago +This guide covers deploying the SIWE OIDC Provider in production environments. Choose from multiple deployment options based on your infrastructure needs. -Sign-In with Ethereum is a new form of authentication that enables users to control their digital identity with their Ethereum account and ENS profile instead of relying on a traditional intermediary. Already used throughout Web3, this effort standardizes the method with best practices and makes it easy to adopt securely. +## Deployment Options -To hop right in, check out our Quickstart Guide. +The SIWE OIDC Provider can be deployed in two primary modes: -Sign-in With Ethereum was a standard built collaboratively with the greater Ethereum community. For more information on the EIP, check out the following page: +1. **[Cloudflare Workers](#cloudflare-workers-deployment)** - Serverless, globally distributed +2. **[Standalone Binary](#standalone-binary-deployment)** - Self-hosted with full control -For more information on Sign-In with Ethereum and its related benefits to both the Web3 ecosystem and Web2 services, check out the following page: +## Prerequisites -πŸ’» Login.xyz - Check out the Sign-In with Ethereum home page for more information about supporters, and recent activity. +### General Requirements -πŸ‘Ύ Discord - Join the #sign-in-with-ethereum channel in the Spruce Discord Server for additional support. +- Domain name with HTTPS support +- Basic knowledge of OIDC flows +- Client applications that support OpenID Connect -πŸ“– Blog - Check out the latest updates on Sign-In with Ethereum posted on the Spruce blog. +### For Standalone Deployment -We host a Sign-In with Ethereum community where we discuss relevant updates, new libraries, additional integrations, and more. If you're interested in contributing to Sign-In with Ethereum, we encourage that you join the calls by filling in this form. +- **Redis** database instance +- **Docker** or container runtime (recommended) +- **Reverse proxy** (nginx, Apache, or cloud load balancer) -# Quickstart Guide +### For Cloudflare Workers -Sign-In with EthereumQuickstart GuideThis guide will show how to implement Sign-In with Ethereum (SIWE) in a client-server JavaScript web application.PreviousSign-In with EthereumNextCreating SIWE MessagesLast updated 3 years ago +- **Cloudflare account** with Workers enabled +- **Wrangler CLI** installed locally -This guide will show how to implement Sign-In with Ethereum (SIWE) in a client-server JavaScript web application. +## Cloudflare Workers Deployment -Requirements +Cloudflare Workers provide a serverless, globally distributed deployment option. -An Ethereum account in the installed MetaMask wallet +### 1. Setup Repository -The repository for this tutorial can be found here: +```bash +# Clone the SIWE OIDC repository +git clone https://github.com/signinwithethereum/siwe-oidc +cd siwe-oidc +``` -# Creating SIWE Messages +### 2. Install Wrangler CLI -Sign-In with EthereumQuickstart GuideCreating SIWE MessagesThis section describes how to generate Sign-In with Ethereum messages and print them to the console.PreviousQuickstart GuideNextImplement the FrontendLast updated 2 years ago +```bash +# Install Wrangler globally +npm install -g @cloudflare/wrangler -This section describes how to generate Sign-In with Ethereum messages and print them to the console. +# Or install locally in project +npm install --save-dev @cloudflare/wrangler +``` -Last updated 2 years ago +### 3. Authenticate with Cloudflare -A completed version of this part can be found in the example repository (). +```bash +# Login to Cloudflare +wrangler auth -Creating SIWE messages in JavaScript is straightforward when using the siwe library in npm. To begin, create a new project called siwe-print. +# Verify authentication +wrangler whoami +``` -siwe +### 4. Create KV Namespace -siwe-print +KV storage is used for session and client data: -mkdir siwe-print && cd siwe-print/ +```bash +# Create production KV namespace +wrangler kv:namespace create "SIWE_OIDC_KV" -yarn init --yes +# Create preview KV namespace for staging +wrangler kv:namespace create "SIWE_OIDC_KV" --preview +``` -yarn add siwe ethers +### 5. Configure wrangler.toml -mkdir src/ +Update `wrangler.toml` with your account details: -We can then write the following into ./src/index.js: +```toml +name = "siwe-oidc-provider" +type = "webpack" +account_id = "your-account-id" +workers_dev = true +route = "" +zone_id = "" -./src/index.js +[build] +command = "npm run build" -const siwe = require('siwe'); +[build.upload] +format = "service-worker" -const domain = "localhost"; +[[kv_namespaces]] +binding = "SIWE_OIDC_KV" +id = "your-kv-namespace-id" +preview_id = "your-preview-kv-namespace-id" -const origin = "https://localhost/login"; +[vars] +SIWEOIDC_BASE_URL = "https://your-worker.your-subdomain.workers.dev" +``` -function createSiweMessage (address, statement) { +### 6. Deploy to Cloudflare - const siweMessage = new siwe.SiweMessage({ +```bash +# Deploy to production +wrangler publish - domain, +# Deploy to preview environment +wrangler publish --env preview +``` - address, +### 7. Configure Custom Domain (Optional) - statement, +```bash +# Add custom domain +wrangler route add "oidc.yourdomain.com/*" your-zone-id +``` - uri: origin, +## Standalone Binary Deployment - version: '1', +For self-hosted environments, deploy as a standalone service with Redis. - chainId: '1' +### 1. Using Docker (Recommended) - }); +#### Quick Start - return siweMessage.prepareMessage(); +```bash +# Run with docker-compose (includes Redis) +curl -O https://raw.githubusercontent.com/spruceid/siwe-oidc/main/docker-compose.yml +docker-compose up -d +``` -} +#### Manual Docker Deployment + +```bash +# Start Redis container +docker run -d --name redis \ + -p 6379:6379 \ + redis:7-alpine + +# Run SIWE OIDC Provider +docker run -d --name siwe-oidc \ + -p 8000:8000 \ + -e SIWEOIDC_ADDRESS="0.0.0.0" \ + -e SIWEOIDC_PORT="8000" \ + -e SIWEOIDC_REDIS_URL="redis://redis:6379" \ + -e SIWEOIDC_BASE_URL="https://oidc.yourdomain.com" \ + --link redis \ + ghcr.io/spruceid/siwe_oidc:latest +``` + +### 2. Using Docker Compose + +Create `docker-compose.yml`: + +```yaml +version: '3.8' + +services: + redis: + image: redis:7-alpine + restart: unless-stopped + volumes: + - redis_data:/data + healthcheck: + test: ['CMD', 'redis-cli', 'ping'] + interval: 10s + timeout: 5s + retries: 3 + + siwe-oidc: + image: ghcr.io/spruceid/siwe_oidc:latest + restart: unless-stopped + ports: + - '8000:8000' + environment: + - SIWEOIDC_ADDRESS=0.0.0.0 + - SIWEOIDC_PORT=8000 + - SIWEOIDC_REDIS_URL=redis://redis:6379 + - SIWEOIDC_BASE_URL=https://oidc.yourdomain.com + - SIWEOIDC_RSA_PEM=${SIWEOIDC_RSA_PEM:-} + depends_on: + - redis + healthcheck: + test: + [ + 'CMD', + 'curl', + '-f', + 'http://localhost:8000/.well-known/openid-configuration', + ] + interval: 30s + timeout: 10s + retries: 3 + +volumes: + redis_data: +``` + +Deploy with: + +```bash +docker-compose up -d +``` -console.log(createSiweMessage( +### 3. Binary Installation - "0x6Ee9894c677EFa1c56392e5E7533DE76004C8D94", +For direct binary installation: - "This is a test statement." +```bash +# Download latest release +wget https://github.com/spruceid/siwe-oidc/releases/latest/download/siwe-oidc-linux-x86_64 +chmod +x siwe-oidc-linux-x86_64 - )); +# Run with environment variables +SIWEOIDC_REDIS_URL=redis://localhost:6379 \ +SIWEOIDC_BASE_URL=https://oidc.yourdomain.com \ +./siwe-oidc-linux-x86_64 +``` -Now run the example: +## Configuration Options -node src/index.js +### Environment Variables -You should see output similar to the following message, with different values for the Nonce and Issued At fields: +| Variable | Description | Default | Required | +| -------------------- | ------------------------------- | ------------------------ | -------- | +| `SIWEOIDC_ADDRESS` | IP address to bind to | `127.0.0.1` | No | +| `SIWEOIDC_PORT` | Port to listen on | `8000` | No | +| `SIWEOIDC_REDIS_URL` | Redis connection URL | `redis://localhost:6379` | Yes | +| `SIWEOIDC_BASE_URL` | Public-facing base URL | None | Yes | +| `SIWEOIDC_RSA_PEM` | RSA private key for JWT signing | Auto-generated | No | -localhost wants you to sign in with your Ethereum account: +### Advanced Configuration -0x6Ee9894c677EFa1c56392e5E7533DE76004C8D94 +#### Custom Signing Key -This is a test statement. +Generate and use a custom RSA key for JWT signing: -URI: https://localhost/login +```bash +# Generate RSA private key +openssl genrsa -out private.pem 2048 -Version: 1 +# Extract public key +openssl rsa -in private.pem -pubout -out public.pem -Chain ID: 1 +# Use in deployment +export SIWEOIDC_RSA_PEM=$(cat private.pem) +``` -Nonce: oNCEHm5jzQU2WvuBB +#### Redis Configuration -Issued At: 2022-01-28T23:28:16.013Z +For production, configure Redis with persistence and security: -To learn about all the available fields in a SiweMessage, check out the information in EIP-4361 +```bash +# Redis with persistence and password +docker run -d --name redis \ + -p 6379:6379 \ + -v redis_data:/data \ + -e REDIS_PASSWORD=your-secure-password \ + redis:7-alpine \ + redis-server --requirepass your-secure-password --appendonly yes +``` -SiweMessage +## Reverse Proxy Setup -The fields we are most interested in for the purposes of this guide are address and statement. address is the Ethereum address which the user is signing in with, and the statement as this will describe to the user what action we wish to perform on their behalf. +### Nginx Configuration -address +```nginx +server { + listen 443 ssl http2; + server_name oidc.yourdomain.com; -statement + ssl_certificate /path/to/your/cert.pem; + ssl_certificate_key /path/to/your/key.pem; -Often, as in this example, we don't need to do any manipulation of the message, so we can immediately convert it into the textual representation that the user will sign. + location / { + proxy_pass http://localhost:8000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; -00_print + # CORS headers for OIDC + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; + add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization' always; + } +} +``` -# Implement the Backend +### Apache Configuration -Sign-In with EthereumQuickstart GuideImplement the BackendHere we learn how to build the backend server to handle the user's submission using Express.js.PreviousImplement the FrontendNextConnect the FrontendLast updated 2 years ago +```apache + + ServerName oidc.yourdomain.com -Here we learn how to build the backend server to handle the user's submission using Express.js. + SSLEngine on + SSLCertificateFile /path/to/your/cert.pem + SSLCertificateKeyFile /path/to/your/key.pem -A completed version of this part can be found here (). This example uses only uses the command line in the terminal to print messages, no monitoring of the browser console log is necessary. + ProxyPreserveHost On + ProxyRequests Off + ProxyPass / http://localhost:8000/ + ProxyPassReverse / http://localhost:8000/ -The backend server gives the frontend a nonce to include in the SIWE message and also verifies the submission. As such, this basic example only provides two corresponding endpoints: + Header always set Access-Control-Allow-Origin "*" + Header always set Access-Control-Allow-Methods "GET, POST, OPTIONS" + Header always set Access-Control-Allow-Headers "Content-Type, Authorization" + +``` -/nonce to generate the nonce for the interaction via GET request. +## Local Development -/nonce +### Development Setup -GET +```bash +# Clone repository +git clone https://github.com/spruceid/siwe-oidc +cd siwe-oidc -/verify to verify the submitted SIWE message and signature via POST request. +# Start development environment with Docker Compose +docker-compose -f docker-compose.dev.yml up -/verify +# Edit /etc/hosts for local testing +echo "127.0.0.1 oidc.localhost" >> /etc/hosts +``` -POST +### Testing the Deployment -While this simple example does not check the nonce during verification, all production implementations should, as demonstrated in the final section . +```bash +# Test OIDC configuration endpoint +curl https://oidc.yourdomain.com/.well-known/openid-configuration -1. Setup the project directory: +# Register a test client +curl -X POST https://oidc.yourdomain.com/register \ + -H 'Content-Type: application/json' \ + -d '{ + "redirect_uris": ["https://yourapp.com/callback"], + "client_name": "Test Client", + "token_endpoint_auth_method": "client_secret_basic" + }' +``` -mkdir siwe-backend && cd siwe-backend/ +## Health Monitoring -yarn add cors express siwe ethers +### Health Check Endpoints -2. Make sure that the package.json type is module like the following: +- **Status**: `GET /.well-known/openid-configuration` - Returns 200 if service is healthy +- **Metrics**: Custom monitoring endpoints can be added via environment variables -package.json +### Monitoring Setup -type +```yaml +# docker-compose monitoring addition +services: + prometheus: + image: prom/prometheus + ports: + - '9090:9090' + volumes: + - ./prometheus.yml:/etc/prometheus/prometheus.yml -module + grafana: + image: grafana/grafana + ports: + - '3000:3000' + environment: + - GF_SECURITY_ADMIN_PASSWORD=admin +``` -{ +## Security Considerations - "name": "backend", +### Production Checklist - "version": "1.0.0", +- [ ] **HTTPS Only**: Ensure all traffic uses HTTPS +- [ ] **Secure Redis**: Use authentication and encryption +- [ ] **Custom Keys**: Generate and securely store RSA signing keys +- [ ] **Domain Validation**: Verify redirect URI domains +- [ ] **Rate Limiting**: Implement request rate limiting +- [ ] **Monitoring**: Set up logging and alerting +- [ ] **Backups**: Regular Redis data backups +- [ ] **Updates**: Keep container images updated - "main": "index.js", +### Important Notes - "type": "module", +⚠️ **Frontend-API Domain Requirement**: The frontend application must be served from the same subdomain as the OIDC API endpoint for security reasons. - "license": "MIT", +βœ… **Valid**: `app.yourdomain.com` β†’ `oidc.yourdomain.com` +❌ **Invalid**: `yourapp.com` β†’ `oidc.anotherdomain.com` - "scripts": { +## Troubleshooting - "start": "node src/index.js" +### Common Issues - }, +1. **CORS Errors**: Ensure proper CORS headers in reverse proxy +2. **Redis Connection**: Verify Redis is running and accessible +3. **Domain Issues**: Check that frontend and API share subdomain +4. **SSL Issues**: Verify certificate is valid and properly configured - "dependencies": { +### Debug Mode - "siwe": "^2.1.4", +Enable debug logging: - "cors": "^2.8.5", +```bash +# Add debug environment variable +RUST_LOG=debug \ +SIWEOIDC_REDIS_URL=redis://localhost:6379 \ +./siwe-oidc +``` - "ethers": "^6.3.0", +--- - "express": "^4.18.2" +import FullWidthLink from '@site/src/components/full-width-link' - } +# OIDC Provider -3. Populate src/index.js with the following: +## Rationale -src/index.js +Many organizations want to consolidate the Sign in with Ethereum workflow to a single identity service (Identity Provider or IdP) that could be used to access all their federated services (Relying Parties or RPs) using [OpenID Connect](https://openid.net/connect/) to forward the user's session. This reduces overhead and mitigates security risks by consolidating authentication to one protected site instead of several, especially in complex IT systems that have many services for their users to access. -import cors from 'cors'; +## Getting Started -import express from 'express'; +The OIDC Provider implementation of Sign in with Ethereum can be found here: -import { generateNonce, SiweMessage } from 'siwe'; + +
-const app = express(); +Currently, two runtime modes are supported: (1) a standalone executable (using +Axum and Redis) and (2) a WASM module within a Cloudflare Worker. Both are built +from the same codebase, specializing at build time. Compilation with a `cargo` target +of `wasm32` will build for Cloudflare Worker deployments. -app.use(express.json()); +--- -app.use(cors()); +import FullWidthLink from '@site/src/components/full-width-link' -app.get('/nonce', function (_, res) { +# πŸ¦€ Rust - res.setHeader('Content-Type', 'text/plain'); +The Rust implementation of Sign in with Ethereum can be found here: - res.send(generateNonce()); + -}); +## Getting Started -app.post('/verify', async function (req, res) { + - const { message, signature } = req.body; +For detailed implementation and usage instructions, refer to the GitHub repository and crates.io documentation. - const siweMessage = new SiweMessage(message); +--- - try { +# Library Implementations - await siweMessage.verify({ signature }); +SIWE provides official libraries in multiple programming languages, making it easy to integrate Sign in with Ethereum authentication into applications regardless of your tech stack. Each library implements the [EIP-4361](https://eips.ethereum.org/EIPS/eip-4361) specification and provides both message creation and signature verification capabilities. - res.send(true); +## Supported Languages - } catch { +### [TypeScript/JavaScript](typescript) - res.send(false); +The original and most feature-complete SIWE implementation. - } +- **Package**: `siwe` on npm +- **Platforms**: Node.js, Browser, React Native +- **Features**: Complete [EIP-4361](https://eips.ethereum.org/EIPS/eip-4361) support, TypeScript definitions, extensive testing +- **Best for**: Web applications, React/Vue/Angular apps, Node.js backends -app.listen(3000); +### [Rust](rust) -4. You can run the server with the following command. +High-performance implementation for Rust applications. -yarn start +- **Package**: `siwe` on crates.io +- **Platforms**: Server applications, CLI tools, embedded systems +- **Features**: Memory-safe, fast verification, serde serialization +- **Best for**: High-performance backends, blockchain infrastructure, CLI tools -In a new terminal window, test the /nonce endpoint to make sure the backend is working: +### [Python](python) -curl 'http://localhost:3000/nonce' +Pythonic implementation for Python developers. -In the same new terminal window, test the /verify endpoint use the following, and ensure the response is true: +- **Package**: `siwe` on PyPI +- **Platforms**: Django, Flask, FastAPI applications +- **Features**: Async/await support, dataclass integration, type hints +- **Best for**: Django/Flask apps, data analysis tools, ML/AI applications -true +### [Ruby](ruby) -curl 'http://localhost:3000/verify' \ +Ruby gem with Rails integration support. - -H 'Content-Type: application/json' \ +- **Package**: `siwe` gem on RubyGems +- **Platforms**: Rails applications, Sinatra, standalone Ruby scripts +- **Features**: ActiveSupport integration, Rails middleware, comprehensive docs +- **Best for**: Ruby on Rails applications, API backends - --data-raw '{"message":"localhost:8080 wants you to sign in with your Ethereum account:\n0x9D85ca56217D2bb651b00f15e694EB7E713637D4\n\nSign in with Ethereum to the app.\n\nURI: http://localhost:8080\nVersion: 1\nChain ID: 1\nNonce: spAsCWHwxsQzLcMzi\nIssued At: 2022-01-29T03:22:26.716Z","signature":"0xe117ad63b517e7b6823e472bf42691c28a4663801c6ad37f7249a1fe56aa54b35bfce93b1e9fa82da7d55bbf0d75ca497843b0702b9dfb7ca9d9c6edb25574c51c"}' +### [Go](go) -We can verify the received SIWE message by parsing it back into a SiweMessage object (the constructor handles this), assigning the received signature to it and calling the verify method: +Go implementation for Go developers. -verify +- **Package**: `github.com/signinwithethereum/siwe-go` +- **Platforms**: Go web servers, microservices, CLI applications +- **Features**: Standard library compatibility, efficient verification, minimal dependencies +- **Best for**: Microservices, Go web applications, infrastructure tools -message.verify({ signature }) +### [Elixir](elixir) -message.verify({ signature }) in the above snippet makes sure that the given signature is correct for the message, ensuring that the Ethereum address within the message produced the matching signature. +Functional implementation for Elixir/Phoenix applications. -In other applications, you may wish to do further verification on other fields in the message, for example asserting that the authority matches the expected domain, or checking that the named address has the authority to access the named URI. +- **Package**: `siwe` on Hex +- **Platforms**: Phoenix applications, LiveView, OTP applications +- **Features**: GenServer integration, Phoenix plugs, fault tolerance +- **Best for**: Phoenix web apps, real-time applications, distributed systems -A small example of this is shown later where the nonce attribute is used to track that a given address has signed the message given by the server. +## Quick Start Comparison -# Connect the Frontend +Here's how to get started with each library: -Sign-In with EthereumQuickstart GuideConnect the FrontendHere we learn how to update the frontend to send the signed messages to the server.PreviousImplement the BackendNextImplement SessionsLast updated 2 years ago +### JavaScript/TypeScript -Here we learn how to update the frontend to send the signed messages to the server. +```bash +npm install siwe ethers +``` -A completed version of the updated frontend can be found here (). This example uses the to print messages, so it should be actively monitored. +```javascript +import { SiweMessage } from 'siwe' + +const message = new SiweMessage({ + domain: 'example.com', + address: '0x...', + uri: 'https://example.com', + version: '1', + chainId: 1, +}) +``` -1. Revisit the siwe-frontend directory, stop any running servers, and update src/index.js: +### Rust -siwe-frontend +```toml +[dependencies] +siwe = "0.6" +``` -src/index.js: +```rust +use siwe::Message; -import { BrowserProvider } from 'ethers'; +let message = Message { + domain: "example.com".parse()?, + address: "0x...".parse()?, + uri: "https://example.com".parse()?, + version: siwe::Version::V1, + chain_id: 1, + // ... +}; +``` -import { SiweMessage } from 'siwe'; +### Python -const domain = window.location.host; +```bash +pip install siwe +``` -const origin = window.location.origin; +```python +from siwe import SiweMessage + +message = SiweMessage( + domain="example.com", + address="0x...", + uri="https://example.com", + version="1", + chain_id=1, +) +``` -const provider = new BrowserProvider(window.ethereum); +### Ruby -const BACKEND_ADDR = "http://localhost:3000"; +```bash +gem install siwe +``` -async function createSiweMessage(address, statement) { +```ruby +require 'siwe' - const res = await fetch(`${BACKEND_ADDR}/nonce`); +message = Siwe::Message.new( + domain: 'example.com', + address: '0x...', + uri: 'https://example.com', + version: '1', + chain_id: 1 +) +``` - const message = new SiweMessage({ +### Go - domain, +```bash +go get github.com/signinwithethereum/siwe-go +``` - address, +```go +import "github.com/signinwithethereum/siwe-go" - statement, +message := siwe.Message{ + Domain: "example.com", + Address: "0x...", + URI: "https://example.com", + Version: "1", + ChainID: 1, +} +``` - uri: origin, +### Elixir - version: '1', +```elixir +# In mix.exs +{:siwe, "~> 0.3"} +``` - chainId: '1', +```elixir +message = %Siwe.Message{ + domain: "example.com", + address: "0x...", + uri: "https://example.com", + version: "1", + chain_id: 1 +} +``` - nonce: await res.text() +## Feature Comparison - }); +| Feature | TypeScript | Rust | Python | Ruby | Go | Elixir | +| ---------------------- | -------------- | ---------- | ------------- | -------------- | --------- | ------- | +| Message Creation | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | +| Signature Verification | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | +| Nonce Generation | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | +| EIP-191 Support | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | +| EIP-712 Support | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | +| EIP-1271 Support | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | +| Async/Await | βœ… | βœ… | βœ… | ❌ | βœ… | βœ… | +| Type Safety | βœ… | βœ… | βœ… | ❌ | βœ… | βœ… | +| Framework Integration | React, Express | Axum, Warp | Django, Flask | Rails, Sinatra | Gin, Echo | Phoenix | +| Browser Support | βœ… | ❌ | ❌ | ❌ | ❌ | ❌ | - return message.prepareMessage(); +## Choosing the Right Library -function connectWallet() { +### For Web Applications - provider.send('eth_requestAccounts', []) +- **Frontend**: Use TypeScript/JavaScript for React, Vue, Angular, or vanilla JS +- **Backend**: Choose based on your existing backend language and framework - .catch(() => console.log('user rejected request')); +### For Mobile Applications -let message = null; +- **React Native**: TypeScript/JavaScript +- **Native iOS/Android**: Use appropriate native HTTP libraries with any backend -let signature = null; +### For Enterprise Applications -async function signInWithEthereum() { +- **Java/.NET**: Use HTTP clients to communicate with SIWE backend services +- **Enterprise backends**: Go, Rust, or TypeScript for high performance - const signer = await provider.getSigner(); +### For Rapid Prototyping - message = await createSiweMessage( +- **TypeScript/JavaScript**: Fastest to get started, works everywhere +- **Python**: Great for data-driven applications and ML integration +- **Ruby**: Excellent for Rails developers - await signer.address, +## Installation Guides - 'Sign in with Ethereum to the app.' +Each library has specific installation and setup instructions: - ); +- **[TypeScript/JavaScript Setup](typescript#installation)**: npm, yarn, browser CDN +- **[Rust Setup](rust)**: Cargo dependencies and features +- **[Python Setup](python)**: pip, conda, virtual environments +- **[Ruby Setup](ruby)**: gem, bundler, Rails integration +- **[Go Setup](go)**: go mod, dependency management +- **[Elixir Setup](elixir)**: mix deps, Phoenix integration - console.log(message); +## Migration Guides - signature = await signer.signMessage(message); +If you need to switch between libraries or upgrade versions: - console.log(signature); +- [TypeScript v1 to v2 Migration](typescript#migration-guide) +- [Cross-language Migration Tips](#cross-language-migration) +- Version Compatibility Matrix (see below) -async function sendForVerification() { +## Cross-Language Migration - const res = await fetch(`${BACKEND_ADDR}/verify`, { +When moving between different SIWE library implementations: - method: "POST", +### Message Format Compatibility - headers: { +All libraries generate identical [EIP-4361](https://eips.ethereum.org/EIPS/eip-4361) compliant messages, ensuring signatures created in one language can be verified in any other. - 'Content-Type': 'application/json', +### Configuration Mapping - }, +```javascript +// JavaScript +const message = new SiweMessage({ + domain: 'example.com', + address: '0x...', + statement: 'Sign in to our app', + uri: 'https://example.com', + version: '1', + chainId: 1, + nonce: 'abc123', + issuedAt: '2023-10-31T16:25:24Z', +}) +``` - body: JSON.stringify({ message, signature }), +```python +# Python equivalent +message = SiweMessage( + domain="example.com", + address="0x...", + statement="Sign in to our app", + uri="https://example.com", + version="1", + chain_id=1, + nonce="abc123", + issued_at="2023-10-31T16:25:24Z" +) +``` - console.log(await res.text()); +### Error Handling Patterns -const connectWalletBtn = document.getElementById('connectWalletBtn'); +Each library follows language-specific error handling conventions but provides equivalent functionality: -const siweBtn = document.getElementById('siweBtn'); +- **JavaScript/TypeScript**: Promise-based with try/catch +- **Rust**: Result types with match expressions +- **Python**: Exception-based with try/except +- **Ruby**: Exception-based with begin/rescue +- **Go**: Error return values with if err != nil +- **Elixir**: `{:ok, result} | {:error, reason}` tuples -const verifyBtn = document.getElementById('verifyBtn'); +## Community Libraries -connectWalletBtn.onclick = connectWallet; +Beyond official libraries, the community has created additional implementations: -siweBtn.onclick = signInWithEthereum; +- **Java**: Community-maintained Spring Boot integration +- **C#/.NET**: Community library for ASP.NET applications +- **Swift**: iOS/macOS native implementation +- **Kotlin**: Android-first implementation +- **PHP**: Laravel and Symfony integrations -verifyBtn.onclick = sendForVerification; +Visit our [GitHub repository](https://github.com/signinwithethereum/siwe) for links to community libraries. -2. Update src/index.html: +## Version Compatibility -src/index.html +### Library Version Matrix - +| Library | Current Version | EIP-4361 Spec | Node.js/Runtime | Notes | +|---------|----------------|---------------|-----------------|-------| +| TypeScript | 2.x | Full support | Node 16+ | Breaking changes from v1 | +| Rust | 0.6.x | Full support | N/A | Stable API | +| Python | 3.x | Full support | Python 3.7+ | Async support added | +| Ruby | 2.x | Full support | Ruby 2.7+ | Rails 6+ recommended | +| Go | 1.x | Full support | Go 1.18+ | Generics support | +| Elixir | 0.3.x | Full support | Elixir 1.12+ | Phoenix 1.6+ | - +### Breaking Changes - +When upgrading between major versions: - +- **TypeScript v1 β†’ v2**: Constructor API changes, see [migration guide](typescript/migrating-to-v2) +- **Python v2 β†’ v3**: Async/await support, dataclass changes +- **Ruby v1 β†’ v2**: Rails integration improvements - SIWE Quickstart +### Specification Compliance - +All libraries implement: +- EIP-4361 (Sign In with Ethereum) +- EIP-191 (Signed Data Standard) +- EIP-1271 (Contract Signatures) +- RFC 3986 (URI Specification) +- RFC 3339 (Timestamp Format) - +## Contributing -
+All SIWE libraries are open source and welcome contributions: -
+- **Bug Reports**: Create issues on the respective GitHub repositories +- **Feature Requests**: Discuss new features in GitHub Discussions +- **Pull Requests**: Follow each repository's contributing guidelines +- **Documentation**: Help improve library documentation and examples +- **Testing**: Add test cases for edge cases and new features -
+## Support - +Get help with SIWE libraries: - +- **Documentation**: Each library has comprehensive docs and examples +- **GitHub Issues**: Repository-specific support for bugs and questions +- **Discord**: Real-time help from the community +- **Stack Overflow**: Tag questions with `sign-in-with-ethereum` +- **Twitter**: Follow [@signinwithethereum](https://twitter.com/signinwithethereum) for updates -3. For this last step, you need to have both the frontend and backend running together. Start by running the backend server with the following command from the parent directory: +--- -cd siwe-backend +_Ready to implement SIWE in your application? Choose your language and dive into the detailed documentation for your selected library._ -In a separate terminal, start the frontend by running the following command and visit the URL printed to the console: +--- -cd siwe-frontend +import FullWidthLink from '@site/src/components/full-width-link' -4. Try to Sign-In with Ethereum by visiting the URL printed to the console, connecting your wallet, and signing in. You can now hit the Send for verification button to receive a true in the console. +# 🍷 Elixir -Send for verification +The Elixir implementation of Sign in with Ethereum can be found here: -03_complete_app/frontend + -# Implement Sessions +## Installation -Sign-In with EthereumQuickstart GuideImplement SessionsHere we learn how to implement sessions with Express.js to add the necessary backend security.PreviousConnect the FrontendNextResolve ENS ProfilesLast updated 2 years ago +Add the library to your `mix.exs` dependencies: -Here we learn how to implement sessions with Express.js to add the necessary backend security. +```elixir +def deps do + [ + {:siwe, "~> 0.3"} + ] +end +``` -A completed version of the updated backend can be found here (). This example uses the to print messages, so it should be actively monitored. +## Example Usage -For additional security against replay attacks, it is not enough for the backend to generate the nonce. It must also be tied to a browser session with the end-user. In the siwe-backend directory, install the following and edit index.js: +To test the library, clone the repository and run: -siwe-backend +```bash +$ mix deps.get +``` -index.js +Create two files: -yarn add express-session +`message.txt`: -Update src/index.js to the following: +``` +login.xyz wants you to sign in with your Ethereum account: +0xfA151B5453CE69ABf60f0dbdE71F6C9C5868800E -import Session from 'express-session'; +Sign in with Ethereum Example Statement -app.use(cors({ +URI: https://login.xyz +Version: 1 +Chain ID: 1 +Nonce: ToTaLLyRanDOM +Issued At: 2021-12-17T00:38:39.834Z +``` - origin: 'http://localhost:8080', +`signature.txt`: - credentials: true, +``` +0x8d1327a1abbdf172875e5be41706c50fc3bede8af363b67aefbb543d6d082fb76a22057d7cb6d668ceba883f7d70ab7f1dc015b76b51d226af9d610fa20360ad1c +``` -})) +Then in `iex`: -app.use(Session({ +```elixir +iex> {:ok, msg} = File.read("./message.txt") +iex> {:ok, sig} = File.read("./signature.txt") +iex> Siwe.parse_if_valid(String.trim(msg), String.trim(sig)) +``` - name: 'siwe-quickstart', +This will return a parsed SIWE message with details like address, chain ID, domain, and other metadata. - secret: "siwe-quickstart-secret", +--- - resave: true, +import FullWidthLink from '@site/src/components/full-width-link' - saveUninitialized: true, +# 🧩 Ethereum Identity Kit - cookie: { secure: false, sameSite: true } +Ethereum Identity Kit is a library for building Ethereum identity applications. Full documentation can be found at: -})); + -app.get('/nonce', async function (req, res) { +## Installation - req.session.nonce = generateNonce(); +```bash +npm install @ethereum-identity-kit +``` - res.status(200).send(req.session.nonce); +--- - if (!req.body.message) { +# TypeScript Quickstart - res.status(422).json({ message: 'Expected prepareMessage object as body.' }); +## Goals - return; +- Run a Sign in with Ethereum example locally +- Sign in using a preferred wallet - } +## Requirements - let SIWEObject = new SiweMessage(req.body.message); +- NodeJS version 14.0 or higher - const { data: message } = await SIWEObject.verify({ signature: req.body.signature, nonce: req.session.nonce }); +## Setup and Running the Quickstart - req.session.siwe = message; +### Clone the Repository - req.session.cookie.expires = new Date(message.expirationTime); +```bash +git clone https://github.com/signinwithethereum/siwe-notepad +``` - req.session.save(() => res.status(200).send(true)); +### Install and Run - } catch (e) { +```bash +cd siwe-notepad +npm install +npm run dev +``` - req.session.siwe = null; +### Access the Example - req.session.nonce = null; +- Visit http://localhost:4361 (or the port allocated by npm) - console.error(e); +## Example Walkthrough - switch (e) { +1. Load the example in your web browser +2. Click on a wallet option to sign in with Ethereum +3. Enter some text +4. Save the text +5. Disconnect and reconnect to reload the saved text - case ErrorTypes.EXPIRED_MESSAGE: { +## Additional Information - req.session.save(() => res.status(440).json({ message: e.message })); +- Full example available on [GitHub - signinwithethereum/siwe-notepad](https://github.com/signinwithethereum/siwe-notepad) - break; +Note: This quickstart guide demonstrates core SIWE functionality using a simple notepad-style application. - } +--- - case ErrorTypes.INVALID_SIGNATURE: { +# Migrating to SIWE TypeScript v2 - req.session.save(() => res.status(422).json({ message: e.message })); +## Overview - default: { +If you are using `siwe v1.1.6`, it is recommended to update to the latest version (`2.1.x`). - req.session.save(() => res.status(500).json({ message: e.message })); +## Key Differences in v2.0 -app.get('/personal_information', function (req, res) { +### Function Changes - if (!req.session.siwe) { +The primary change is the replacement of the `validate(sig, provider)` function with a new `verify(VerifyParams, VerifyOpts)` method. - res.status(401).json({ message: 'You have to first sign_in' }); +#### New Verification Parameters - return; +```typescript +export interface VerifyParams { + /** Signature of the message signed by the wallet */ + signature: string; - console.log("User is authenticated!"); + /** RFC 4501 dns authority that is requesting the signing */ + domain?: string; - res.send(`You are authenticated and your address is: ${req.session.siwe.address}`); + /** Randomized token to prevent replay attacks, at least 8 alphanumeric characters */ + nonce?: string; -This way, the session (req.session) stores the nonce for the initial validation of the message, and once that's done, more can be added. For example, here we store the message's fields, so we can always reference the address of the user. + /** ISO 8601 datetime string of the current time */ + time?: string; +} -req.session +export interface VerifyOpts { + /** ethers provider to be used for EIP-1271 validation */ + provider?: providers.Provider; -A potential extension is to resolve the ENS domain of the user and keep it in the session. + /** If the library should reject promises on errors, defaults to false */ + suppressExceptions?: boolean; +} +``` -On the client side, the flow is similar to the previous example, except we now need to send cookies with our requests for the session to work. We can add a new endpoint, personal_information, to retrieve the information from the session in place, without having to send the message and signature every time. +### Return Type Changes -personal_information +The verification now returns a `SiweResponse` with a more detailed structure: -In the siwe-frontend directory, stop any running instances and populate src/index.js to match the following: +```typescript +export interface SiweResponse { + /** Boolean representing if the message was verified successfully */ + success: boolean; - const res = await fetch(`${BACKEND_ADDR}/nonce`, { + /** If present and success is false, provides extra information on failure reason */ + error?: SiweError; - credentials: 'include', + /** Original message that was verified */ + data: SiweMessage; +} +``` - const message = await createSiweMessage( +### Additional Notes - await signer.getAddress(), +- The new function makes it easier to automatically match fields like `domain`, `nonce`, and compare against current time. +- New error types have been introduced to provide more clarity on verification failures. - const signature = await signer.signMessage(message); +## Recommended Upgrade Path - credentials: 'include' +1. Replace `validate()` calls with `verify()` +2. Update error handling to work with the new `SiweResponse` structure +3. Review and adapt to the new parameter and return type interfaces -async function getInformation() { +For more detailed information, refer to the [SIWE TypeScript v2 release notes](https://blog.spruceid.com/sign-in-with-ethereum-typescript). - const res = await fetch(`${BACKEND_ADDR}/personal_information`, { +--- -const infoBtn = document.getElementById('infoBtn'); +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import FullWidthLink from '@site/src/components/full-width-link' -infoBtn.onclick = getInformation; +# πŸ’Ž Ruby -Update the siwe-frontend/src/index.html to match the following: +The Ruby implementation of Sign in with Ethereum can be found here: -siwe-frontend/src/index.html + - +## Installation - +### Dependencies - SIWE Quickstart + + + ```bash + brew install automake openssl libtool pkg-config gmp libffi + ``` + + + ```bash + sudo apt-get install build-essential automake pkg-config libtool \ + libffi-dev libssl-dev libgmp-dev python-dev + ``` + + - +Install the gem: - +```bash +gem install siwe +``` -
+## Usage -
+### Creating a SIWE Message -
+```ruby +require 'siwe' +require 'time' - +# Only mandatory arguments +Siwe::Message.new("domain.example", "0x9D85ca56217D2bb651b00f15e694EB7E713637D4", "some.uri", "1") -4. Try to Sign-In with Ethereum by visiting the URL printed to the console, connecting your wallet, and signing in. You can now hit the Get session information button to receive a message similar to the following in the console: +# Complete SIWE message with default values +Siwe::Message.new("domain.example", "0x9D85ca56217D2bb651b00f15e694EB7E713637D4", "some.uri", "1", { + issued_at: Time.now.utc.iso8601, + statement: "Example statement for SIWE", + nonce: Siwe::Util.generate_nonce, + chain_id: "1", + expiration_time: "", + not_before: "", + request_id: "", + resources: [] +}) +``` -Get session information +### Parsing a SIWE Message -You are authenticated and your address is: +```ruby +# From EIP-4361 format +Siwe::Message.from_message "domain.example wants you to sign in with your Ethereum account:..." -Refer to for additional information on how to use express-session in production. +# From JSON string +Siwe::Message.from_json_string "{\"domain\":\"domain.example\",\"address\":\"0x9D85ca56217D2bb651b00f15e694EB7E713637D4\",...}" +``` -express-session +### Verifying and Authenticating a SIWE Message -03_complete_app/backend +```ruby +begin + message.validate(signature) # returns true if valid, throws otherwise +rescue Siwe::ExpiredMessage + # Handle expired message +rescue Siwe::InvalidSignature + # Handle invalid signature +end +``` -# Resolve ENS Profiles +## Error Handling -Sign-In with EthereumQuickstart GuideResolve ENS ProfilesHere we learn how to resolve a user's ENS profile data.PreviousImplement SessionsNextResolve NFT HoldingsLast updated 2 years ago +The library provides specific exception types for different validation failures: -Here we learn how to resolve a user's ENS profile data. +- `Siwe::ExpiredMessage`: When the message has expired +- `Siwe::InvalidSignature`: When the signature is invalid +- Other validation errors as appropriate -A completed version of the updated frontend can be found here: (). +--- -Now that the application knows the user's connected account, a basic profile can be built using additional information from if available. Because the frontend is already is using ethers, it is simple to add this functionality and retrieve this data. +# πŸ›€οΈ Rails -frontend +## Overview -ethers +Sign in with Ethereum (SIWE) provides authentication for Rails applications through multiple integration approaches. This documentation covers three primary methods of implementation: -Update the frontend/src/index.html file to the following: +### Requirements -frontend/src/index.html +- Ruby version 2.7.0 or above +- Rails framework +- MetaMask wallet - +### Custom Controller Example -
+```bash +cd siwe-rails-examples/custom-controller +bundle install +bin/rails db:migrate RAILS_ENV=development +bundle exec rails server +``` -
+### Rails Engine Example -
+```bash +cd siwe-rails-examples/rails-engine +bundle install +bin/rails db:migrate RAILS_ENV=development +bundle exec rails server +``` - +1. Register as a client with OIDC provider +2. Update configuration with client credentials +3. Start Rails server - +``` -siwe.ParseMessage +### 2. Update JavaScript (index.js) -var message *siwe.Message +Add functions to retrieve and display both asset and NFT holdings: -var err error +#### Asset Holdings Functions -message, err = siwe.ParseMessage(messageStr) +```javascript +// Element references +const holdingsElm = document.getElementById('holdings') +const assetsLoaderElm = document.getElementById('assetsLoader') +const assetsTableElm = document.getElementById('assetsTable') +const nftElm = document.getElementById('nft') +const nftLoaderElm = document.getElementById('nftLoader') +const nftTableElm = document.getElementById('nftTable') + +// Asset holdings functions +async function getTokenBalances() { + try { + // Using Moralis API for token balances + const response = await fetch( + `https://deep-index.moralis.io/api/v2/${address}/erc20`, + { + headers: { + 'X-API-Key': 'your-moralis-api-key', + 'accept': 'application/json' + } + } + ) + + if (!response.ok) { + throw new Error(response.statusText) + } + + const data = await response.json() + + return data + .filter(token => parseFloat(token.balance) > 0) + .map(token => ({ + name: token.name, + symbol: token.symbol, + balance: (parseFloat(token.balance) / Math.pow(10, token.decimals)).toFixed(4), + address: token.token_address, + decimals: token.decimals + })) + } catch (error) { + console.error('Failed to fetch token balances:', error) + return [] + } +} -The function will return a nil pointer and an error if there was an issue while parsing. +async function getETHBalance() { + try { + const balance = await provider.getBalance(address) + const ethBalance = ethers.utils.formatEther(balance) + + return { + name: 'Ethereum', + symbol: 'ETH', + balance: parseFloat(ethBalance).toFixed(4), + address: 'native', + decimals: 18 + } + } catch (error) { + console.error('Failed to fetch ETH balance:', error) + return null + } +} -Verification and Authentication is performed via EIP-191, using the address field of the Message as the expected signer. This returns the Ethereum public key of the signer: +async function displayAssets() { + assetsLoaderElm.innerHTML = 'Loading token assets...' + + try { + const [ethBalance, tokenBalances] = await Promise.all([ + getETHBalance(), + getTokenBalances() + ]) + + const allAssets = ethBalance ? [ethBalance, ...tokenBalances] : tokenBalances + + if (allAssets.length === 0) { + assetsLoaderElm.innerHTML = 'No token assets found' + return + } + + let tableHtml = 'TokenSymbolBalanceContract' + + allAssets.forEach(asset => { + tableHtml += ` + ${asset.name} + ${asset.symbol} + ${asset.balance} + ${asset.address === 'native' ? 'Native ETH' : asset.address} + ` + }) + + assetsTableElm.innerHTML = tableHtml + assetsLoaderElm.classList = 'hidden' + document.getElementById('assetsContainer').classList = '' + } catch (error) { + console.error('Error displaying assets:', error) + assetsLoaderElm.innerHTML = 'Error loading assets' + } +} -var publicKey *ecdsa.PublicKey +// NFT holdings functions -publicKey, err = message.VerifyEIP191(signature) +async function getNFTs() { + try { + let res = await fetch( + `https://api.opensea.io/api/v1/assets?owner=${address}` + ) + if (!res.ok) { + throw new Error(res.statusText) + } + + let body = await res.json() + + if ( + !body.assets || + !Array.isArray(body.assets) || + body.assets.length === 0 + ) { + return [] + } + + return body.assets.map(asset => { + let { name, asset_contract, token_id } = asset + let { address } = asset_contract + return { name, address, token_id } + }) + } catch (err) { + console.error(`Failed to resolve nfts: ${err.message}`) + return [] + } +} -The time constraints (expiry and not-before) can also be validated, at current or particular times: +async function displayNFTs() { + nftLoaderElm.innerHTML = 'Loading NFT Ownership...' + nftElm.classList = '' -if message.ValidNow() { + let nfts = await getNFTs() + if (nfts.length === 0) { + nftLoaderElm.innerHTML = 'No NFTs found' + return + } - // ... + let tableHtml = + 'NameContract AddressToken ID' -// equivalent to + nfts.forEach(nft => { + tableHtml += ` + ${nft.name || 'Unnamed'} + ${nft.address} + ${nft.token_id} + ` + }) -if message.ValidAt(time.Now().UTC()) { + nftTableElm.innerHTML = tableHtml + nftLoaderElm.classList = 'hidden' + document.getElementById('nftContainer').classList = '' +} +``` -Combined verification of time constraints and authentication can be done in a single call with verify: +### 3. Call the Functions -// Optional nonce variable to be matched against the +Add both asset and NFT display functions to your authentication flow: -// built message struct being verified +```javascript +// After successful authentication +async function onAuthenticated() { + await displayENSProfile() + await displayEFPProfile() + await displayAssets() + await displayNFTs() + + // Show the holdings section + holdingsElm.classList = '' +} +``` -var optionalNonce *string +## Alternative Asset APIs -// Optional timestamp variable to verify at any point +### Using Alchemy for Token Balances -// in time, by default it will use `time.Now()` +```javascript +async function getTokenBalancesAlchemy() { + const apiKey = 'your-alchemy-api-key' + const baseURL = `https://eth-mainnet.g.alchemy.com/v2/${apiKey}` + + try { + const response = await fetch(`${baseURL}/getTokenBalances`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 1, + method: 'alchemy_getTokenBalances', + params: [address, 'erc20'] + }) + }) + + const data = await response.json() + + return data.result.tokenBalances + .filter(token => parseInt(token.tokenBalance, 16) > 0) + .map(token => ({ + address: token.contractAddress, + balance: parseInt(token.tokenBalance, 16) + })) + } catch (error) { + console.error('Failed to fetch token balances from Alchemy:', error) + return [] + } +} +``` -var optionalTimestamp *time.Time +### Using CoinGecko for Price Data -publicKey, err = message.Verify(signature, optionalNonce, optionalTimestamp) +```javascript +async function getTokenPrices(tokenAddresses) { + try { + const addressList = tokenAddresses.join(',') + const response = await fetch( + `https://api.coingecko.com/api/v3/simple/token_price/ethereum?contract_addresses=${addressList}&vs_currencies=usd` + ) + + return await response.json() + } catch (error) { + console.error('Failed to fetch token prices:', error) + return {} + } +} +``` -// If you won't be using nonce matching and want +## Alternative NFT APIs -// to verify the message at the current time, it's +### Using Alchemy -// safe to pass `nil` in both arguments +```javascript +async function getNFTsAlchemy() { + const apiKey = 'your-alchemy-api-key' + const baseURL = `https://eth-mainnet.g.alchemy.com/v2/${apiKey}/getNFTs/` + + try { + const response = await fetch(`${baseURL}?owner=${address}`) + const data = await response.json() + + return data.ownedNfts.map(nft => ({ + name: nft.title, + address: nft.contract.address, + token_id: nft.id.tokenId, + })) + } catch (error) { + console.error('Failed to fetch NFTs from Alchemy:', error) + return [] + } +} +``` -publicKey, err = message.Verify(signature, nil, nil) +### Using Moralis -Message instances can also be serialized as their EIP-4361 string representations via the String method: +```javascript +async function getNFTsMoralis() { + const apiKey = 'your-moralis-api-key' + + try { + const response = await fetch( + `https://deep-index.moralis.io/api/v2/${address}/nft`, + { + headers: { + 'X-API-Key': apiKey, + }, + } + ) + + const data = await response.json() + + return data.result.map(nft => ({ + name: nft.name, + address: nft.token_address, + token_id: nft.token_id, + })) + } catch (error) { + console.error('Failed to fetch NFTs from Moralis:', error) + return [] + } +} +``` -String +## Enhanced CSS Styling -fmt.Printf("%s", message.String()) +Add styles for better presentation of holdings: -To sign messages directly from Go code, you will need to do it like shown below to correctly follow the personal_sign format: +```css +.holdings-section { + margin: 20px 0; + padding: 15px; + border: 1px solid #e0e0e0; + border-radius: 8px; + background-color: #fafafa; +} -personal_sign +.holdings-section h4 { + margin-top: 0; + color: #333; + border-bottom: 2px solid #007bff; + padding-bottom: 8px; +} -func signHash(data []byte) common.Hash { +.assets-table, .nft-table { + width: 100%; + border-collapse: collapse; + margin-top: 10px; +} - msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data) +.assets-table th, .nft-table th { + background-color: #007bff; + color: white; + padding: 12px; + text-align: left; +} - return crypto.Keccak256Hash([]byte(msg)) +.assets-table td, .nft-table td { + padding: 10px; + border-bottom: 1px solid #ddd; +} -func signMessage(message string, privateKey *ecdsa.PrivateKey) ([]byte, error) { +.balance-cell { + font-weight: bold; + color: #28a745; +} - sign := signHash([]byte(message)) +.contract-address { + font-family: monospace; + font-size: 0.9em; + color: #666; +} +``` - signature, err := crypto.Sign(sign.Bytes(), privateKey) +## Rate Limiting Considerations - if err != nil { +- Implement proper rate limiting for API calls +- Consider caching both token and NFT data to reduce API requests +- Handle API rate limit errors gracefully +- Use batch requests when possible to minimize API calls +- Implement exponential backoff for failed requests - return nil, err +## Privacy Considerations - } +- All onchain holdings (tokens and NFTs) are public blockchain data +- Consider allowing users to opt-out of holdings display +- Be mindful of revealing sensitive information through asset ownership +- Some users may prefer to keep their wealth information private +- Consider implementing privacy toggles for different asset types - signature[64] += 27 +## Advanced Features - return signature, nil +### Token-Gated Access Control +```javascript +async function checkTokenGatedAccess(requiredToken, minBalance) { + const tokenBalances = await getTokenBalances() + const tokenBalance = tokenBalances.find(token => + token.address.toLowerCase() === requiredToken.toLowerCase() + ) + + return tokenBalance && parseFloat(tokenBalance.balance) >= minBalance } -# Discourse - -IntegrationsDiscourseA Discourse plugin to enable Sign-In with Ethereum as an authentication method.PreviousGoNextNextAuth.jsLast updated 1 year ago - -A Discourse plugin to enable Sign-In with Ethereum as an authentication method. +async function checkNFTGatedAccess(requiredCollection) { + const nfts = await getNFTs() + return nfts.some(nft => + nft.address.toLowerCase() === requiredCollection.toLowerCase() + ) +} +``` -Last updated 1 year ago +### Portfolio Value Calculation -Overview +```javascript +async function calculatePortfolioValue() { + const [tokenBalances, prices] = await Promise.all([ + getTokenBalances(), + getTokenPrices(tokenBalances.map(t => t.address)) + ]) + + let totalValue = 0 + tokenBalances.forEach(token => { + const price = prices[token.address.toLowerCase()] + if (price && price.usd) { + totalValue += parseFloat(token.balance) * price.usd + } + }) + + return totalValue +} +``` -Discourse is an open-source discussion platform used for most crypto governances and projects to discuss proposals, updates, and research. The following is a quick guide on how to add Sign-In with Ethereum to your existing Discourse. +--- -Note +import FullWidthLink from '@site/src/components/full-width-link' -This guide is currently compatible with . The discussion about the issues with other builds can be followed . +# ENS Profiles -The Sign-In with Ethereum plugin still requires users to enter an email to associate with their accounts after authenticating for the first time. If the user owns an ENS address, it will be the default selected username. Once an email address is associated, users can then sign in using the SIWE option at any time. + -Access your container’s app.yml file (present in /var/discourse/containers/) +## Resources -app.yml +- [ENS Documentation](https://docs.ens.domains) - Complete protocol documentation +- [Ethereum Identity Kit](https://ethidentitykit.com) - React components for ENS integration +- [ENS App](https://app.ens.domains) - Register and manage ENS names and profiles +- [ENS Website](https://ens.domains) - ENS protocol website -/var/discourse/containers/ +## Component Library (React) -cd /var/discourse +You can use the Ethereum Identity Kit to integrate ENS into your application: -nano containers/app.yml + -Add the plugin’s repository URL to your container’s app.yml file: +## Implementation Steps -hooks: +### 1. Update HTML (frontend/src/index.html) - before_code: # <-- added +Add a new section for displaying ENS metadata: - - exec: # <-- added +```html + + +``` - cmd: # <-- added +### 2. Update JavaScript (frontend/src/index.js) - - gem install rubyzip # <-- added +Add element references and the `displayENSProfile()` function: - after_code: +```javascript +const profileElm = document.getElementById('profile') +const noProfileElm = document.getElementById('noProfile') +const ensTableElm = document.getElementById('ensTable') +const welcomeElm = document.getElementById('welcome') - - exec: +async function displayENSProfile() { + const ensName = await provider.lookupAddress(address) + + if (ensName) { + profileElm.classList = '' + welcomeElm.innerHTML = `Hello, ${ensName}` + + let avatar = await provider.getAvatar(ensName) + if (avatar) { + welcomeElm.innerHTML += ` ` + } + + const resolver = await provider.getResolver(ensName) + const keys = ['email', 'url', 'description', 'com.twitter'] + + // Populate ENS metadata table + ensTableElm.innerHTML += `name:${ensName}` + for (const key of keys) { + const value = await resolver.getText(key) + if (value) { + ensTableElm.innerHTML += `${key}:${value}` + } + } + + document.getElementById('ensContainer').classList = '' + } else { + welcomeElm.innerHTML = `Hello, ${address}` + noProfileElm.classList = '' + } - cd: $home/plugins + welcomeElm.classList = '' +} +``` - cmd: +### 3. CSS for Avatar Display - - sudo -E -u discourse git clone https://github.com/discourse/docker_manager.git +Add some basic styling for the avatar: - - sudo -E -u discourse git clone https://github.com/spruceid/discourse-siwe-auth.git # <-- added +```css +.avatar { + width: 32px; + height: 32px; + border-radius: 50%; + margin-left: 8px; + vertical-align: middle; +} -Follow the existing format of the docker_manager.git line; if it does not contain sudo -E -u discourse then insert - git clone https://github.com/spruceid/discourse-siwe-auth.git. +.hidden { + display: none; +} -docker_manager.git +table { + border-collapse: collapse; + width: 100%; +} -sudo -E -u discourse +td { + border: 1px solid #ddd; + padding: 8px; +} -- git clone https://github.com/spruceid/discourse-siwe-auth.git +td:first-child { + font-weight: bold; + background-color: #f2f2f2; +} +``` -Rebuild the container: +## Key Features -./launcher rebuild app +- **ENS Name Lookup**: Resolves the ENS name associated with an address +- **Avatar Display**: Shows the user's ENS avatar if available +- **Profile Metadata**: Displays common ENS text records like email, URL, and social media +- **Fallback Handling**: Gracefully handles addresses without ENS profiles -To disable it either remove the plugin or uncheck discourse siwe enabled at (Admin Settings -> Plugins -> discourse-siwe -> discourse siwe enabled ). +## ENS Text Records -discourse siwe enabled +The implementation checks for these common text records: -Admin Settings -> Plugins -> discourse-siwe -> discourse siwe enabled +- `email`: Contact email address +- `url`: Personal or professional website +- `description`: Bio or description +- `com.twitter`: Twitter handle -By default, a statement is added to the messages: Sign-in to Discourse via Ethereum. To edit this statement access the settings (same as before) and update it. +## Error Handling -To install and enable the plugin on your self-hosted Discourse use the : +The code includes proper error handling for: -This plugin uses the newest Web3Modal v2, in order to use it you need to create a free project id at and configure it in the plugin. +- Addresses without ENS names +- Missing or empty text records +- Network connectivity issues -# NextAuth.js +--- -IntegrationsNextAuth.jsA complete open source authentication solution.PreviousDiscourseNextAuth0Last updated 2 years ago +import FullWidthLink from '@site/src/components/full-width-link' -A complete open source authentication solution. +# EFP Social Graph -is an easy-to-implement, full-stack (client/server) open-source authentication library originally designed for and serverless applications. + -The library provides the ability to set up a custom credential provider, which we can take advantage of in order to authenticate users using their existing Ethereum wallet via Sign-In with Ethereum (EIP-4361). +## Resources -The complete example can be found . +- [EFP Documentation](https://docs.efp.app) - Complete protocol documentation +- [EFP API Reference](https://ethidentitykit.com/docs/api) - Full API specification +- [Ethereum Identity Kit](https://ethidentitykit.com) - React components for EFP integration +- [EFP App](https://efp.app) - Reference implementation -First clone the official NextAuth.js example using your terminal: +## What is EFP? -git clone https://github.com/nextauthjs/next-auth-example +Ethereum Follow Protocol (EFP) is an onchain social graph protocol for Ethereum accounts. It enables users to follow other Ethereum addresses, creating a decentralized social network layer. Unlike traditional social networks, EFP stores all relationship data onchain, making it composable and censorship-resistant. -Then, switch to the project directory: +Key features: +- **Decentralized Social Graph**: All follow relationships stored onchain +- **Composable**: Can be integrated into any application +- **Tag System**: Support for custom tags like "top8", "mute", "block" +- **No Vendor Lock-in**: Open protocol accessible by anyone -cd next-auth-example +## Component Library (React) -After cloning, modify the given .env.local.example file, and populate it with the following variables: +You can use the Ethereum Identity Kit to integrate EFP into your application: -.env.local.example + -NEXTAUTH_URL=http://localhost:3000 +## API Integration -NEXTAUTH_SECRET=somereallysecretsecret +### Basic User Stats -Note: After this, rename the file to .env.local. This example will be routed to http://localhost:3000. +Get followers and following counts for any Ethereum address: -.env.local +```javascript +async function getEFPStats(address) { + try { + const response = await fetch(`https://api.ethfollow.xyz/api/v1/users/${address}/stats`) + const stats = await response.json() + + return { + followers: stats.followers_count, + following: stats.following_count, + } + } catch (error) { + console.error('Error fetching EFP stats:', error) + return null + } +} +``` -yarn add siwe@beta ethers wagmi +### Get User's Following List -Now, modify pages/_app.tsx to inject the WagmiProvider component: +Retrieve the complete list of accounts a user follows: -pages/_app.tsx +```javascript +async function getUserFollowing(address, limit = 100) { + try { + const response = await fetch( + `https://api.ethfollow.xyz/api/v1/users/${address}/following?limit=${limit}` + ) + const data = await response.json() + + return data.following.map(follow => ({ + address: follow.address, + ens: follow.ens, + avatar: follow.avatar, + tags: follow.tags || [] + })) + } catch (error) { + console.error('Error fetching following list:', error) + return [] + } +} +``` -WagmiProvider +### Get User's Followers -import { Session } from "next-auth" +Retrieve the list of accounts following a user: -import { SessionProvider } from "next-auth/react" +```javascript +async function getUserFollowers(address, limit = 100) { + try { + const response = await fetch( + `https://api.ethfollow.xyz/api/v1/users/${address}/followers?limit=${limit}` + ) + const data = await response.json() + + return data.followers.map(follower => ({ + address: follower.address, + ens: follower.ens, + avatar: follower.avatar + })) + } catch (error) { + console.error('Error fetching followers list:', error) + return [] + } +} +``` -import type { AppProps } from "next/app" +## Implementation Steps -import { WagmiConfig, createClient, configureChains, chain } from "wagmi" +### 1. Update HTML (frontend/src/index.html) -import { publicProvider } from "wagmi/providers/public" +Add a section for displaying EFP social graph data: -import "./styles.css" +```html + + +``` -export const { chains, provider } = configureChains( +### 2. Update JavaScript (frontend/src/index.js) - [chain.mainnet, chain.polygon, chain.optimism, chain.arbitrum], +Add EFP profile resolution functionality: - [publicProvider()] +```javascript +const efpProfileElm = document.getElementById('efpProfile') +const noEFPProfileElm = document.getElementById('noEFPProfile') +const efpStatsElm = document.getElementById('efpStats') +const efpConnectionsElm = document.getElementById('efpConnections') + +async function displayEFPProfile() { + try { + // Get basic EFP stats + const stats = await getEFPStats(address) + + if (stats && (stats.followers > 0 || stats.following > 0)) { + efpProfileElm.classList = '' + + // Display stats + efpStatsElm.innerHTML = ` +
+
+ ${stats.followers} + Followers +
+
+ ${stats.following} + Following +
+
+ ` + + // Get and display some recent follows + if (stats.following > 0) { + const following = await getUserFollowing(address, 5) + let connectionsHTML = '

Recent Follows:

' + + following.forEach(follow => { + connectionsHTML += ` +
+ ${follow.avatar ? `` : ''} + ${follow.ens || formatAddress(follow.address)} + ${follow.tags.length > 0 ? `${follow.tags.join(', ')}` : ''} +
+ ` + }) + + connectionsHTML += '
' + efpConnectionsElm.innerHTML = connectionsHTML + } + + document.getElementById('efpContainer').classList = '' + } else { + noEFPProfileElm.classList = '' + } + + document.getElementById('efpLoader').style.display = 'none' + } catch (error) { + console.error('Error displaying EFP profile:', error) + noEFPProfileElm.classList = '' + document.getElementById('efpLoader').style.display = 'none' + } +} -) +function formatAddress(address) { + return `${address.substring(0, 6)}...${address.substring(address.length - 4)}` +} +``` -const client = createClient({ +### 3. CSS Styling - autoConnect: true, +Add styles for the EFP profile display: - provider, +```css +.efp-stats { + display: flex; + gap: 20px; + margin: 15px 0; + padding: 15px; + background-color: #f8f9fa; + border-radius: 8px; +} -}) +.stat-item { + text-align: center; +} -// Use of the is mandatory to allow components that call +.stat-number { + display: block; + font-size: 24px; + font-weight: bold; + color: #333; +} -// `useSession()` anywhere in your application to access the `session` object. +.stat-label { + display: block; + font-size: 12px; + color: #666; + text-transform: uppercase; +} -export default function App({ +.connections-list { + display: flex; + flex-direction: column; + gap: 8px; + margin-top: 10px; +} - Component, +.connection-item { + display: flex; + align-items: center; + gap: 8px; + padding: 8px; + background-color: #fff; + border: 1px solid #e0e0e0; + border-radius: 6px; +} - pageProps, +.avatar-small { + width: 24px; + height: 24px; + border-radius: 50%; +} -}: AppProps<{ +.connection-name { + font-weight: 500; +} - session: Session; +.tags { + font-size: 11px; + color: #666; + background-color: #f0f0f0; + padding: 2px 6px; + border-radius: 3px; + margin-left: auto; +} -}>) { +.hidden { + display: none; +} +``` - return ( +## Advanced Features - +### Leaderboard Integration - +Get top users by followers or mutual connections: - +```javascript +async function getEFPLeaderboard(sort = 'followers', limit = 10) { + try { + const response = await fetch( + `https://api.ethfollow.xyz/api/v1/leaderboard/ranked?sort=${sort}&limit=${limit}` + ) + const data = await response.json() + return data.results + } catch (error) { + console.error('Error fetching leaderboard:', error) + return [] + } +} +``` - +### Search EFP Users - +Search for users by ENS name or address: - ) +```javascript +async function searchEFPUsers(searchTerm) { + try { + const response = await fetch( + `https://api.ethfollow.xyz/api/v1/leaderboard/search?term=${encodeURIComponent(searchTerm)}` + ) + const data = await response.json() + return data.results + } catch (error) { + console.error('Error searching EFP users:', error) + return [] + } +} +``` -We're going to now add the provider that will handle the message validation. Since it's not possible to sign in using the default page, the original provider should be removed from the list of providers before rendering. Modify pages/api/auth/[...nextauth].ts with the following: +### Check Mutual Connections -pages/api/auth/[...nextauth].ts +Find mutual follows between two addresses: -import NextAuth from "next-auth" +```javascript +async function getMutualConnections(address1, address2) { + try { + // Get following lists for both addresses + const [following1, following2] = await Promise.all([ + getUserFollowing(address1), + getUserFollowing(address2) + ]) + + // Find mutual connections + const mutuals = following1.filter(f1 => + following2.some(f2 => f2.address.toLowerCase() === f1.address.toLowerCase()) + ) + + return mutuals + } catch (error) { + console.error('Error getting mutual connections:', error) + return [] + } +} +``` -import CredentialsProvider from "next-auth/providers/credentials" +## API Endpoints Reference -import { getCsrfToken } from "next-auth/react" +| Endpoint | Description | +|----------|-------------| +| `/users/{address}/stats` | Get follower/following counts | +| `/users/{address}/following` | Get list of addresses user follows | +| `/users/{address}/followers` | Get list of user's followers | +| `/leaderboard/ranked` | Get ranked users by various metrics | +| `/leaderboard/search` | Search users by name or address | -import { SiweMessage } from "siwe" +## Error Handling -// For more information on each option (and a full list of options) go to +The implementation includes proper error handling for: -// https://next-auth.js.org/configuration/options +- Network connectivity issues +- Invalid or non-existent addresses +- API rate limiting +- Missing or empty social graph data -export default async function auth(req: any, res: any) { +## React Integration - const providers = [ +For React applications, consider using the Ethereum Identity Kit: - CredentialsProvider({ +```jsx +import { useEFPProfile } from '@ethereum-identity-kit/core' - name: "Ethereum", +function UserProfile({ address }) { + const { data: efpData, loading, error } = useEFPProfile(address) + + if (loading) return
Loading EFP data...
+ if (error) return
Error loading social graph
+ + return ( +
+

Social Graph

+
Followers: {efpData.followers}
+
Following: {efpData.following}
+
+ ) +} +``` - credentials: { +--- - message: { +# Creating a SIWE Message - label: "Message", +In this first tutorial, you'll learn how to create Sign in with Ethereum (SIWE) messages using the official TypeScript library. This is the foundation of SIWE authentication - generating properly formatted messages that users will sign with their wallets. - type: "text", +:::note - placeholder: "0x0", +Other supported libraries can be found at [here](../libraries/index.md) - signature: { +::: - label: "Signature", +## Learning Objectives - }, +By the end of this tutorial, you'll understand: - async authorize(credentials) { +- How to install and import the SIWE library +- The anatomy of a SIWE message +- How to generate secure nonces +- Best practices for message creation - try { +## Installation - const siwe = new SiweMessage(JSON.parse(credentials?.message || "{}")) +First, let's set up a new Node.js project and install the required dependencies: - const nextAuthUrl = new URL(process.env.NEXTAUTH_URL) +```bash +# Create a new project directory +mkdir siwe-tutorial +cd siwe-tutorial - const result = await siwe.verify({ +# Initialize a new Node.js project +npm init -y - signature: credentials?.signature || "", +# Install SIWE library and ethers for Ethereum utilities +npm install siwe ethers - domain: nextAuthUrl.host, +# Install development dependencies +npm install -D typescript @types/node ts-node +``` - nonce: await getCsrfToken({ req }), +## Basic Message Creation - }) +Let's start by creating our first SIWE message. Create a new file called `create-message.js`: - if (result.success) { +```javascript +const { SiweMessage } = require('siwe') - return { +// Basic SIWE message configuration +const domain = 'localhost:3000' +const origin = 'http://localhost:3000' +const address = '0x742d35Cc6C4C1Ca5d428d9eE0e9B1E1234567890' - id: siwe.address, +function createBasicMessage() { + const message = new SiweMessage({ + domain: domain, + address: address, + statement: 'Sign in to our awesome Web3 app!', + uri: origin, + version: '1', + chainId: 1, // Ethereum mainnet + }) - } + // Generate the formatted message string + const messageString = message.prepareMessage() + console.log('Generated SIWE Message:') + console.log(messageString) - return null + return message +} - } catch (e) { +// Run the function +createBasicMessage() +``` - }), +Run this script: - const isDefaultSigninPage = +```bash +node create-message.js +``` - req.method === "GET" && req.query.nextauth.includes("signin") +You should see output similar to: - // Hide Sign-In with Ethereum from default sign page +``` +Generated SIWE Message: +localhost:3000 wants you to sign in with your Ethereum account: +0x742d35Cc6C4C1Ca5d428d9eE0e9B1E1234567890 - if (isDefaultSigninPage) { +Sign in to our awesome Web3 app! - providers.pop() +URI: http://localhost:3000 +Version: 1 +Chain ID: 1 +Nonce: 32891756 +Issued At: 2023-10-31T16:25:24Z +``` - return await NextAuth(req, res, { +## Understanding Message Components - // https://next-auth.js.org/configuration/providers/oauth +Let's break down what each part of the message does: - providers, +### Required Fields - session: { +```javascript +const message = new SiweMessage({ + // The domain requesting the signature + domain: 'localhost:3000', - strategy: "jwt", + // User's Ethereum address (EIP-55 checksum format) + address: '0x742d35Cc6C4C1Ca5d428d9eE0e9B1E1234567890', - }, + // The URI being signed (usually your app's login endpoint) + uri: 'http://localhost:3000', - secret: process.env.NEXTAUTH_SECRET, + // SIWE specification version (always "1") + version: '1', - callbacks: { + // Blockchain network (1 = Ethereum mainnet, 5 = Goerli, etc.) + chainId: 1, +}) +``` - async session({ session, token }: { session: any; token: any }) { +### Optional Fields - session.address = token.sub +You can enhance messages with additional security and context: - session.user.name = token.sub +```javascript +const enhancedMessage = new SiweMessage({ + domain: 'localhost:3000', + address: address, + uri: origin, + version: '1', + chainId: 1, - session.user.image = "https://www.fillmurray.com/128/128" + // Optional human-readable statement + statement: + 'Welcome to our decentralized application. By signing this message, you agree to our terms of service.', - return session + // Custom nonce (if not provided, one is generated automatically) + nonce: 'custom-nonce-12345', - }) + // Message expiration (1 hour from now) + expirationTime: new Date(Date.now() + 60 * 60 * 1000).toISOString(), -The default sign-in page can't be used because there is no way to hook wagmi to listen for clicks on the default sign-in page provided by next-auth, so a custom page must be created to handle the sign-in flow. Create pages/siwe.tsx and populate it with the following: + // Message valid from (current time) + notBefore: new Date().toISOString(), -pages/siwe.tsx + // Request identifier for tracking + requestId: 'auth-request-001', -import { getCsrfToken, signIn, useSession } from "next-auth/react" + // Resources the user is requesting access to + resources: ['https://api.example.com/user-data', 'ipfs://QmHash123...'], +}) +``` -import { useAccount, useConnect, useNetwork, useSignMessage } from "wagmi" +## Secure Nonce Generation -import Layout from "../components/layout" +Nonces prevent replay attacks by ensuring each signature is unique. The SIWE library provides a secure nonce generator: -import { InjectedConnector } from 'wagmi/connectors/injected' +```javascript +const { SiweMessage, generateNonce } = require('siwe') -import { useEffect, useState } from "react" +function createMessageWithSecureNonce() { + // Generate a cryptographically secure nonce + const nonce = generateNonce() + console.log('Generated nonce:', nonce) -function Siwe() { + const message = new SiweMessage({ + domain: 'localhost:3000', + address: '0x742d35Cc6C4C1Ca5d428d9eE0e9B1E1234567890', + statement: 'Secure authentication with unique nonce', + uri: 'http://localhost:3000', + version: '1', + chainId: 1, + nonce: nonce, // Use the generated nonce + }) - const { signMessageAsync } = useSignMessage() + return message +} +``` - const { chain } = useNetwork() +## TypeScript Implementation - const { address, isConnected } = useAccount() +For better type safety, let's create a TypeScript version. Create `create-message.ts`: - const { connect } = useConnect({ +```typescript +import { SiweMessage, generateNonce } from 'siwe' - connector: new InjectedConnector(), +interface MessageOptions { + domain: string + address: string + uri: string + chainId: number + statement?: string + expirationTime?: string +} - const { data: session, status } = useSession() +function createSiweMessage(options: MessageOptions): SiweMessage { + const message = new SiweMessage({ + domain: options.domain, + address: options.address, + statement: options.statement || 'Sign in with Ethereum to authenticate', + uri: options.uri, + version: '1', + chainId: options.chainId, + nonce: generateNonce(), + issuedAt: new Date().toISOString(), + expirationTime: options.expirationTime, + }) - const handleLogin = async () => { + return message +} - const callbackUrl = "/protected" +// Example usage +const messageOptions: MessageOptions = { + domain: 'myapp.com', + address: '0x742d35Cc6C4C1Ca5d428d9eE0e9B1E1234567890', + uri: 'https://myapp.com/login', + chainId: 1, + statement: 'Welcome to MyApp! Sign this message to authenticate.', + expirationTime: new Date(Date.now() + 10 * 60 * 1000).toISOString(), // 10 minutes +} - const message = new SiweMessage({ +const siweMessage = createSiweMessage(messageOptions) +console.log(siweMessage.prepareMessage()) +``` - domain: window.location.host, +## Production Best Practices - address: address, +### Server-Side Message Creation - statement: "Sign in with Ethereum to the app.", +In production applications, **always create SIWE messages on the server**: - uri: window.location.origin, +```javascript +// ❌ DON'T: Client-side message creation +// This allows clients to manipulate security-critical fields + +// βœ… DO: Server-side message creation +function createServerSideMessage(userAddress, clientDomain) { + // Verify the domain matches your application + if (clientDomain !== 'myapp.com') { + throw new Error('Invalid domain') + } - version: "1", + const message = new SiweMessage({ + domain: 'myapp.com', // Use server-controlled domain + address: userAddress, + statement: 'Authenticate with MyApp', + uri: 'https://myapp.com/auth', + version: '1', + chainId: 1, + nonce: generateNonce(), // Server-generated nonce + issuedAt: new Date().toISOString(), // Server timestamp + }) - chainId: chain?.id, + return message +} +``` - nonce: await getCsrfToken(), +### Nonce Management - }) +Store and validate nonces to prevent replay attacks: - const signature = await signMessageAsync({ +```javascript +// Simple in-memory nonce storage (use Redis/database in production) +const usedNonces = new Set() - message: message.prepareMessage(), +function createMessageWithNonceTracking(address) { + const nonce = generateNonce() - signIn("credentials", { + // Store nonce as pending + usedNonces.add(nonce) - message: JSON.stringify(message), + const message = new SiweMessage({ + domain: 'localhost:3000', + address: address, + uri: 'http://localhost:3000', + version: '1', + chainId: 1, + nonce: nonce, + issuedAt: new Date().toISOString(), + expirationTime: new Date(Date.now() + 5 * 60 * 1000).toISOString(), // 5 minutes + }) - redirect: false, + return { message, nonce } +} - signature, +function validateNonce(nonce) { + if (!usedNonces.has(nonce)) { + throw new Error('Invalid or expired nonce') + } - callbackUrl, + // Remove nonce after use to prevent replay + usedNonces.delete(nonce) + return true +} +``` - } catch (error) { +## Error Handling - window.alert(error) +Always handle potential errors when creating messages: - useEffect(() => { +```javascript +function safeCreateMessage(options) { + try { + // Validate required fields + if (!options.domain || !options.address || !options.uri) { + throw new Error('Missing required fields') + } + + // Validate Ethereum address format + if (!/^0x[a-fA-F0-9]{40}$/.test(options.address)) { + throw new Error('Invalid Ethereum address format') + } + + // Create message + const message = new SiweMessage({ + domain: options.domain, + address: options.address, + statement: options.statement, + uri: options.uri, + version: '1', + chainId: options.chainId || 1, + nonce: generateNonce(), + issuedAt: new Date().toISOString(), + }) + + return { success: true, message } + } catch (error) { + console.error('Error creating SIWE message:', error.message) + return { success: false, error: error.message } + } +} +``` - console.log(isConnected); +## Testing Your Implementation - if (isConnected && !session) { +Create a test script to verify your message creation: - handleLogin() +```javascript +const { SiweMessage, generateNonce } = require('siwe') - }, [isConnected]) +function testMessageCreation() { + console.log('Testing SIWE message creation...\n') - + // Test 1: Basic message + const basicMessage = new SiweMessage({ + domain: 'test.com', + address: '0x742d35Cc6C4C1Ca5d428d9eE0e9B1E1234567890', + uri: 'http://test.com', + version: '1', + chainId: 1, + }) - +## Project Setup - +Let's create a new Node.js backend project: -export async function getServerSideProps(context: any) { +```bash +# Create backend directory +mkdir siwe-backend +cd siwe-backend - return { +# Initialize Node.js project +npm init -y - props: { +# Install dependencies +npm install express siwe ethers cors helmet express-rate-limit +npm install -D nodemon - csrfToken: await getCsrfToken(context), +# Create basic project structure +mkdir src routes middleware utils +touch src/server.js routes/auth.js middleware/auth.js utils/nonce.js +``` -Siwe.Layout = Layout +Update `package.json` to add scripts: -export default Siwe +```json +{ + "scripts": { + "start": "node src/server.js", + "dev": "nodemon src/server.js", + "test": "echo \"Error: no test specified\" && exit 1" + } +} +``` -Modify pages/styles.css by appending the following CSS: +## Basic Express Server -pages/styles.css +Create `src/server.js`: -button { +```javascript +const express = require('express') +const cors = require('cors') +const helmet = require('helmet') +const rateLimit = require('express-rate-limit') - margin: 0 0 0.75rem 0; +// Import routes +const authRoutes = require('../routes/auth') - text-decoration: none; +const app = express() +const PORT = process.env.PORT || 3001 - padding: 0.7rem 1.4rem; +// Security middleware +app.use(helmet()) - border: 1px solid #346df1; +// Rate limiting +const limiter = rateLimit({ + windowMs: 15 * 60 * 1000, // 15 minutes + max: 100, // Limit each IP to 100 requests per windowMs + message: 'Too many requests from this IP, please try again later.', +}) +app.use(limiter) + +// CORS configuration +app.use( + cors({ + origin: process.env.FRONTEND_URL || 'http://localhost:3000', + credentials: true, + }) +) - background-color: #346df1; +// Body parsing middleware +app.use(express.json({ limit: '10mb' })) +app.use(express.urlencoded({ extended: true })) + +// Health check endpoint +app.get('/health', (req, res) => { + res.status(200).json({ + status: 'OK', + timestamp: new Date().toISOString(), + message: 'SIWE Backend is running', + }) +}) - color: #fff; +// Authentication routes +app.use('/auth', authRoutes) - font-size: 1rem; +// Global error handler +app.use((err, req, res, next) => { + console.error('Global error handler:', err) - border-radius: 4px; + res.status(err.status || 500).json({ + success: false, + error: { + message: err.message || 'Internal server error', + ...(process.env.NODE_ENV === 'development' && { stack: err.stack }), + }, + }) +}) - transition: all 0.1s ease-in-out; +// 404 handler +app.use('*', (req, res) => { + res.status(404).json({ + success: false, + error: { + message: 'Route not found', + }, + }) +}) - font-weight: 500; +app.listen(PORT, () => { + console.log(`πŸš€ SIWE Backend server running on port ${PORT}`) + console.log(`πŸ“Š Health check: http://localhost:${PORT}/health`) +}) - position: relative; +module.exports = app +``` -button:hover { +## Nonce Management - cursor: pointer; +Create `utils/nonce.js`: - box-shadow: inset 0 0 5rem rgb(0 0 0 / 20%); +```javascript +const { generateNonce } = require('siwe') -Finally, modify the components/header.tsx in order to clean it up and add a SIWE tab to navigate to the newly created page: +// In-memory storage for demo (use Redis/database in production) +class NonceManager { + constructor() { + this.nonces = new Map() // Map + this.cleanupInterval = 5 * 60 * 1000 // 5 minutes + this.maxAge = 10 * 60 * 1000 // 10 minutes -components/header.tsx + // Start cleanup interval + setInterval(() => this.cleanup(), this.cleanupInterval) + } -import { signOut, useSession } from "next-auth/react" + /** + * Generate a new nonce and store it + */ + generateNonce() { + const nonce = generateNonce() + const timestamp = Date.now() -import Link from "next/link" + this.nonces.set(nonce, { + timestamp, + used: false, + }) -import { useDisconnect } from "wagmi" + console.log(`Generated nonce: ${nonce}`) + return nonce + } -import styles from "./header.module.css" + /** + * Validate and consume a nonce + */ + validateNonce(nonce) { + const nonceData = this.nonces.get(nonce) + + if (!nonceData) { + throw new Error('Invalid nonce: not found') + } + + if (nonceData.used) { + throw new Error('Invalid nonce: already used') + } + + // Check if nonce is expired + const now = Date.now() + if (now - nonceData.timestamp > this.maxAge) { + this.nonces.delete(nonce) + throw new Error('Invalid nonce: expired') + } + + // Mark as used + nonceData.used = true + this.nonces.set(nonce, nonceData) + + console.log(`Validated and consumed nonce: ${nonce}`) + return true + } -// The approach used in this component shows how to build a sign in and sign out + /** + * Clean up expired nonces + */ + cleanup() { + const now = Date.now() + let cleaned = 0 + + for (const [nonce, data] of this.nonces.entries()) { + if (now - data.timestamp > this.maxAge) { + this.nonces.delete(nonce) + cleaned++ + } + } + + if (cleaned > 0) { + console.log(`Cleaned up ${cleaned} expired nonces`) + } + } -// component that works on pages which support both client and server side + /** + * Get statistics about nonce usage + */ + getStats() { + const total = this.nonces.size + let used = 0 + let expired = 0 + const now = Date.now() + + for (const [nonce, data] of this.nonces.entries()) { + if (data.used) used++ + if (now - data.timestamp > this.maxAge) expired++ + } + + return { + total, + used, + available: total - used - expired, + expired, + } + } +} -// rendering, and avoids any flash incorrect content on initial page load. +// Export singleton instance +module.exports = new NonceManager() +``` -export default function Header() { +## Authentication Routes - const loading = status === "loading" +Create `routes/auth.js`: - const { disconnect } = useDisconnect() +```javascript +const express = require('express') +const { SiweMessage } = require('siwe') +const nonceManager = require('../utils/nonce') -
+const router = express.Router() - +/** + * POST /auth/logout + * Logout user (invalidate session) + */ +router.post('/logout', (req, res) => { + // In a real application, you would: + // 1. Invalidate the user's session/JWT token + // 2. Clear any stored user data + // 3. Log the logout event + + res.status(200).json({ + success: true, + message: 'Logged out successfully', + }) +}) -
+/** + * GET /auth/stats + * Get nonce manager statistics (development only) + */ +router.get('/stats', (req, res) => { + if (process.env.NODE_ENV === 'production') { + return res.status(404).json({ + success: false, + error: { message: 'Not found' }, + }) + } -

+## Authentication Middleware - {!session && ( +Create `middleware/auth.js` for protecting routes: - <> +```javascript +/** + * Middleware to protect routes that require authentication + * In a real app, this would verify JWT tokens or sessions + */ +function requireAuth(req, res, next) { + const authHeader = req.headers.authorization + + if (!authHeader) { + return res.status(401).json({ + success: false, + error: { + message: 'Authorization header required', + }, + }) + } - + // In a real implementation, verify JWT token here + // For demo purposes, we'll just check for a valid format + const token = authHeader.replace('Bearer ', '') + + if (!token || token.length < 10) { + return res.status(401).json({ + success: false, + error: { + message: 'Invalid authorization token', + }, + }) + } - You are not signed in + // Add user info to request (would come from JWT payload) + req.user = { + address: '0x...', // Would be extracted from verified JWT + } - + next() +} - +/** + * Middleware to validate Ethereum address format + */ +function validateAddress(addressField = 'address') { + return (req, res, next) => { + const address = req.body[addressField] || req.params[addressField] + + if (!address) { + return res.status(400).json({ + success: false, + error: { + message: `${addressField} is required`, + }, + }) + } + + // Validate Ethereum address format + if (!/^0x[a-fA-F0-9]{40}$/.test(address)) { + return res.status(400).json({ + success: false, + error: { + message: 'Invalid Ethereum address format', + }, + }) + } + + next() + } +} - )} +module.exports = { + requireAuth, + validateAddress, +} +``` - {session?.user && ( +## Enhanced Verification with Security Checks - {session.user.image && ( +Create `utils/verification.js`: - + /** + * Validate SIWE message structure and content + */ + async validateMessage(siweMessage, additionalChecks = {}) { + // Check domain whitelist + if (!this.config.allowedDomains.includes(siweMessage.domain)) { + throw new Error(`Domain '${siweMessage.domain}' not allowed`) + } + + // Check chain ID whitelist + if (!this.config.allowedChainIds.includes(siweMessage.chainId)) { + throw new Error(`Chain ID '${siweMessage.chainId}' not allowed`) + } + + // Check message age + const issuedAt = new Date(siweMessage.issuedAt) + const now = new Date() + const messageAge = now.getTime() - issuedAt.getTime() + + if (messageAge > this.config.maxMessageAge) { + throw new Error('Message is too old') + } + + if (messageAge < 0) { + throw new Error('Message issued in the future') + } + + // Check expiration + if (siweMessage.expirationTime) { + const expirationTime = new Date(siweMessage.expirationTime) + if (now > expirationTime) { + throw new Error('Message has expired') + } + } + + // Check not before + if (siweMessage.notBefore) { + const notBefore = new Date(siweMessage.notBefore) + if (now < notBefore) { + throw new Error('Message is not yet valid') + } + } + + // Check statement requirement + if (this.config.requireStatement && !siweMessage.statement) { + throw new Error('Statement is required') + } + + // Additional custom checks + if ( + additionalChecks.requiredAddress && + siweMessage.address.toLowerCase() !== + additionalChecks.requiredAddress.toLowerCase() + ) { + throw new Error('Address mismatch') + } + + if ( + additionalChecks.requiredChainId && + siweMessage.chainId !== additionalChecks.requiredChainId + ) { + throw new Error('Chain ID mismatch') + } + } +} - )} +module.exports = SiweVerifier +``` - +## Testing the Backend - Signed in as +Create a simple test script `test-backend.js`: -
+```javascript +const axios = require('axios') + +const BASE_URL = 'http://localhost:3001' + +async function testBackend() { + try { + console.log('πŸ§ͺ Testing SIWE Backend API...\n') + + // Test 1: Health check + console.log('1. Testing health check...') + const health = await axios.get(`${BASE_URL}/health`) + console.log('βœ… Health check:', health.data.status) + + // Test 2: Generate nonce + console.log('\n2. Testing nonce generation...') + const nonceResponse = await axios.get(`${BASE_URL}/auth/nonce`) + const nonce = nonceResponse.data.nonce + console.log('βœ… Nonce generated:', nonce) + + // Test 3: Try invalid verification + console.log('\n3. Testing invalid verification...') + try { + await axios.post(`${BASE_URL}/auth/verify`, { + message: 'invalid message', + signature: 'invalid signature', + }) + } catch (error) { + console.log( + 'βœ… Invalid verification properly rejected:', + error.response.status + ) + } + + // Test 4: Get stats (dev only) + console.log('\n4. Testing stats endpoint...') + const stats = await axios.get(`${BASE_URL}/auth/stats`) + console.log('βœ… Stats:', stats.data.stats) + + console.log('\nπŸŽ‰ All tests passed!') + } catch (error) { + console.error('❌ Test failed:', error.message) + if (error.response) { + console.error('Response:', error.response.data) + } + } +} - {session.user.email ?? session.user.name} +// Run tests +testBackend() +``` - { +# Security settings +MAX_MESSAGE_AGE=600000 +ALLOWED_DOMAINS=localhost:3000,localhost:3001 +ALLOWED_CHAIN_IDS=1,5,137 - e.preventDefault() +# Rate limiting +RATE_LIMIT_WINDOW=900000 +RATE_LIMIT_MAX=100 +``` - disconnect() +Update `src/server.js` to use environment variables: - signOut() +```javascript +require('dotenv').config() + +// Use environment variables for CORS +app.use( + cors({ + origin: process.env.FRONTEND_URL || 'http://localhost:3000', + credentials: true, + }) +) +``` - }} +## Running the Backend - > +Start your backend server: - Sign out +```bash +# Install additional dependencies +npm install dotenv axios - +# Run in development mode +npm run dev +``` -

+Test the endpoints: -
+```bash +# Test health endpoint +curl http://localhost:3001/health - +Implement strict rate limits: -
+```javascript +const authLimiter = rateLimit({ + windowMs: 15 * 60 * 1000, // 15 minutes + max: 5, // 5 attempts per window + message: 'Too many authentication attempts', +}) -Run the application using the following commands: +router.post('/verify', authLimiter, async (req, res) => { + // Verification logic +}) +``` -yarn install +### 3. Logging and Monitoring -yarn dev +Add comprehensive logging: -Navigate to localhost:3000 - now you are now ready to Sign-In with Ethereum. Just click the SIWE link in the header, hit the "Sign-In with Ethereum" button, sign the message, and you are now authenticated. +```javascript +const winston = require('winston') + +const logger = winston.createLogger({ + level: 'info', + format: winston.format.json(), + transports: [ + new winston.transports.File({ filename: 'error.log', level: 'error' }), + new winston.transports.File({ filename: 'combined.log' }), + ], +}) -localhost:3000 +// Log authentication attempts +logger.info('Authentication attempt', { + address: siweMessage.address, + domain: siweMessage.domain, + timestamp: new Date().toISOString(), +}) +``` -SIWE +--- -If you face the following error: +# Frontend Setup -Error: Invalid with child. Please remove or use . +In this tutorial, you'll build a React frontend that connects to user wallets and requests SIWE message signatures. You'll learn how to detect wallet connections, handle user addresses, and request message signatures using ethers.js. -go to components/footer.tsx and remove the tag from Policy at line 21. +## Learning Objectives -components/footer.tsx +By the end of this tutorial, you'll understand: -Next Add siwe, ethers, and wagmi as dependencies. In this example, we're using , which is a well-known React hooks library for Ethereum. In your terminal, navigate to the project we originally cloned and add the dependencies via the following commands: +- How to connect to MetaMask and other Ethereum wallets +- How to detect user address changes and network switches +- How to request message signatures from connected wallets +- Best practices for wallet integration and user experience -wagmi +## Project Setup -# Auth0 +Let's create a React application with the necessary dependencies: -IntegrationsAuth0Auth0 marketplace integrationPreviousNextAuth.jsNextSecurity ConsiderationsLast updated 3 years ago +```bash +# Create a new React app +npx create-react-app siwe-frontend +cd siwe-frontend -Auth0 marketplace integration +# Install Web3 dependencies +npm install siwe ethers -Auth0 is the leading platform for authentication and authorization for web2 applications and services in retail, publishing, B2B SaaS, and more. Sign-In with Ethereum was recently integrated into Auth0 by Spruce in collaboration with Auth0 and the Auth0 Lab team. +# Install additional UI dependencies (optional) +npm install styled-components -The integration uses the open-source OpenID Connect Identity Provider (hosted under oidc.login.xyz) for Sign-In with Ethereum implementation in Rust: +# Start the development server +npm start +``` -The entire workflow involved can be seen in this activity diagram: +## Wallet Connection Component -After hitting the login button, users are redirected to the Auth0 flow and Sign-In with Ethereum using the provided interface. Once authenticated, users are then redirected back to the application where they can view their gallery. +Create a new file `src/components/WalletConnect.js`: -As part of the login, the application also resolves the user's ENS name if present. Users can then return to the main splash screen or disconnect from the application. +```javascript +import React, { useState, useEffect } from 'react' +import { ethers } from 'ethers' + +const WalletConnect = () => { + const [isConnected, setIsConnected] = useState(false) + const [userAddress, setUserAddress] = useState('') + const [chainId, setChainId] = useState(null) + const [provider, setProvider] = useState(null) + const [signer, setSigner] = useState(null) + + // Check if wallet is already connected on component mount + useEffect(() => { + checkWalletConnection() + setupEventListeners() + }, []) + + const checkWalletConnection = async () => { + if (typeof window.ethereum !== 'undefined') { + try { + const provider = new ethers.providers.Web3Provider( + window.ethereum + ) + const accounts = await provider.listAccounts() + + if (accounts.length > 0) { + const signer = provider.getSigner() + const address = await signer.getAddress() + const network = await provider.getNetwork() + + setProvider(provider) + setSigner(signer) + setUserAddress(address) + setChainId(network.chainId) + setIsConnected(true) + } + } catch (error) { + console.error('Error checking wallet connection:', error) + } + } + } -An example application to show off the authentication flow can be found . The example features a mock NFT gallery website where users can Sign-In with Ethereum, and their NFT holdings are resolved via the OpenSea API once authenticated. + const setupEventListeners = () => { + if (window.ethereum) { + // Listen for account changes + window.ethereum.on('accountsChanged', handleAccountsChanged) -# πŸ”“Security Considerations + // Listen for chain changes + window.ethereum.on('chainChanged', handleChainChanged) -Additional SupportπŸ”“Security ConsiderationsSign-In with Ethereum Security ConsiderationsWhen using SIWE, implementers should aim to mitigate security issues on both the client and server. This is a growing collection of best practices for implementers, but no security checklist can ever be truly complete.Message Generation and ValidationWhen processing a SIWE message on the backend, it must be validated as per specified in EIP-4361. This is achieved in the quickstart guide by creating the entire SIWE message on the backend, and verifying that the message signed was identical with a valid signature.However, some implementers may choose not to generate the signing message entirely on the server-side, and instead, have the frontend request specific field values from the server or otherwise agree on a value generation method. The backend then asserts that the received signed message matches what is expected during verification.(WIP) Notes on select fields and their value selection:nonce. To prevent replay attacks, a nonce should be selected with sufficient entropy for the use case, and the server should assert that the nonce matches the expected value. In some systems, the server and client may agree to use a nonce derived from a recent block hash or system time, reducing server interaction.domain. Wallets conforming to EIP-4361 are able to check for (or even generate) correct domain bindings to prevent phishing attacks, i.e., that the website "example.org" is indeed securely serving the SIWE message beginning with "example.org wants you to sign in with..."PreviousAuth0NextENS Profile ResolutionLast updated 3 years ago + // Listen for connection changes + window.ethereum.on('connect', handleConnect) + window.ethereum.on('disconnect', handleDisconnect) + } + } -Sign-In with Ethereum Security Considerations + const handleAccountsChanged = accounts => { + if (accounts.length === 0) { + // User disconnected + disconnect() + } else { + // User switched accounts + setUserAddress(accounts[0]) + } + } -When using SIWE, implementers should aim to mitigate security issues on both the client and server. This is a growing collection of best practices for implementers, but no security checklist can ever be truly complete. + const handleChainChanged = chainId => { + // Convert hex to decimal + const decimalChainId = parseInt(chainId, 16) + setChainId(decimalChainId) -When processing a SIWE message on the backend, it must be validated as per specified in EIP-4361. This is achieved in the quickstart guide by creating the entire SIWE message on the backend, and verifying that the message signed was identical with a valid signature. + // Reload the page to reset the dapp state + window.location.reload() + } -However, some implementers may choose not to generate the signing message entirely on the server-side, and instead, have the frontend request specific field values from the server or otherwise agree on a value generation method. The backend then asserts that the received signed message matches what is expected during verification. + const handleConnect = connectInfo => { + console.log('Wallet connected:', connectInfo) + checkWalletConnection() + } -(WIP) Notes on select fields and their value selection: + const handleDisconnect = error => { + console.log('Wallet disconnected:', error) + disconnect() + } -nonce. To prevent replay attacks, a nonce should be selected with sufficient entropy for the use case, and the server should assert that the nonce matches the expected value. In some systems, the server and client may agree to use a nonce derived from a recent block hash or system time, reducing server interaction. + const connectWallet = async () => { + if (typeof window.ethereum === 'undefined') { + alert('MetaMask is not installed! Please install MetaMask.') + return + } + + try { + // Request account access + await window.ethereum.request({ method: 'eth_requestAccounts' }) + + // Create provider and signer + const provider = new ethers.providers.Web3Provider(window.ethereum) + const signer = provider.getSigner() + const address = await signer.getAddress() + const network = await provider.getNetwork() + + setProvider(provider) + setSigner(signer) + setUserAddress(address) + setChainId(network.chainId) + setIsConnected(true) + + console.log('Connected to wallet:', { + address, + chainId: network.chainId, + networkName: network.name, + }) + } catch (error) { + console.error('Error connecting wallet:', error) + + // Handle specific error cases + if (error.code === 4001) { + alert('Please connect to MetaMask.') + } else { + alert('An error occurred while connecting to the wallet.') + } + } + } -domain. Wallets conforming to EIP-4361 are able to check for (or even generate) correct domain bindings to prevent phishing attacks, i.e., that the website "example.org" is indeed securely serving the SIWE message beginning with "example.org wants you to sign in with..." + const disconnect = () => { + setIsConnected(false) + setUserAddress('') + setChainId(null) + setProvider(null) + setSigner(null) + } -Last updated 3 years ago + const formatAddress = address => { + if (!address) return '' + return `${address.substring(0, 6)}...${address.substring( + address.length - 4 + )}` + } -# ENS Profile Resolution + const getNetworkName = chainId => { + const networks = { + 1: 'Ethereum Mainnet', + 5: 'Goerli Testnet', + 137: 'Polygon Mainnet', + 80001: 'Polygon Mumbai', + } + return networks[chainId] || `Network ${chainId}` + } -Additional SupportENS Profile ResolutionResolve ENS Profiles for users signing into a servicePreviousSecurity ConsiderationsNextCommunity HighlightsLast updated 3 years ago + return ( +
+

Wallet Connection

+ + {!isConnected ? ( +
+

Connect your wallet to get started

+ +
+ ) : ( +
+

+ Status: Connected βœ… +

+

+ Address: {formatAddress(userAddress)} +

+

+ Full Address:{' '} + {userAddress} +

+

+ Network: {getNetworkName(chainId)} +

+ +
+ )} +
+ ) +} + +export default WalletConnect +``` + +## SIWE Message Signing Component + +Create `src/components/SiweAuth.js`: -Resolve ENS Profiles for users signing into a service +```javascript +import React, { useState } from 'react' +import { SiweMessage } from 'siwe' +import { ethers } from 'ethers' + +const SiweAuth = ({ userAddress, signer, chainId }) => { + const [message, setMessage] = useState('') + const [signature, setSignature] = useState('') + const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState('') + + const createSiweMessage = () => { + if (!userAddress) { + setError('No wallet connected') + return + } + + try { + const siweMessage = new SiweMessage({ + domain: window.location.host, + address: userAddress, + statement: + 'Welcome to our Web3 app! Sign this message to authenticate securely.', + uri: window.location.origin, + version: '1', + chainId: chainId || 1, + nonce: Math.random().toString(36).substring(2, 15), // Simple nonce for demo + issuedAt: new Date().toISOString(), + expirationTime: new Date( + Date.now() + 10 * 60 * 1000 + ).toISOString(), // 10 minutes + }) + + const formattedMessage = siweMessage.prepareMessage() + setMessage(formattedMessage) + setError('') + + console.log('Created SIWE message:', formattedMessage) + } catch (error) { + console.error('Error creating SIWE message:', error) + setError('Failed to create message: ' + error.message) + } + } ->= v5.5.3 + const signMessage = async () => { + if (!message) { + setError('No message to sign') + return + } + + if (!signer) { + setError('No signer available') + return + } + + setIsLoading(true) + setError('') + + try { + console.log('Requesting signature for message:', message) + + // Request signature from wallet + const signature = await signer.signMessage(message) + + console.log('Signature received:', signature) + setSignature(signature) + } catch (error) { + console.error('Error signing message:', error) + + // Handle user rejection + if (error.code === 4001) { + setError('User rejected the signature request') + } else if (error.code === -32603) { + setError('Internal wallet error. Please try again.') + } else { + setError('Failed to sign message: ' + error.message) + } + } finally { + setIsLoading(false) + } + } -The user's linked (ENS) information can be retrieved after their Ethereum address is known. After the user connects with their wallet but before they Sign-In with Ethereum, ENS information can be used to provide additional context to the user about which account is connected. + const verifySignature = async () => { + if (!message || !signature) { + setError('Message and signature required for verification') + return + } + + try { + // Parse the message back to SiweMessage object + const siweMessage = new SiweMessage(message) + + // Verify the signature + const verification = await siweMessage.verify({ signature }) + + if (verification.success) { + console.log( + 'βœ… Signature verification successful!', + verification + ) + alert('Signature verified successfully!') + } else { + console.log('❌ Signature verification failed:', verification) + setError('Signature verification failed') + } + } catch (error) { + console.error('Error verifying signature:', error) + setError('Verification failed: ' + error.message) + } + } -If the user completes Sign-In with Ethereum to authenticate, the ENS data resolved from their Ethereum address may be used as part of the authenticated session, such as checking that the address's default ENS name is alisha.eth before granting access to certain pages or resources. + const resetDemo = () => { + setMessage('') + setSignature('') + setError('') + } -alisha.eth + return ( +
+

SIWE Authentication

+ + {!userAddress && ( +

+ Please connect your wallet first to use SIWE authentication. +

+ )} + + {userAddress && ( + <> +
+ + + + + + + +
+ + {error && ( +
+ Error: {error} +
+ )} + + {message && ( +
+

Generated Message:

+
+								{message}
+							
+
+ )} + + {signature && ( +
+

Generated Signature:

+
+ {signature} +
+
+ )} + + )} +
+ ) +} + +export default SiweAuth +``` + +## Main App Component + +Update `src/App.js` to use our components: -The information can be retrieved using : +```javascript +import React, { useState } from 'react' +import WalletConnect from './components/WalletConnect' +import SiweAuth from './components/SiweAuth' +import './App.css' -import { ethers } from 'ethers'; +function App() { + const [walletInfo, setWalletInfo] = useState({ + isConnected: false, + userAddress: '', + chainId: null, + provider: null, + signer: null, + }) + + const handleWalletConnection = connectionInfo => { + setWalletInfo(connectionInfo) + } -const provider = new ethers.providers.EtherscanProvider() + return ( +
+
+

Sign in with Ethereum Demo

+

A complete tutorial on Web3 authentication

+
+ +
+ + + + +
+

How it works:

+
    +
  1. + Connect Wallet: Click "Connect + Wallet" to connect your MetaMask or compatible + wallet +
  2. +
  3. + Create Message: Generate a SIWE + message with your address and current timestamp +
  4. +
  5. + Sign Message: Use your wallet to + cryptographically sign the authentication message +
  6. +
  7. + Verify Signature: Validate that the + signature matches the message and address +
  8. +
+ +
+ πŸ’‘ Pro Tip: In a real application, + message creation and signature verification would happen + on your backend server for security. This demo shows the + complete flow in the browser for educational purposes. +
+
+
+
+ ) +} + +export default App +``` + +## Enhanced Wallet Detection + +Create `src/utils/walletUtils.js` for better wallet handling: -const address = '0x9297A132AF2A1481441AB8dc1Ce6e243d879eaFD' +```javascript +// Detect available wallets +export const detectWallets = () => { + const wallets = [] + + // MetaMask + if (window.ethereum?.isMetaMask) { + wallets.push({ + name: 'MetaMask', + icon: '🦊', + provider: window.ethereum, + type: 'metamask', + }) + } -const ensName = await provider.lookupAddress(address) + // Coinbase Wallet + if (window.ethereum?.isCoinbaseWallet) { + wallets.push({ + name: 'Coinbase Wallet', + icon: 'πŸ”΅', + provider: window.ethereum, + type: 'coinbase', + }) + } -const ensAvatarUrl = await provider.getAvatar(ensName) + // WalletConnect (if injected) + if ( + window.ethereum && + !window.ethereum.isMetaMask && + !window.ethereum.isCoinbaseWallet + ) { + wallets.push({ + name: 'Injected Wallet', + icon: 'πŸ’Ό', + provider: window.ethereum, + type: 'injected', + }) + } -const ensResolver = await provider.getResolver(ensName) + return wallets +} + +// Format Ethereum address for display +export const formatAddress = (address, startLength = 6, endLength = 4) => { + if (!address) return '' + if (address.length < startLength + endLength) return address + return `${address.substring(0, startLength)}...${address.substring( + address.length - endLength + )}` +} + +// Get network information +export const getNetworkInfo = chainId => { + const networks = { + 1: { name: 'Ethereum Mainnet', color: '#627eea' }, + 5: { name: 'Goerli Testnet', color: '#f6c343' }, + 137: { name: 'Polygon Mainnet', color: '#8247e5' }, + 80001: { name: 'Polygon Mumbai', color: '#8247e5' }, + 56: { name: 'BSC Mainnet', color: '#f0b90b' }, + 97: { name: 'BSC Testnet', color: '#f0b90b' }, + 43114: { name: 'Avalanche C-Chain', color: '#e84142' }, + 250: { name: 'Fantom Opera', color: '#1969ff' }, + 42161: { name: 'Arbitrum One', color: '#96bedc' }, + 10: { name: 'Optimism', color: '#ff0420' }, + } -// You can fetch any key stored in their ENS profile. + return ( + networks[chainId] || { + name: `Network ${chainId}`, + color: '#666666', + } + ) +} -const twitterHandle = await ensResolver.getText('com.twitter') +// Validate Ethereum address +export const isValidAddress = address => { + return /^0x[a-fA-F0-9]{40}$/.test(address) +} -The user's Avatar location can be resolved using ensResolver.getText, butgetAvatar is recommended as it resolves NFT avatars to a URL. +// Switch network (for MetaMask) +export const switchNetwork = async chainId => { + if (!window.ethereum) { + throw new Error('No wallet found') + } -ensResolver.getText + try { + await window.ethereum.request({ + method: 'wallet_switchEthereumChain', + params: [{ chainId: `0x${chainId.toString(16)}` }], + }) + } catch (error) { + // Network not added to wallet + if (error.code === 4902) { + throw new Error(`Network ${chainId} not added to wallet`) + } + throw error + } +} +``` -getAvatar +## Error Handling and User Experience -The EtherscanProvider above uses a shared API key and is therefore rate-limited. For a production application, we strongly recommend using a new API key with or . +Create `src/components/ErrorBoundary.js`: -EtherscanProvider +```javascript +import React from 'react' -# Community Highlights +class ErrorBoundary extends React.Component { + constructor(props) { + super(props) + this.state = { hasError: false, error: null } + } -Additional SupportCommunity HighlightsCommunity-Driven Sign-In with Ethereum Guides and BuildsPreviousENS Profile ResolutionNextOIDC ProviderLast updated 2 years ago + static getDerivedStateFromError(error) { + return { hasError: true, error } + } -Community-Driven Sign-In with Ethereum Guides and Builds + componentDidCatch(error, errorInfo) { + console.error('SIWE Error Boundary caught an error:', error, errorInfo) + } -The following is a list of libraries, guides, and more made available by the Sign-In with Ethereum community. Note - some of the listed items may have not yet undergone formal security audits, and may also be experimental or alpha stage. + render() { + if (this.state.hasError) { + return ( +
+

🚫 Something went wrong

+

+ An error occurred in the SIWE authentication component. +

+
+ Error Details +
+							{this.state.error?.toString()}
+						
+
+ +
+ ) + } + + return this.props.children + } +} -(Django) +export default ErrorBoundary +``` -(C#) +## Testing Your Frontend -(Next.js + Iron-Session) +1. **Start the development server:** -(WordPress) +```bash +npm start +``` -(Serverless SIWE) +2. **Test wallet connection:** -(Fastify SIWE) + - Click "Connect Wallet" + - Approve the connection in MetaMask + - Verify address and network display correctly -(Java) +3. **Test message creation:** -# OIDC Provider + - Click "Create SIWE Message" + - Review the generated message format + - Verify all required fields are present -ServersOIDC ProviderAn OpenID Connect Identity Provider for Sign-In with EthereumPreviousCommunity HighlightsNextDeployment GuideLast updated 2 years ago +4. **Test message signing:** -An OpenID Connect Identity Provider for Sign-In with Ethereum + - Click "Sign Message" + - Approve the signature in MetaMask + - Verify signature is generated -Many organizations want to consolidate the Sign-In with Ethereum workflow to a single identity service (Identity Provider or IdP) that could be used to access all their federated services (Relying Parties or RPs) using to forward the user's session. This reduces overhead and mitigates security risks by consolidating authentication to one protected site instead of several, especially in complex IT systems that have many services for their users to access. +5. **Test signature verification:** + - Click "Verify Signature" + - Confirm verification succeeds -The OIDC Provider implementation of Sign-In with Ethereum can be found here: +--- -Currently, two runtime modes are supported: (1) a standalone executable (using Axum and Redis) and (2) a WASM module within a Cloudflare Worker. Both are built from the same codebase, specializing at build time. Compilation with a cargo target of wasm32 will build for Cloudflare Worker deployments. +# Message Troubleshooting -cargo +The SIWE Message Validator is an interactive tool that helps you validate, lint, and debug Sign in with Ethereum (SIWE) messages for compliance with the [EIP-4361](https://eips.ethereum.org/EIPS/eip-4361) specification. -wasm32 +## Validation Categories -For convenience, a fully deployed and hosted version of the OpenID Connect Provider (OP) is available under . SeeHosted OIDC Provider for more information. +### Format Validation +- Message structure and grammar compliance +- Required field presence and formatting +- Timestamp format (RFC 3339/ISO 8601) +- Ethereum address format validation +- URI format checking -# Deployment Guide +### Security Validation +- Nonce entropy and uniqueness analysis +- Domain binding verification +- Time-based security checks +- Resource access scope validation +- Common vulnerability detection -ServersOIDC ProviderDeployment GuideDeploying the self-hosted SIWE OIDC ProviderPreviousOIDC ProviderNextHosted OIDC ProviderLast updated 3 years ago +### Compliance Validation +- EIP-4361 specification adherence +- Version compatibility checking +- Chain ID validation +- Standard field requirements -Deploying the self-hosted SIWE OIDC Provider +## Common Errors -First, ensure is installed and ready to interact with Cloudflare Worker API. You will need a Cloudflare account. Clone the project repository, and setup your Cloudflare Worker project after authenticating with Wrangler. +### Unnecessary Line Breaks +``` +❌ example.com wants you to sign in with your Ethereum account: +❌ +❌ 0x742d35Cc6C4C1Ca5d428d9eE0e9B1E1234567890 +❌ +βœ… example.com wants you to sign in with your Ethereum account: +βœ… 0x742d35Cc6C4C1Ca5d428d9eE0e9B1E1234567890 +βœ… +``` +**Fix**: Only add line breaks where specified in ERC-4361. -git clone https://github.com/spruceid/siwe-oidc +### Address Format Issues +``` +❌ 742d35Cc6C4C1Ca5d428d9eE0e9B1E1234567890 +βœ… 0x742d35Cc6C4C1Ca5d428d9eE0e9B1E1234567890 +``` +**Fix**: Add the `0x` prefix to Ethereum addresses. -cd siwe-oidc +### Timestamp Format Issues +``` +❌ 2023-10-31 16:25:24 +βœ… 2023-10-31T16:25:24Z +``` +**Fix**: Use RFC 3339 format with `T` separator and timezone. -wrangler login +## Common Suggestions -wrangler whoami # account_id +### Weak Nonce Security +``` +❌ test123 +βœ… a1B2c3D4e5F6g7H8 +``` +**Fix**: Use cryptographically secure random nonces with mixed characters. -wrangler kv:namespace create SIWE_OIDC # kv_namespaces entry +### Missing Expiration Time +```diff + Nonce: a1B2c3D4e5F6g7H8 + Issued At: 2023-10-31T16:25:24Z ++ Expiration Time: 2023-10-31T16:35:24Z +``` +**Fix**: Add expiration time for security (5-15 minutes recommended). -Use the example Wrangler configuration file as a starting template: +## Security Best Practices -cp wrangler_example.toml wrangler.toml +When implementing SIWE authentication, always: -Populate the following fields for the Cloudflare Worker: +1. **Generate messages server-side** to prevent client manipulation +2. **Use strong nonces** with sufficient entropy (16+ characters) +3. **Implement expiration times** to limit message lifetime +4. **Validate domain binding** to prevent phishing attacks +5. **Check signatures server-side** - never trust client validation alone +6. **Store used nonces** to prevent replay attacks -account_id: the Cloudflare account ID; +## API Reference -account_id +The validator is built on a modular validation engine that you can use programmatically: -zone_id: (Optional) DNS zone ID; and +```javascript +import { ValidationEngine } from '@site/src/components/SiweValidator'; -zone_id +// Validate a message +const result = ValidationEngine.validate(message, { + profile: ValidationEngine.PROFILES.strict +}); -kv_namespaces: an array of KV namespaces +// Quick validation for real-time feedback +const quickCheck = ValidationEngine.quickValidate(message); -kv_namespaces +// Apply auto-fixes +const fixed = AutoFixer.fixMessage(parsedMessage, errors); +``` -Create and publish the worker: +--- -wrangler publish +# Message Validator Tool -The IdP currently only supports having the frontend under the same subdomain as the API. Here is the configuration for Cloudflare Pages: +Validate and lint your Sign in with Ethereum messages for EIP-4361 compliance, security best practices, and proper formatting. Consult the [message troubleshooting guide](./validator-guide) for usage and troubleshooting tips. -Build command: cd js/ui && npm install && npm run build; +import SiweValidator from '@site/src/components/SiweValidator/SiweValidator'; -Build command + -cd js/ui && npm install && npm run build +--- -Build output directory: /static; and +# Integrations -Build output directory +Sign in with Ethereum (SIWE) can be integrated with various platforms and frameworks to provide seamless authentication experiences. This section covers popular integration options. -/static +## Available Integrations -Root directory: /. And you will need to add some rules to do the routing between the Page and the Worker. Here are the rules for the Worker (the Page being used as the fallback on the subdomain): +### Discussion Platforms -Root directory +- **[Discourse](/integrations/discourse)**: Add SIWE authentication to your Discourse community forums -/ +### Authentication Libraries -siweoidc.example.com/s* +- **[NextAuth.js](/integrations/nextauth.js)**: Integrate SIWE with NextAuth.js for Next.js applications +- **[Auth0](/integrations/auth0)**: Use SIWE with Auth0's enterprise authentication platform -siweoidc.example.com/u* +## Integration Benefits -siweoidc.example.com/r* +- **Decentralized Authentication**: Users authenticate with their Ethereum wallets +- **No Passwords**: Eliminates the need for traditional password-based authentication +- **ENS Support**: Automatic username resolution from ENS names +- **Cross-Platform**: Works across web, mobile, and desktop applications -siweoidc.example.com/a* +## Common Integration Patterns -siweoidc.example.com/t* +### Frontend Integration -siweoidc.example.com/j* +Most integrations follow a similar pattern: -siweoidc.example.com/.w* +1. Connect to user's wallet +2. Create SIWE message +3. Request signature from user +4. Send signed message to backend for verification +5. Establish authenticated session -Dependencies +### Backend Verification -Redis, or a Redis compatible database (e.g. MemoryDB in AWS), is required. +Backend integrations typically: -Starting the IdP +1. Receive signed SIWE message +2. Verify signature cryptographically +3. Validate message parameters (domain, nonce, expiration) +4. Create user session or JWT token -The Docker image is available at ghcr.io/spruceid/siwe_oidc:0.1.0. Here is an example usage: +## Getting Started -ghcr.io/spruceid/siwe_oidc:0.1.0 +Choose the integration that best fits your technology stack: -docker run -p 8000:8000 -e SIWEOIDC_ADDRESS="0.0.0.0" -e SIWEOIDC_REDIS_URL="redis://redis" ghcr.io/spruceid/siwe_oidc:latest +- For Next.js applications, start with [NextAuth.js](/integrations/nextauth.js) +- For community platforms, see [Discourse](/integrations/discourse) +- For enterprise applications, explore [Auth0](/integrations/auth0) -It can be configured either with the siwe-oidc.toml configuration file, or through environment variables: +## Custom Integrations -siwe-oidc.toml +If you don't see your platform listed, you can build custom integrations using the SIWE libraries: -SIWEOIDC_ADDRESS is the IP address to bind to. +- [TypeScript/JavaScript](/libraries/typescript) +- [Python](/libraries/python) +- [Ruby](/libraries/ruby) +- [Go](/libraries/go) +- [Rust](/libraries/rust) +- [Elixir](/libraries/elixir) -SIWEOIDC_ADDRESS +## Community Contributions -SIWEOIDC_REDIS_URL is the URL to the Redis instance. +The SIWE ecosystem welcomes community contributions. If you've built an integration for a platform not listed here, consider sharing it with the community. -SIWEOIDC_REDIS_URL +--- -SIWEOIDC_BASE_URL is the URL you want to advertise in the OIDC configuration (e.g. https://oidc.example.com). +import FullWidthLink from '@site/src/components/full-width-link' -SIWEOIDC_BASE_URL +# πŸ›‘οΈ NextAuth.js -https://oidc.example.com +## Overview -SIWEOIDC_RSA_PEM is the signing key, in PEM format. One will be generated if none is provided. +NextAuth.js is "a complete open source authentication solution" designed for Next.js and serverless applications. This integration allows authentication using Ethereum wallets via Sign in with Ethereum ([EIP-4361](https://eips.ethereum.org/EIPS/eip-4361)). -SIWEOIDC_RSA_PEM + -The current flow is very basic -- after the user is authenticated you will receive an Ethereum address as the subject (sub field). +## Requirements -sub +- [Node.js](https://nodejs.org/en/) +- [Yarn](https://classic.yarnpkg.com/en/docs/install#mac-stable) +- Ethereum account at [Metamask](https://metamask.io) -For the core OIDC information, it is available under /.well-known/openid-configuration. +## Setup Steps -/.well-known/openid-configuration +### 1. Clone Example Project -OIDC Conformance Suite: +```bash +git clone https://github.com/nextauthjs/next-auth-example +cd next-auth-example +``` -wrangler dev +### 2. Configure Environment -At the moment it's not possible to use it end-to-end with the frontend as they need to share the same host (i.e. port), unless using a local load-balancer. +Modify `.env.local.example`: -A Docker Compose is available to test the IdP locally with Keycloak. +``` +NEXTAUTH_URL=http://localhost:3000 +NEXTAUTH_SECRET=somereallysecretsecret +``` -You will first need to run: +Rename to `.env.local` -docker-compose up -d +### 3. Install Dependencies -And then edit your /etc/hosts to have siwe-oidc point to 127.0.0.1. This is so both your browser, and Keycloak, can access the IdP. +```bash +yarn add siwe@beta ethers wagmi +``` -/etc/hosts +### 4. Update App Configuration -siwe-oidc +In `pages/_app.tsx`, add WagmiProvider: -127.0.0.1 +```typescript +import { WagmiConfig, createClient, configureChains, chain } from 'wagmi' +import { publicProvider } from 'wagmi/providers/public' -In Keycloak, you will need to create a new IdP. You can use http://siwe-oidc:8000/.well-known/openid-configuration to fill the settings automatically. As for the client ID/secret, you can use sdf/sdf. +export const { chains, provider } = configureChains( + [chain.mainnet, chain.polygon, chain.optimism, chain.arbitrum], + [publicProvider()] +) -http://siwe-oidc:8000/.well-known/openid-configuration +const client = createClient({ + autoConnect: true, + provider, +}) -sdf +export default function App({ Component, pageProps }) { + return ( + + + + + + ) +} +``` -🟨 (25/29, and 10 skipped) (email scope skipped, profile scope partially supported, ACR, prompt=none and request URIs yet to be supported); +### 5. Configure NextAuth Provider -profile +Update `pages/api/auth/[...nextauth].ts`: -prompt=none +```typescript +import NextAuth from 'next-auth' +import CredentialsProvider from 'next-auth/providers/credentials' +import { SiweMessage } from 'siwe' + +export default NextAuth({ + providers: [ + CredentialsProvider({ + name: 'Ethereum', + credentials: { + message: { + label: 'Message', + type: 'text', + placeholder: '0x0', + }, + signature: { + label: 'Signature', + type: 'text', + placeholder: '0x0', + }, + }, + async authorize(credentials) { + try { + const siwe = new SiweMessage( + JSON.parse(credentials?.message || '{}') + ) + + const nextAuthUrl = new URL(process.env.NEXTAUTH_URL) + if (siwe.domain !== nextAuthUrl.host) { + return null + } + + await siwe.validate(credentials?.signature || '') + return { + id: siwe.address, + name: siwe.address, + email: `${siwe.address}@ethereum.local`, + } + } catch (e) { + return null + } + }, + }), + ], + session: { strategy: 'jwt' }, + debug: process.env.NODE_ENV === 'development', + secret: process.env.NEXTAUTH_SECRET, + callbacks: { + async session({ session, token }) { + session.address = token.sub + session.user.name = token.sub + return session + }, + }, +}) +``` -🟩 ; +## Frontend Integration -🟧 . +Create a sign-in component that uses SIWE with NextAuth.js: -You can now use . +```typescript +import { useAccount, useConnect, useSignMessage, useDisconnect } from 'wagmi' +import { SiweMessage } from 'siwe' +import { signIn, useSession } from 'next-auth/react' + +export function SiweLogin() { + const { signMessageAsync } = useSignMessage() + const { address, isConnected } = useAccount() + const { connect, connectors } = useConnect() + const { disconnect } = useDisconnect() + const { data: session } = useSession() + + const handleLogin = async () => { + try { + const callbackUrl = '/protected' + const message = new SiweMessage({ + domain: window.location.host, + address: address, + statement: 'Sign in with Ethereum to the app.', + uri: window.location.origin, + version: '1', + chainId: 1, + nonce: await getCsrfToken(), + }) + + const signature = await signMessageAsync({ + message: message.prepareMessage(), + }) + + signIn('credentials', { + message: JSON.stringify(message), + redirect: false, + signature, + callbackUrl, + }) + } catch (error) { + console.log(error) + } + } -wrangler + if (isConnected) { + return ( +
+

Connected to {address}

+ + +
+ ) + } -# Hosted OIDC Provider + return ( +
+ {connectors.map(connector => ( + + ))} +
+ ) +} +``` -ServersOIDC ProviderHosted OIDC ProviderUsing the hosted SIWE OIDC ProviderPreviousDeployment GuideNextSIWE OverviewLast updated 1 year ago +## Key Features -Using the hosted SIWE OIDC Provider +- Seamless integration with NextAuth.js authentication flow +- Support for multiple wallet connectors via Wagmi +- Session management through NextAuth.js +- Customizable authentication callbacks +- TypeScript support -We deployed an OpenID Connect Provider (OP) with SIWE support hosted under . This deployment is supported by the ENS DAO, under in order to have a DAO-governed OpenID Connect Provider. +--- -Developers will be able to use a standard OIDC client to connect to the hosted OP. Please see our for more information about supported OIDC features. +# πŸ”° Auth0 -To use the hosted OP, developers are typically interested in the following steps: +## Overview -Retrieving the OP configuration. +This integration provides authentication and authorization for web2 applications across various sectors including retail, publishing, and B2B SaaS. -Registering the OIDC client with the OP. +import FullWidthLink from '@site/src/components/full-width-link' -Using the OP configuration to configure the OIDC client. + -The OP supports the OpenID Connect Provider Configuration specification as per . To fetch the OP configuration which is required for configuring OIDC clients, developers can make a GET HTTPS request to the following endpoint as follows: +## Key Components -curl https://oidc.signinwithethereum.org/.well-known/openid-configuration +- **Identity Provider**: Open-source OpenID Connect Identity Provider +- **Implementation**: Hosted at oidc.login.xyz +- **Language**: Rust-based implementation -This will result in the latest OP configuration object that provides information about supported OIDC flows, endpoints, public keys, signing algorithm, client authentication types, etc. as follows: +## Workflow - "issuer":"https://oidc.signinwithethereum.org/", +The integration follows a comprehensive authentication workflow that demonstrates the complete authentication process from wallet connection to application access. - "authorization_endpoint":"https://oidc.signinwithethereum.org/authorize", +### Authentication Flow - "token_endpoint":"https://oidc.signinwithethereum.org/token", +1. User clicks login button +2. Redirected to Auth0 SIWE interface +3. Wallet authentication process +4. Redirected back to application +5. Optional ENS name resolution - "userinfo_endpoint":"https://oidc.signinwithethereum.org/userinfo", +## Example Application - "jwks_uri":"https://oidc.signinwithethereum.org/jwk", +A demo application showcases the authentication flow: - "registration_endpoint":"https://oidc.signinwithethereum.org/register", +- Features a mock NFT gallery website +- Enables Sign In with Ethereum +- Resolves NFT holdings via OpenSea API after authentication +- Available at: [auth0-demo.login.xyz](https://auth0-demo.login.xyz/#/) - "scopes_supported":[ +## Technical Details - "openid", +- Supports Ethereum-based authentication +- Integrates with existing Auth0 authentication systems +- Provides flexible authentication options for web applications +- Uses OpenID Connect standard for interoperability - "profile" +## Implementation Benefits - ], +- **Seamless Integration**: Works with existing Auth0 infrastructure +- **Enterprise Ready**: Built for production web2 applications +- **Multi-Sector Support**: Suitable for retail, publishing, and B2B SaaS +- **Standards Compliance**: Uses OpenID Connect for standardization - "response_types_supported":[ +## Getting Started - "code", +1. **Set up Auth0 Account**: Configure your Auth0 tenant +2. **Enable SIWE Provider**: Add SIWE as an identity provider +3. **Configure Application**: Update your application settings +4. **Test Integration**: Use the demo application as reference - "id_token", +## Configuration - "token id_token" +The integration requires proper configuration of: - "subject_types_supported":[ +- Auth0 tenant settings +- Application callback URLs +- SIWE provider settings +- Session management - "pairwise" +## Resources - "id_token_signing_alg_values_supported":[ +- [Demo Application](https://auth0-demo.login.xyz/#/) +- [OIDC Provider Documentation](/oidc-provider) +- [Auth0 Documentation (official Auth0 docs)](https://auth0.com/docs) - "RS256" +## Support - "userinfo_signing_alg_values_supported":[ +This integration is part of the broader SIWE ecosystem and leverages Auth0's enterprise-grade authentication infrastructure while providing decentralized identity capabilities. - "token_endpoint_auth_methods_supported":[ +--- - "client_secret_basic", +import FullWidthLink from '@site/src/components/full-width-link' - "client_secret_post", +# πŸ’¬ Discourse - "private_key_jwt" +## Overview - "claims_supported":[ +**Discourse** is an open-source discussion platform used for crypto governance and project discussions. This guide explains how to add Sign in with Ethereum (SIWE) authentication to your Discourse instance. - "sub", + - "aud", +## Requirements - "exp", +- A self-hosted Discourse server +- Discourse's official distribution - "iat", +## Key Notes - "iss", +- Users must enter an email after first authentication +- If the user owns an ENS address, it will be the default username +- After email association, users can sign in using SIWE - "preferred_username", +## Installation Steps - "picture" +### 1. Enabling the Plugin - "op_policy_uri":"https://oidc.signinwithethereum.org/legal/privacy-policy.pdf", +Access your container's `app.yml` file: - "op_tos_uri":"https://oidc.signinwithethereum.org/legal/terms-of-use.pdf" +```bash +cd /var/discourse +nano containers/app.yml +``` -To use the hosted OIDC server it is required to register the application as an OIDC client using the OIDC client registration of oidc.signinwithethereum.org. Currently, no user interface for OIDC client registration is supported. For that reason, developers will need to use the REST API. +Add the plugin repository URL: -To register a new OIDC client, the following request has to be adapted: +```yaml +hooks: + before_code: + - exec: + cmd: + - gem install rubyzip + after_code: + - exec: + cd: $home/plugins + cmd: + - sudo -E -u discourse git clone https://github.com/discourse/docker_manager.git + - sudo -E -u discourse git clone https://github.com/signinwithethereum/discourse-siwe-auth.git +``` -curl -X POST https://oidc.signinwithethereum.org/register \ +Rebuild the container: - -H 'Content-Type: application/json' \ +```bash +cd /var/discourse +./launcher rebuild app +``` - -d '{"redirect_uris": ["https:///cb"]}' +### 2. Create a Project ID -The OIDC server needs to know whether the user is allowed to be redirected to the URI in the OIDC request after authentication for the specific OIDC client. This must be configured through the redirect_uris parameter. + -redirect_uris +
-The response will be a OIDC client metadata object that contains the client_id and client_secret that have to be used to retrieve the OIDC tokens from the token endpoint. Developers have to make sure that those parameters have to be kept secret. +- Configure the project ID in the plugin settings -client_id +### 3. Configure Message Statement -client_secret +By default, the statement is "Sign-in to Discourse via Ethereum". You can customize this in the plugin settings. -The following is an example response: +## Plugin Management - "client_id": "9b49de48-d198-47e7-afff-7ee26cbcbc95", +- To disable: Remove the plugin or uncheck `discourse siwe enabled` in Admin Settings - "client_secret": "er...", +## Additional Configuration - "registration_access_token": "2a...", +Access plugin settings at: +`Admin Settings -> Plugins -> discourse-siwe -> discourse siwe enabled` - "registration_client_uri": "https://oidc.signinwithethereum.org/client/9b49de48-d198-47e7-afff-7ee26cbcbc95", +## Compatibility - "redirect_uris": ["https:///cb"] +- Compatible with Discourse's official distribution +- Ongoing discussion about compatibility with other builds -A client can then be updated or deleted using the registration_client_uri with the registration_access_token as a Bearer token. +--- -registration_client_uri +# πŸ”’ Security Considerations -registration_access_token +## Overview -A variety of are available. In particular, we make use of the following: +When using Sign In with Ethereum (SIWE), implementers should aim to mitigate security issues on both the client and server. This is a growing collection of best practices for implementers, but no security checklist can ever be truly complete. -- client_name; +## Message Generation and Validation -- logo_uri; and +### Key Validation Principles -- client_uri. +1. **Backend Validation** -client_name + - Process SIWE messages according to [EIP-4361](https://eips.ethereum.org/EIPS/eip-4361) specifications + - Create the entire SIWE message on the backend + - Verify that the signed message is identical to the generated message with a valid signature -logo_uri +2. **Flexible Message Generation Approaches** + - Some implementers may choose alternative methods: + - Frontend can request specific field values from the server + - Agree on a value generation method + - Backend asserts received signed message matches expected verification parameters -client_uri. +### Critical Field Considerations -# SIWE Overview +#### Nonce -General InformationSIWE OverviewSign-In with Ethereum - For Web2 and Web3PreviousHosted OIDC ProviderNextEIP-4361Last updated 3 years ago +**Purpose**: Prevent replay attacks -Sign-In with Ethereum - For Web2 and Web3 +**Recommendations**: -Today’s login experiences rely on accounts controlled by centralized identity providers, for-profit behemoths like Google, Facebook, and Apple. Identity providers often have sole discretion over the existence and use of users’ digital identities across the web, fundamentally at odds with the best interest of users. +- Select nonce with sufficient entropy +- Server should assert nonce matches expected value +- Potential strategies: + - Derive nonce from recent block hash + - Use system time + - Reduce server interaction -The Ethereum Foundation and Ethereum Name Service (ENS) put forward a for Sign-in with Ethereum in 2021, which would enable users to use their Ethereum accounts to access web services instead of accounts owned by large corporations. +**Best Practices**: -The Ethereum ecosystem already has tens of millions of monthly active wallet users signing with their cryptographic keys for financial transactions, community governance, and more. +- Use cryptographically secure random generation +- Ensure nonces are single-use +- Implement proper nonce cleanup/expiration +- Store nonces securely on the server -The security of these wallets has been proven across billions of dollars of digital assets at stake--not theoretical security, but real tests in production. These secure wallets can also be used to sign in to Web2 services. +#### Domain -Sign-In with Ethereum describes how Ethereum accounts authenticate with off-chain services by signing a standard message format parameterized by scope, session details, and security mechanisms (e.g., a nonce). +**Purpose**: Prevent phishing attacks -Already, many services support workflows to authenticate Ethereum accounts using message signing, such as establishing a cookie-based web session which can manage privileged metadata about the authenticating address. +**Wallet Capabilities**: -For Web2, this is an opportunity to give users control over their identifiers and slowly introduce their dedicated user bases to Web3. By providing a strict specification that can be followed along with any necessary tooling to ease any integration concerns, Sign-In with Ethereum has a chance at truly transforming the way in which individuals interact with apps and services. +- Can check/generate correct domain bindings +- Verify website serving message matches domain +- Example: Ensure "example.org" securely serves its SIWE message -For Web3, this is an opportunity to standardize the sign-in workflow and improve interoperability across existing services, while also providing wallet vendors a reliable method to identify signing requests as Sign-In with Ethereum requests for improved UX. +**Best Practices**: -# Review of Related EIPs +- Always validate domain matches the serving origin +- Use exact domain matching (no wildcards) +- Implement proper HTTPS/TLS validation +- Consider subdomain implications -General InformationSIWE OverviewReview of Related EIPsA review of existing EIPs that helped shape EIP-4361, Sign-In with EthereumPreviousEIP-4361NextSIWE Code of ConductLast updated 3 years ago +#### Time-based Fields -A review of existing EIPs that helped shape EIP-4361, Sign-In with Ethereum +**issuedAt**: -Ethereum Improvement Proposals, or EIPs, are "" that include specifications for developers, and start typically with drafts that encourage a feedback cycle for the greater community. Part of the research in creating EIP-4361 necessitated an investigation into previous EIPs that sought to standardize the way decentralized identity is managed using Ethereum, as well as different ways of signing data. +- Should reflect the actual time of message creation +- Validate against server time with reasonable tolerance +- Use RFC 3339 (ISO 8601) format consistently -EIP-191 is a specification about how to handle signed data in Ethereum contracts. It produces human-readable messages and is simple to implement by prefixing a custom message with an invariable prefix prior to presenting it to wallet users for interactive signing. +**expirationTime**: -Originally, EIP-191 was a response to prevent pre-signed transactions originating from multisig wallets from being reused by multisig wallets with the same members. +- Set reasonable expiration windows (5-15 minutes) +- Always validate expiration on the server +- Consider clock skew between client and server -It consists of the following format for signed_data: +**notBefore**: -signed_data: +- Use when implementing scheduled authentication +- Validate current time is after notBefore time -0x19 <1 byte version> +#### Chain ID -In practice, it is prefixed with: "\x19Ethereum Signed Message:\n" + len(message). +**Purpose**: Prevent cross-chain replay attacks -"\x19Ethereum Signed Message:\n" + len(message) +**Recommendations**: -Additionally, signed_data could never be an Ethereum transaction, because it cannot be one RLP-structure, but a 1-byte RLP payload followed by something else. +- Always specify the correct chain ID +- Validate chain ID matches expected network +- Consider multi-chain implications for your application -signed_data +## Server-Side Security -EIP-712 is a standard for the hashing and signing of typed structured data as opposed to just bytestrings. At the core of EIP-712 is the need to sign more complex messages in order to have safer and deeper interactions with decentralized applications. +### Message Verification -Another goal for EIP-712 was to improve the usability of off-chain message signing for use on-chain in order to save gas and reduce the number of on-chain transactions. +1. **Signature Validation** -EIP-725 is an identity standard proposed in order to describe proxy smart contracts that can be controlled by multiple keys and other contracts. An associated EIP (735) enables the addition and removal of claims to an ERC-725 smart contract. + - Use proper cryptographic libraries for signature verification + - Implement EIP-191 personal message signing validation + - Support EIP-1271 for contract-based signatures when needed -EIP-725 was an early attempt at enabling self-sovereign identity on Ethereum, which involved the deployment of a smart contract that enabled the association of multiple keys with an identity, included execute and approve methods that allowed it to be used as a proxy, and attempted to forge the building blocks for access control lists through those contracts. +2. **Parameter Validation** + - Validate all message parameters against expected values + - Implement strict parsing of message format + - Reject malformed or unexpected messages -execute +### Session Management -approve +1. **Session Security** -EIP-735 is a standard for a claim holder interface to allow dapps and smart contracts to check claims about a claim holder (EIP-725). Claims must first be requested and issued by an issuer that signs a message containing the identity’s address, the claim topic, and additional data. The data is then stored on-chain in the identity owner’s smart contract. + - Implement secure session storage + - Use HTTPOnly and Secure cookie flags + - Implement proper session expiration + - Consider using JWTs with proper validation -EIP-780 defines an Ethereum Claims Registry to support decentralized identity on Ethereum and provides a central point of reference for on-chain claims. The Ethereum Claims Registry is a smart contract that can be commonly used by any account and provides an interface for adding, receiving, and removing claims. In this scenario, claims are issued from an issuer to a subject with a key. +2. **Rate Limiting** + - Implement rate limiting for authentication endpoints + - Prevent brute force attacks + - Monitor for suspicious authentication patterns -Additionally, off-chain claims can be made when an issuer signs data, encodes it into a JWT, and the JWT can later be verified. These off-chain claims can eventually be anchored on-chain via a form of β€˜badges’ system which differs from using traditional NFTs because they are non-transferable. +### Database Security -EIP-1056 is a specification for an identity system for Ethereum compliant with the decentralized identifiers standard (DID). It is meant to be paired with EIP-780, which defines an Ethereum Claims Registry to be used for the issuance and verification of claims. +1. **Data Storage** + - Store minimal necessary user data + - Hash or encrypt sensitive information + - Implement proper database access controls + - Regular security audits of stored data -EIP-1056 defines all existing Ethereum accounts as valid identities based solely on their existence as a public / private keypair. All identities can assign delegates that are valid for a specified amount of time and can be revocable or not. Additionally, delegates can be used on or off-chain to sign JWTs, Ethereum transactions, and arbitrary data. +## Client-Side Security -This EIP was a response to EIP-725, due to EIP-725 requiring the creation of a smart contract and not being free for the end-user. The specification additionally accounts for key rotation without changing the primary identifier of the identity. +### Wallet Integration -EIP-1102 proposes a more consentful and privacy-preserving way to expose metadata from an Ethereum user's wallet by prompting the user to accept the egressing data prior to its release. This strategy has already been implemented by MetaMask for user wallet connections. +1. **Wallet Connection** -EIP-1115 defines an authentication mechanism for dapps to authenticate users. In the specification, an HTTP server is set up by a user (called a DAuth server) and requires a password and public / private keypair. The user then registers a username on the smart contract that the specification defines by including the public key of the DAuth server and an address. + - Only connect to trusted wallet providers + - Validate wallet signatures properly + - Handle wallet disconnection gracefully + - Implement proper error handling -Logging into a dapp includes the user providing a random string (β€œCode”) to identify the current authentication session, a HashCode, and the username from the smart contract. The dapp will fetch both the public key and server address from the smart contract, and generate a secret string. After that, it will pass the β€œCode,” HashCode, username, and secret string to the DAuth server’s verification endpoint. +2. **Message Presentation** + - Display message contents clearly to users + - Ensure users understand what they're signing + - Implement proper message formatting + - Avoid misleading or confusing message content -The DAuth server will fetch the hash of the password and private key from its database related to the username in the request. After validating the HashCode, and attempting to decrypt the cipher, the decrypted value will then be sent to the dapp. +### Frontend Security -EIP-1271 is a specification that demonstrates a way for contracts to verify if a provided signature is valid when an account in question is a smart contract. This could be in the case of a user using a smart contract-based wallet or a user being part of a multisig wallet. +1. **HTTPS Requirements** -Smart contracts cannot directly sign messages, so EIP-1271 serves as a guide to implement isValidSignature(hash, signature) on the signing contract that can be called to validate a signature. With the rise of smart contract wallets and DAOs controlled by multi-sigs, these parties require the means to use signed messages to demonstrate the right to move assets, vote, or for other purposes. + - Always serve SIWE applications over HTTPS + - Implement proper TLS certificate validation + - Use secure headers (HSTS, CSP, etc.) -isValidSignature(hash, signature) +2. **Cross-Origin Considerations** + - Implement proper CORS policies + - Validate origins for authentication requests + - Prevent cross-site request forgery (CSRF) -EIP-1484 provides a different attempt at creating a digital identity standard on Ethereum by proposing an identity management and aggregation framework. Entities in this specification can claim an identity via a singular identity registry smart contract. The goal of this additional specification was to keep it compliant with the EIP-725 and EIP-1056 specifications that already existed. +## Common Vulnerabilities -EIP-1484’s aim was to create a protocol layer between the Ethereum blockchain and individual applications that required some form of user identity (tackling identity management and interoperability challenges along the way). This implementation required a global ERC1848 implementation up and running on the network in order to handle and enforce the rules for a global namespace made up of β€œEINs” or Ethereum Identification Numbers. +### Replay Attacks -EIP-1812 defines a method for off-chain verifiable claims built on EIP-712, which can be verified on-chain by smart contracts, state channel implementations, or off-chain libraries. By using ERC-735 and ERC-780 individuals can make claims that live on-chain, but sensitive information is best kept off-chain for GDPR and PII protection purposes. +**Risk**: Reusing signed messages for unauthorized access -Additionally, this EIP also recognizes EIP-1056 to provide a method for addresses to assign delete signers so an account can perform an action on behalf of a smart contract. Based on this specification, EIP-712 based state channels can include embeddable claims which are useful for exchanging private claims between parties for regulatory reasons and avoiding posting them on-chain. +**Mitigation**: -EIP-2525 presented a new method of login to the Ethereum blockchain using metadata stored in ENS. EIP-2525’s workflow would include an ENS domain retrieval from the user, domain resolution, interpretation of a text entry, an evaluation of the content in the text entry, and lastly, the corresponding object would then be returned to the dapp. +- Implement proper nonce validation +- Use time-based expiration +- Track used signatures server-side -At its core, EIP-2525 seeks to use ENS metadata to standardize across login mechanisms. +### Phishing Attacks -EIP-2844 makes JOSE-conformant DID resolution and verificationMethod requests part of ETH RPC (EIP-1474) and standardizes how Ethereum wallets can be queried for DID documents. +**Risk**: Users signing messages on malicious domains -EIP-3361 introduces a standard for JSON-RPC calls across wallets and provides an upgrade path for all of today’s signing methods that are currently splintered. It mentions EIP-1474, eth_sign standardization with prefixed signatures, as where signing became fragmented due to some applications using it and some choosing to keep the older eth_sign behavior pre-EIP-1474. +**Mitigation**: -eth_sign +- Validate domain field matches serving origin +- Educate users about domain verification +- Implement visual indicators for trusted domains -It seeks to depreciate personal_sign in order to make sure both old and new applications are using the same method. +### Session Hijacking -personal_sign +**Risk**: Unauthorized access to authenticated sessions -# SIWE Code of Conduct +**Mitigation**: -General InformationSIWE Code of ConductA code of conduct for public community activity around Sign-In with Ethereum.PreviousReview of Related EIPsLast updated 3 years ago +- Use secure session management +- Implement session regeneration +- Monitor for suspicious session activity +- Provide session termination controls -A code of conduct for public community activity around Sign-In with Ethereum. +### Message Tampering -The following outlines a code of conduct for the public community-facing interactions around the Sign-In with Ethereum project, including hosted by Spruce, and the . +**Risk**: Modification of SIWE message content -Our goal is to create the best environment for the Sign-In with Ethereum standard to flourish across Web2 and Web3. To achieve this, we must have the best technologists, strategists, and community leaders actively involved, building and maintaining an environment of trust which can only exist where each individual is able to enjoy respect and courtesy. +**Mitigation**: -To ensure a common understanding of β€œshowing respect and courtesy to each other,” we have adopted the following code of conduct. +- Generate messages server-side when possible +- Validate all message parameters +- Use cryptographic signatures for integrity -The following types of abusive behavior are unacceptable and constitute code of conduct violations: +## Deployment Security -Harassment - including offensive verbal comments related to gender, sexual orientation, disability, physical appearance, body size, race, or religion, and unwelcome sexual or romantic attention. +### Infrastructure Security -Threats - threatening someone verbally or in writing. +1. **Server Configuration** -Maliciousness - any direct abuse towards other members of the community β€”deliberately attempting to make others feel bad, name-calling, singling out others for derision or exclusion. For example, telling someone they don’t belong at community calls or in the public discussion. + - Keep servers and dependencies updated + - Implement proper firewall rules + - Use secure authentication for server access + - Regular security patches and updates -If abusive behavior is witnessed or reported either on the community calls or in the public Discord channel, it will first be evaluated by a member of the Spruce team. If it is deemed inappropriate and in violation of the code of conduct, it will result in a permanent suspension from both the community calls and Discord channel. +2. **Monitoring and Logging** + - Log authentication attempts and failures + - Monitor for unusual patterns + - Implement alerting for security events + - Regular security audit reviews -All individuals reporting violations of the code of conduct made by other community members will remain anonymous. +### Third-Party Dependencies -The Sign-In with Ethereum code of conduct is available under the terms of the . +1. **Library Security** -This code of conduct draws from and was heavily inspired by the . We hope to create a welcoming space for anyone wishing to participate in helping shape Sign-In with Ethereum. + - Keep SIWE libraries updated + - Audit third-party dependencies + - Monitor for security vulnerabilities + - Use dependency scanning tools +2. **Infrastructure Dependencies** + - Secure database connections + - Use trusted Ethereum node providers + - Implement proper API key management + - Regular infrastructure security reviews --- # ERC-4361: Sign-In with Ethereum diff --git a/public/llms/complete/llms.txt b/public/llms/complete/llms.txt index cf74153..ba3fe36 100644 --- a/public/llms/complete/llms.txt +++ b/public/llms/complete/llms.txt @@ -1,6 +1,6 @@ # Aggregated Ethereum Identity Context -> Updated at: 22:35 05/28/25 +> Updated at: 21:29 08/27/25 # docs.efp.app llms.txt @@ -507,40 +507,34 @@ > Offering resources and guidance for integrating Sign-In with Ethereum, enhancing user control over digital identities in web applications, while promoting best practices and supporting community involvement within the Ethereum ecosystem. -- [Community Highlights](https://docs.login.xyz/additional-support/community-highlights): To showcase libraries, guides, and community projects related to Sign-In with Ethereum. -- [Sign-In with Ethereum](https://docs.login.xyz/integrations/auth0): Guide to integrating Sign-In with Ethereum using Auth0 for authentication in web applications. -- [Elixir Sign-In Guide](https://docs.login.xyz/libraries/elixir): Guide for implementing Sign-In with Ethereum using Elixir. -- [Python Sign-In Documentation](https://docs.login.xyz/libraries/python): Provides documentation for using the Sign-In with Ethereum library in Python. -- [Sign-In with Ethereum Overview](https://docs.login.xyz/general-information/siwe-overview): Explains Sign-In with Ethereum and its benefits for users and developers in both Web2 and Web3 contexts. -- [Creating SIWE Messages](https://docs.login.xyz/sign-in-with-ethereum/quickstart-guide/creating-siwe-messages): Guide to create SIWE messages for Ethereum sign-in using JavaScript. -- [Sign-In with Ethereum for Rails](https://docs.login.xyz/libraries/ruby/rails): Guide for integrating Sign-In with Ethereum in Rails applications. -- [ENS Profile Resolution Guide](https://docs.login.xyz/sign-in-with-ethereum/quickstart-guide/resolve-ens-profiles): Guide to resolving ENS profiles in a Sign-In with Ethereum implementation. -- [Ethereum Sign-In Support](https://docs.login.xyz/additional-support): Provide support resources for integrating sign-in with Ethereum across various programming languages and tools. -- [Sign-In with Ethereum Docs](https://docs.login.xyz/libraries): To provide comprehensive documentation and resources for integrating Sign-In with Ethereum across various programming languages and platforms. -- [Sign-In with Ethereum Guide](https://docs.login.xyz/): Guide for implementing Sign-In with Ethereum authentication. -- [Discourse Ethereum Integration Guide](https://docs.login.xyz/integrations/discourse): Guide for integrating Sign-In with Ethereum into Discourse platforms. -- [OIDC Provider Deployment Guide](https://docs.login.xyz/servers/oidc-provider/deployment-guide): Guide for deploying an OIDC provider using Ethereum-based authentication. -- [TypeScript Migration Guide](https://docs.login.xyz/libraries/typescript/migrating-to-v2): Guidelines for migrating applications to TypeScript v2.0 related to Sign-In with Ethereum. -- [Sign-In with Ethereum Go](https://docs.login.xyz/libraries/go): Provides documentation for implementing Sign-In with Ethereum using the Go programming language. -- [Implement Sessions Guide](https://docs.login.xyz/sign-in-with-ethereum/quickstart-guide/implement-sessions): Guide on implementing sessions for Sign-In with Ethereum in applications. -- [EIP-4361 Overview](https://docs.login.xyz/general-information/siwe-overview/eip-4361): Explain the EIP-4361 specification for Sign-In with Ethereum, emphasizing decentralized identity and user-controlled authentication. -- [Ruby SIWE Implementation](https://docs.login.xyz/libraries/ruby): Guidance on implementing Sign-In with Ethereum in Ruby applications. -- [Sign-In with Ethereum Docs](https://docs.login.xyz/general-information): To provide comprehensive documentation for implementing Sign-In with Ethereum across various programming languages and integrations. -- [Ethereum Improvement Proposals](https://docs.login.xyz/general-information/siwe-overview/review-of-related-eips): Overviews of Ethereum Improvement Proposals related to decentralized identity and SIWE standards. -- [Sign-In with Ethereum Guides](https://docs.login.xyz/libraries/typescript): Provide resources and guides for implementing Sign-In with Ethereum using TypeScript and other programming languages. -- [Frontend Implementation Guide](https://docs.login.xyz/sign-in-with-ethereum/quickstart-guide/implement-the-frontend): Guide for implementing a frontend to enable sign-in with Ethereum. -- [Ethereum Authentication Guide](https://docs.login.xyz/integrations/nextauth.js): To guide users in implementing Ethereum authentication using NextAuth.js. -- [Connect Frontend Guide](https://docs.login.xyz/sign-in-with-ethereum/quickstart-guide/connect-the-frontend): Guide to connect frontend for Sign-In with Ethereum implementation. -- [Sign-In with Ethereum Guide](https://docs.login.xyz/servers): Documentation for implementing Sign-In with Ethereum and related technologies. -- [Sign-In with Ethereum Docs](https://docs.login.xyz/integrations): To provide documentation and resources for integrating Sign-In with Ethereum and related technologies. -- [Sign-In with Ethereum](https://docs.login.xyz/servers/oidc-provider): Guide for implementing Sign-In with Ethereum using OpenID Connect. -- [Backend Implementation Guide](https://docs.login.xyz/sign-in-with-ethereum/quickstart-guide/implement-the-backend): Guide for implementing backend for Sign-In with Ethereum authentication. -- [TypeScript Quickstart Guide](https://docs.login.xyz/libraries/typescript/typescript-quickstart): Guide for signing in with Ethereum using TypeScript, with setup instructions and requirements. -- [ENS Profile Resolution Guide](https://docs.login.xyz/additional-support/ens-profile-resolution): Guide for resolving ENS profiles using Ethereum addresses in authentication workflows. -- [Security Considerations for SIWE](https://docs.login.xyz/additional-support/security-considerations): Provide security considerations for implementing Sign-In with Ethereum (SIWE). -- [SIWE Code of Conduct](https://docs.login.xyz/general-information/siwe-code-of-conduct): Establish guidelines for respectful community interaction in the Sign-In with Ethereum project. -- [Hosted OIDC Provider Guide](https://docs.login.xyz/servers/oidc-provider/hosted-oidc-provider): Guide for developers on configuring and using a hosted OpenID Connect Provider with Ethereum integration. -- [Resolve NFT Holdings](https://docs.login.xyz/sign-in-with-ethereum/quickstart-guide/resolve-nft-holdings): Guide for resolving NFT holdings through Ethereum sign-in. -- [Quickstart Guide](https://docs.login.xyz/sign-in-with-ethereum/quickstart-guide): Guide for integrating Sign-In with Ethereum into applications. -- [Rust Sign-In Documentation](https://docs.login.xyz/libraries/rust): Provides documentation for the Rust implementation of Sign-In with Ethereum. +- [SIWE Security Guidelines](https://docs.siwe.xyz/security-considerations): Offer comprehensive security best practices for implementing Sign In with Ethereum (SIWE) authentication. +- [SIWE Auth0 Integration](https://docs.siwe.xyz/integrations/auth0): Guide integration of Sign In With Ethereum (SIWE) authentication with Auth0 for web2 applications. +- [TypeScript SIWE Library](https://docs.siwe.xyz/libraries/typescript): Provide TypeScript library documentation and integration examples for Sign in with Ethereum (EIP-4361) implementation. +- [SIWE Integration for Rails](https://docs.siwe.xyz/libraries/ruby/rails): Guide Rails developers on integrating Sign in with Ethereum authentication using multiple approaches and setup steps. +- [Retrieve Onchain Data](https://docs.siwe.xyz/quickstart/retrieve-onchain-data): Guide developers on accessing and using onchain Ethereum data like ENS, EFP, and assets after user authentication. +- [Sign in with Ethereum Quickstart](https://docs.siwe.xyz/quickstart): Guide to implement Sign in with Ethereum authentication including frontend, backend, and session management. +- [Go SIWE Library Guide](https://docs.siwe.xyz/libraries/go): Guide to installing and using the Go library for Sign in with Ethereum authentication. +- [Sign in with Ethereum Docs](https://docs.siwe.xyz): Guide developers to implement secure Ethereum account authentication via the Sign in with Ethereum (SIWE) standard. +- [SIWE Backend Verification](https://docs.siwe.xyz/quickstart/backend-verification): Guide to building a secure Express.js backend for verifying SIWE signatures and managing authentication. +- [OIDC Provider Guide](https://docs.siwe.xyz/oidc-provider): Explain how to use OpenID Connect for consolidated Sign in with Ethereum authentication. +- [EFP Data Integration Guide](https://docs.siwe.xyz/quickstart/resolve-efp-data): Guide developers to integrate and display Ethereum Follow Protocol (EFP) social graph data using API and React components. +- [SIWE Session Implementation](https://docs.siwe.xyz/quickstart/implement-sessions): Guide to implement Express.js sessions for backend security in Sign in with Ethereum (SIWE) projects. +- [SIWE Message Validator](https://docs.siwe.xyz/validator): Validate and lint Sign in with Ethereum messages for EIP-4361 compliance and security best practices. +- [Migrating SIWE to v2](https://docs.siwe.xyz/libraries/typescript/migrating-to-v2): Guide to upgrading TypeScript SIWE library from version 1.1.6 to 2.1.x with detailed changes and steps. +- [SIWE TypeScript Quickstart](https://docs.siwe.xyz/libraries/typescript/typescript-quickstart): Guide to quickly set up and run a TypeScript example for Sign in with Ethereum using a notepad app. +- [Onchain Holdings Guide](https://docs.siwe.xyz/quickstart/resolve-onchain-holdings): Guide for developers to retrieve and display users' onchain token and NFT holdings via blockchain APIs. +- [Connect Frontend Guide](https://docs.siwe.xyz/quickstart/connect-the-frontend): Guide to updating frontend for signing and sending Ethereum authentication messages to backend. +- [SIWE React Frontend Setup](https://docs.siwe.xyz/quickstart/frontend-setup): Guide to build a React frontend for connecting Ethereum wallets and using SIWE for authentication user signatures. +- [Elixir SIWE Library](https://docs.siwe.xyz/libraries/elixir): Provide Elixir library installation and usage instructions for Sign in with Ethereum integration. +- [Ruby SIWE Library](https://docs.siwe.xyz/libraries/ruby): Provide installation, usage, and error handling instructions for the Ruby Sign-In with Ethereum library. +- [SIWE Integration Guide](https://docs.siwe.xyz/integrations): Guide developers on integrating Sign in with Ethereum (SIWE) for decentralized authentication across platforms. +- [NextAuth.js Ethereum Integration](https://docs.siwe.xyz/integrations/nextauth.js): Guide to integrating Sign in with Ethereum using NextAuth.js in Next.js applications. +- [SIWE Validator Guide](https://docs.siwe.xyz/validator/validator-guide): Guide to validating, linting, and debugging Sign-In with Ethereum messages per EIP-4361 standards. +- [Rust SIWE Documentation](https://docs.siwe.xyz/libraries/rust): Provide documentation and resources for using the Rust implementation of Sign in with Ethereum. +- [Resolving ENS Profiles](https://docs.siwe.xyz/quickstart/resolve-ens-profiles): Guide to integrating ENS profile resolution and display into applications using JavaScript and React components. +- [SIWE Libraries Overview](https://docs.siwe.xyz/libraries): Detail official SIWE libraries across languages for EIP-4361 authentication integration with usage and migration guidance. +- [Ethereum Identity Kit Docs](https://docs.siwe.xyz/libraries/ethereum-idetntiy-kit): Provide documentation and resources for using the Ethereum Identity Kit library in dapp development. +- [Sign in with Ethereum Python](https://docs.siwe.xyz/libraries/python): Provide documentation and resources for the Python implementation of Sign in with Ethereum. +- [SIWE Integration for Discourse](https://docs.siwe.xyz/integrations/discourse): Guide to integrate Sign in with Ethereum authentication into a Discourse discussion platform. +- [SIWE Message Creation Guide](https://docs.siwe.xyz/quickstart/creating-messages): Guide developers to create and implement Sign in with Ethereum (SIWE) authentication messages securely using TypeScript. - [EIP-4361: Sign-In with Ethereum](https://eips.ethereum.org/EIPS/eip-4361): Off-chain authentication for Ethereum accounts to establish sessions. \ No newline at end of file