{"id":35686854,"url":"https://github.com/iamolegga/lana","last_synced_at":"2026-04-02T18:26:51.715Z","repository":{"id":324783217,"uuid":"1056024773","full_name":"iamolegga/lana","owner":"iamolegga","description":"Lightweight AutheNtication App","archived":false,"fork":false,"pushed_at":"2026-03-29T21:08:03.000Z","size":104,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-29T22:23:57.490Z","etag":null,"topics":["authentication","oauth2","sso","sso-authentication","sso-login","sso-server"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/iamolegga.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-13T08:31:27.000Z","updated_at":"2026-03-29T20:33:00.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/iamolegga/lana","commit_stats":null,"previous_names":["iamolegga/lana"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/iamolegga/lana","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iamolegga%2Flana","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iamolegga%2Flana/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iamolegga%2Flana/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iamolegga%2Flana/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/iamolegga","download_url":"https://codeload.github.com/iamolegga/lana/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iamolegga%2Flana/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31312862,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T12:59:32.332Z","status":"ssl_error","status_checked_at":"2026-04-02T12:54:48.875Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authentication","oauth2","sso","sso-authentication","sso-login","sso-server"],"created_at":"2026-01-05T22:01:51.932Z","updated_at":"2026-04-02T18:26:51.705Z","avatar_url":"https://github.com/iamolegga.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg alt=\"Lana logo\" src=\"lana.svg\"/\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003eLANA\u003c/h1\u003e\n\u003cp align=\"center\"\u003e\u003cb\u003eL\u003c/b\u003eightweight \u003cb\u003eA\u003c/b\u003euthe\u003cb\u003eN\u003c/b\u003etication \u003cb\u003eA\u003c/b\u003epp\u003c/p\u003e\n\u003cp align=\"center\"\u003eA single OAuth 2.0 SSO server to simplify authentication for multiple applications.\u003c/p\u003e\n\nLana provides OAuth 2.0 authentication through multiple providers (Google, Facebook, X, Apple, and more to come) and issues JWTs for authenticated users. The server supports multi-host configurations with host-specific JWT signing keys and provider settings, making it ideal for managing authentication across multiple applications from a single deployment.\n\n## Authentication Flow\n\n```mermaid\nsequenceDiagram\n    participant User\n    participant App as Client App\n    participant LANA as LANA Server\n    participant OAuth as OAuth Provider\n\n    User-\u003e\u003eApp: 1. Visit application\n    App--\u003e\u003eUser: 2. Redirect to LANA for auth with ?redirect=...\n    User-\u003e\u003eLANA: 3. Request LANA with ?redirect=...\n    LANA--\u003e\u003eUser: 4. Serves static files (html/css/js/etc) configured for request's hostname\n    Note over User,LANA: The webpage should include links to LANA's login API endpoint with configured OAuth providers.\u003cbr/\u003eAdd the `?redirect=...` search param to these links with JS (copy from example code).\n    User-\u003e\u003eLANA: 5. Request LANA with /oauth/login/{provider}?redirect=...\n    LANA--\u003e\u003eLANA: 6. Check and save redirect URL in signed cookie,\u003cbr/\u003epass state in next OAuth request to OAuth Provider\n    LANA--\u003e\u003eUser: 7. Redirect to OAuth provider\n    Note over User,LANA: OAuth callback will be LANA/oauth/callback/{provider}\n    User-\u003e\u003eOAuth: 8. Authenticate (login)\n    OAuth--\u003e\u003eUser: 9. Redirect to callback with auth code\n    User-\u003e\u003eLANA: 10. Request callback with auth code and state\n    LANA-\u003e\u003eLANA: 11. Validate state from cookie\n    LANA-\u003e\u003eOAuth: 12. Exchange code for tokens\n    OAuth--\u003e\u003eLANA: 13. Return tokens\n    LANA-\u003e\u003eLANA: 14. Extract user info from tokens\n    LANA--\u003e\u003eApp: 15. Redirect with own JWT (?token=\u003cjwt\u003e)\n    App-\u003e\u003eLANA: 16. Fetch JWKS (public keys)\n    LANA--\u003e\u003eApp: 17. Return JWKS\n    App-\u003e\u003eApp: 18. Verify JWT signature,\u003cbr/\u003eregister/login user,\u003cbr/\u003ecreate session, etc.\n    App--\u003e\u003eUser: 19. Authenticated session established\n```\n\n## Features\n\n- **Multi-Provider OAuth 2.0** - Built-in support for Google (with OIDC), Facebook, X (Twitter, with PKCE), and Apple OAuth, with a pluggable provider architecture for easy extension\n- **JWT Token Generation** - Issues signed JWTs with RSA-256 using host-specific private keys\n- **JWKS Endpoint** - Exposes public keys at `/.well-known/jwks.json` for downstream JWT verification\n- **Multi-Host Support** - Single server instance can handle multiple hosts with different configurations, JWT keys, and OAuth providers\n- **Rate Limiting** - Per-IP rate limiting with token bucket algorithm, proxy-aware with multi-header IP detection (CF-Connecting-IP, X-Real-IP, X-Forwarded-For)\n- **CSRF Protection** - Encrypted state cookies using AES-GCM prevent cross-site request forgery attacks\n- **Prometheus Metrics** - Built-in metrics for HTTP requests, authentication attempts, and request duration\n- **Wildcard Redirect URLs** - Support for wildcard patterns in allowed redirect URLs for flexible client configuration\n- **Environment Variable Substitution** - Configuration supports `$VAR_NAME` syntax for secrets and environment-specific values\n\n## Security\n\nLana is built with security as a top priority:\n\n- **RSA-256 JWT Signing** - Industry-standard asymmetric signing using PKCS#1 PEM format\n- **AES-GCM Cookie Encryption** - State cookies encrypted with AES-256-GCM for CSRF protection\n- **OIDC Support** - Full OpenID Connect implementation for Google and Apple OAuth with ID token verification\n- **PKCE Support** - Proof Key for Code Exchange (RFC 7636) for providers that require it (X/Twitter)\n- **Provider-Agnostic Identity** - Stable `sub` claim derived from `sha256(provider:id)`, with optional `email` and `name` claims when available from the provider\n- **Secure Cookie Flags** - HttpOnly, Secure, and SameSite flags prevent cookie theft and CSRF\n- **Rate Limiting** - Token bucket algorithm prevents brute force and DoS attacks\n- **Proxy-Aware IP Detection** - Supports X-Forwarded-For, CF-Connecting-IP, and X-Real-IP headers with priority-based detection\n- **Context-Aware Timeouts** - All OAuth operations have 10-second timeouts with configured HTTP client limits\n- **Minimal Attack Surface** - Docker image built from scratch with only essential binaries\n- **Non-Root Execution** - Docker container runs as user `65534:65534` (nobody)\n\n## Configuration\n\nThe server is configured via `config.yaml` with support for environment variable substitution using `$VAR_NAME` syntax.\n\nLana supports multiple hosts from a single deployment. Each host can have:\n- **Unique JWT signing keys** - Different RSA key pairs per domain\n- **Different OAuth providers** - Enable Google for one host, Facebook for another\n- **Separate login pages** - Custom branding per domain\n- **Individual JWT settings** - Different audiences, expiry times, and key IDs\n\n### Configuration Example\n\n**Minimal configuration** (using defaults):\n\n```yaml\n# Only required field at top level\ncookie:\n  secret: $COOKIE_SECRET  # Must be 32+ characters for AES-256\n\n# Host configuration (at least one required)\nhosts:\n  auth.example.com:\n    login_dir: ./web/example/\n    allowed_redirect_urls:\n      - \"https://app.example.com/*\"\n      - \"http://localhost:*/*\"  # For development\n    jwt:\n      private_key_file: \"./keys/example.pem\"\n      kid: \"example-key-2025-01\"\n      audience: \"https://app.example.com\"\n      expiry: \"2h\"\n    providers:\n      google:\n        client_id: $GOOGLE_CLIENT_ID\n        client_secret: $GOOGLE_CLIENT_SECRET\n```\n\n**Full configuration** (with all options):\n\n```yaml\n# Environment: development or production (default: production)\nenv: production\n\n# Server settings (default port: 8080)\nserver:\n  port: 8080\n\n# Cookie configuration (default name: oauth_state)\ncookie:\n  name: \"oauth_state\"\n  secret: $COOKIE_SECRET  # Must be 32+ characters for AES-256\n\n# Rate limiting (defaults: 60 req/min, 5m cleanup, index 0)\nratelimit:\n  requests_per_minute: 60\n  cleanup_interval: \"5m\"\n  x_forwarded_for_index: 0  # 0 = original client, -1 = closest proxy\n\n# Logging (defaults: info level, text format)\nlogging:\n  level: \"info\"  # debug, info, warn, error\n  format: \"json\"  # json, text\n\n# Metrics - Prometheus (optional, disabled by default)\nmetrics:\n  enable: true\n  go_metrics: false\n  path: \"/metrics\"\n\n# Multi-host configuration\nhosts:\n  auth.example.com:\n    login_dir: ./web/example/\n    allowed_redirect_urls:   # Wildcard patterns supported\n      - \"https://app.example.com/*\"\n      - \"https://admin.example.com/*\"\n      - \"http://localhost:*/*\"  # For development\n    jwt:\n      private_key_file: \"./keys/example.pem\"\n      kid: \"example-key-2025-01\"\n      audience: \"https://app.example.com\"\n      expiry: \"2h\"\n    providers:\n      google:\n        client_id: $EXAMPLE_GOOGLE_CLIENT_ID\n        client_secret: $EXAMPLE_GOOGLE_CLIENT_SECRET\n      facebook:\n        client_id: $EXAMPLE_FB_CLIENT_ID\n        client_secret: $EXAMPLE_FB_CLIENT_SECRET\n      x:\n        client_id: $EXAMPLE_X_CLIENT_ID\n        client_secret: $EXAMPLE_X_CLIENT_SECRET\n      apple:\n        services_id: $EXAMPLE_APPLE_SERVICES_ID\n        team_id: $EXAMPLE_APPLE_TEAM_ID\n        key_id: $EXAMPLE_APPLE_KEY_ID\n        private_key_file: \"./keys/apple-example.p8\"\n\n  auth.anotherapp.com:\n    login_dir: ./web/anotherapp/\n    allowed_redirect_urls:\n      - \"https://anotherapp.com/*\"\n      - \"https://*.anotherapp.com/*\"\n    jwt:\n      private_key_file: \"./keys/anotherapp.pem\"\n      kid: \"anotherapp-key-2025-01\"\n      audience: \"https://anotherapp.com\"\n      expiry: \"1h\"\n    providers:\n      facebook:\n        client_id: $ANOTHERAPP_FB_CLIENT_ID\n        client_secret: $ANOTHERAPP_FB_CLIENT_SECRET\n      x:\n        client_id: $ANOTHERAPP_X_CLIENT_ID\n        client_secret: $ANOTHERAPP_X_CLIENT_SECRET\n      apple:\n        services_id: $ANOTHERAPP_APPLE_SERVICES_ID\n        team_id: $ANOTHERAPP_APPLE_TEAM_ID\n        key_id: $ANOTHERAPP_APPLE_KEY_ID\n        private_key_file: \"./keys/apple-anotherapp.p8\"\n```\n\n### Configuration Options Reference\n\n| Field | Type | Required | Default | Description |\n|-------|------|----------|---------|-------------|\n| `env` | string | No | `\"production\"` | Environment mode: `development` or `production` |\n| `server.port` | int | No | `\"8080\"` | HTTP port to listen on |\n| `cookie.name` | string | No | `\"oauth_state\"` | Cookie name for OAuth state storage |\n| `cookie.secret` | string | Yes | - | 32+ character secret for AES-256 encryption |\n| `ratelimit.requests_per_minute` | int | No | `60` | Max requests per IP per minute |\n| `ratelimit.cleanup_interval` | duration | No | `\"5m\"` | How often to clean expired entries |\n| `ratelimit.x_forwarded_for_index` | int | No | `0` | Which IP to use from X-Forwarded-For (0-based, -1 for rightmost) |\n| `logging.level` | string | No | `\"info\"` | Log level: `debug`, `info`, `warn`, `error` |\n| `logging.format` | string | No | `\"text\"` | Log format: `json` or `text` |\n| `metrics.enable` | bool | No | `false` | Enable Prometheus metrics endpoint (entire `metrics` section is optional) |\n| `metrics.go_metrics` | bool | No | `false` | Include Go runtime metrics (memory, goroutines, GC) |\n| `metrics.path` | string | No | `\"/metrics\"` | Metrics endpoint path |\n| `hosts.\u003chostname\u003e.login_dir` | string | Yes | - | Path to login page directory |\n| `hosts.\u003chostname\u003e.allowed_redirect_urls` | []string | Yes | - | List of allowed redirect URLs (supports wildcards: `*`) |\n| `hosts.\u003chostname\u003e.jwt.private_key_file` | string | Yes | - | Path to RSA private key (PEM format) |\n| `hosts.\u003chostname\u003e.jwt.kid` | string | Yes | - | Key ID for JWT header |\n| `hosts.\u003chostname\u003e.jwt.audience` | string | Yes | - | JWT audience claim (aud) |\n| `hosts.\u003chostname\u003e.jwt.expiry` | duration | Yes | - | JWT expiration time (e.g., \"1h\", \"30m\") |\n| `hosts.\u003chostname\u003e.providers.\u003cname\u003e.client_id` | string | Non-Apple | - | OAuth provider client/app ID |\n| `hosts.\u003chostname\u003e.providers.\u003cname\u003e.client_secret` | string | Non-Apple | - | OAuth provider client/app secret |\n| `hosts.\u003chostname\u003e.providers.\u003cname\u003e.services_id` | string | Apple only | - | Apple Services ID (required for Apple provider) |\n| `hosts.\u003chostname\u003e.providers.\u003cname\u003e.team_id` | string | Apple only | - | Apple Team ID (required for Apple provider) |\n| `hosts.\u003chostname\u003e.providers.\u003cname\u003e.key_id` | string | Apple only | - | Apple Key ID (required for Apple provider) |\n| `hosts.\u003chostname\u003e.providers.\u003cname\u003e.private_key_file` | string | Apple only | - | Path to Apple .p8 private key (required for Apple provider) |\n\nVariables are substituted at server startup using `$VAR_NAME` syntax. If a variable is missing, the server will fail to start with a clear error message.\n\n## Client Integration\n\nTo integrate Lana with your application:\n\n1. **Redirect users to Lana** for authentication:\n   ```\n   https://auth.yourapp.com/oauth/login/google?redirect=https://yourapp.com/callback\n   ```\n\n2. **Handle the callback** with the JWT token:\n   ```\n   https://yourapp.com/callback?token=\u003cjwt\u003e\n   ```\n\n3. **Verify the JWT** using the public key from `/.well-known/jwks.json`\n\nFor a complete working example of a client application that integrates with Lana, see [example/README.md](example/README.md).\n\nThe example demonstrates:\n- JWT verification using JWKS\n- Secure session management\n- User profile display\n- Logout handling\n\n## License\n\nApache License 2.0\n\nSee [LICENSE](LICENSE) for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fiamolegga%2Flana","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fiamolegga%2Flana","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fiamolegga%2Flana/lists"}