{"id":36533338,"url":"https://github.com/xraph/frank-go-sdk","last_synced_at":"2026-01-12T03:03:58.343Z","repository":{"id":301799264,"uuid":"1010336240","full_name":"xraph/frank-go-sdk","owner":"xraph","description":"Go SDK for Frank","archived":false,"fork":false,"pushed_at":"2025-07-10T17:53:03.000Z","size":88,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-07-10T21:05:58.095Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/xraph.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}},"created_at":"2025-06-28T21:16:44.000Z","updated_at":"2025-07-10T17:10:40.000Z","dependencies_parsed_at":"2025-06-28T22:35:32.364Z","dependency_job_id":"0dd74c62-599b-4a17-b0dc-4637193a0b6d","html_url":"https://github.com/xraph/frank-go-sdk","commit_stats":null,"previous_names":["xraph/frank-go-sdk"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/xraph/frank-go-sdk","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xraph%2Ffrank-go-sdk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xraph%2Ffrank-go-sdk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xraph%2Ffrank-go-sdk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xraph%2Ffrank-go-sdk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/xraph","download_url":"https://codeload.github.com/xraph/frank-go-sdk/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xraph%2Ffrank-go-sdk/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28332861,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-12T00:36:25.062Z","status":"online","status_checked_at":"2026-01-12T02:00:08.677Z","response_time":98,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":[],"created_at":"2026-01-12T03:03:58.288Z","updated_at":"2026-01-12T03:03:58.337Z","avatar_url":"https://github.com/xraph.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Frank Auth Go SDK\nA comprehensive Go SDK for integrating with Frank Auth - a multi-tenant authentication SaaS platform. This SDK provides middleware for both standard HTTP handlers and the Huma framework, supporting JWT, API keys, session-based authentication, and comprehensive RBAC.\n\n## Features\n\n- **Multiple Authentication Methods**: JWT, API keys, session tokens, OAuth2\n- **Three-Tier User System**: Internal users, external users, and end users\n- **Multi-Tenant Architecture**: Organization-scoped permissions and isolation\n- **Comprehensive RBAC**: Role-based access control with context-aware permissions\n- **Framework Support**: Standard HTTP handlers and Huma framework integration\n- **Flexible Configuration**: Environment variables, manual config, or hybrid approaches\n- **Advanced Permission Checking**: Resource-scoped and context-aware permission validation\n- **Optional Authentication**: Support for both authenticated and anonymous users\n- **Custom Error Handling**: Configurable error responses and handling\n- **Production Ready**: Caching, logging, retries, and error handling\n\n## Installation\n\n```bash\ngo get github.com/your-org/frank-go-sdk\n```\n\n## Quick Start\n\n### 1. Environment Configuration\n\nSet up your environment variables:\n\n```bash\nexport FRANK_AUTH_BASE_URL=\"https://api.frankauth.com\"\nexport FRANK_AUTH_API_KEY=\"your-api-key-here\"\n```\n\n### 2. Basic HTTP Server\n\n```go\npackage main\n\nimport (\n    \"encoding/json\"\n    \"log\"\n    \"net/http\"\n\n    \"github.com/go-chi/chi/v5\"\n    \"github.com/frank-go-sdk/frank\"\n    \"github.com/frank-go-sdk/middleware\"\n)\n\nfunc main() {\n    // Initialize Frank Auth SDK\n    sdk, err := frank.NewFromEnv()\n    if err != nil {\n        log.Fatal(\"Failed to initialize Frank Auth SDK:\", err)\n    }\n\n    r := chi.NewRouter()\n\n    // Public routes\n    r.Get(\"/health\", func(w http.ResponseWriter, r *http.Request) {\n        json.NewEncoder(w).Encode(map[string]string{\"status\": \"ok\"})\n    })\n\n    // Protected routes\n    r.Route(\"/api\", func(r chi.Router) {\n        r.Use(sdk.Middleware())\n\n        r.Get(\"/profile\", func(w http.ResponseWriter, r *http.Request) {\n            user := middleware.GetUser(r)\n            json.NewEncoder(w).Encode(user)\n        })\n    })\n\n    log.Fatal(http.ListenAndServe(\":8080\", r))\n}\n```\n\n### 3. Huma Framework Integration\n\n```go\npackage main\n\nimport (\n    \"context\"\n    \"log\"\n    \"net/http\"\n\n    \"github.com/danielgtaylor/huma/v2\"\n    \"github.com/danielgtaylor/huma/v2/adapters/humachi\"\n    \"github.com/go-chi/chi/v5\"\n    \"github.com/frank-go-sdk/frank\"\n    frankHuma \"github.com/frank-go-sdk/huma\"\n)\n\nfunc main() {\n    sdk, err := frank.NewFromEnv()\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    router := chi.NewMux()\n    config := huma.DefaultConfig(\"My API\", \"1.0.0\")\n    api := huma.NewAPI(config, humachi.New(router))\n\n    // Protected group\n    protectedGroup := huma.NewGroup(api, \"/api\")\n    protectedGroup.UseMiddleware(sdk.HumaMiddleware(api))\n\n    huma.Register(protectedGroup, huma.Operation{\n        OperationID: \"getProfile\",\n        Method:      http.MethodGet,\n        Path:        \"/profile\",\n        Summary:     \"Get user profile\",\n    }, func(ctx context.Context, input *struct{}) (*UserOutput, error) {\n        humaCtx := ctx.Value(\"huma_context\").(huma.Context)\n        user := frankHuma.GetUser(humaCtx)\n        return \u0026UserOutput{Body: user}, nil\n    })\n\n    log.Fatal(http.ListenAndServe(\":8080\", router))\n}\n```\n\n## Configuration\n\n### Environment Variables\n\n| Variable | Description | Default |\n|----------|-------------|---------|\n| `FRANK_AUTH_BASE_URL` | Frank Auth API base URL | `https://api.frankauth.com` |\n| `FRANK_AUTH_API_KEY` | API key for server-to-server auth | - |\n| `FRANK_AUTH_TIMEOUT` | HTTP request timeout | `30s` |\n| `FRANK_AUTH_OPTIONAL` | Allow requests without auth | `false` |\n| `FRANK_AUTH_REQUIRED_ROLES` | Required roles (comma-separated) | - |\n| `FRANK_AUTH_REQUIRED_PERMISSIONS` | Required permissions (comma-separated) | - |\n| `FRANK_AUTH_REQUIRE_ORGANIZATION` | Require organization context | `false` |\n| `FRANK_AUTH_REQUIRED_USER_TYPE` | Required user type | - |\n| `FRANK_AUTH_SKIP_PATHS` | Paths to skip auth (comma-separated) | - |\n| `FRANK_AUTH_CACHE_ENABLED` | Enable response caching | `true` |\n| `FRANK_AUTH_CACHE_TTL` | Cache TTL | `5m` |\n| `FRANK_AUTH_LOG_LEVEL` | Log level | `info` |\n| `FRANK_AUTH_LOG_REQUESTS` | Log all requests | `false` |\n\n### Manual Configuration\n\n```go\ncfg := \u0026config.Config{\n    BaseURL:             \"https://api.frankauth.com\",\n    APIKey:              \"your-api-key\",\n    Optional:            false,\n    RequiredPermissions: []string{\"read:profile\"},\n    RequiredRoles:       []string{\"member\"},\n    RequireOrganization: true,\n    RequiredUserType:    \"external\",\n    SkipPaths:           []string{\"/health\", \"/metrics\"},\n    CacheEnabled:        true,\n    LogRequests:         true,\n}\n\nsdk, err := frank.New(cfg)\n```\n\n## Authentication Methods\n\nThe SDK supports multiple authentication methods that can be used simultaneously:\n\n### 1. JWT Bearer Token\n\n```bash\ncurl -H \"Authorization: Bearer your-jwt-token\" https://api.example.com/api/profile\n```\n\n### 2. API Key\n\n```bash\ncurl -H \"X-API-Key: your-api-key\" https://api.example.com/api/profile\n```\n\n### 3. Session Token (Cookie)\n\n```bash\ncurl -b \"session_token=your-session-token\" https://api.example.com/api/profile\n```\n\n### 4. Session Token (Header)\n\n```bash\ncurl -H \"X-Session-Token: your-session-token\" https://api.example.com/api/profile\n```\n\n## Permission System\n\n### Basic Permission Checking\n\n```go\n// Require specific permissions\nr.Use(sdk.RequirePermissions(\"read:users\", \"write:users\"))\n\n// Check permissions in handlers\nif !middleware.HasPermission(r, \"delete:users\") {\n    http.Error(w, \"Insufficient permissions\", http.StatusForbidden)\n    return\n}\n```\n\n### Advanced Permission Checking\n\n```go\n// Dynamic permission checking with context\nchecker := sdk.PermissionChecker()\ncanRead, err := checker.Check(r.Context(), userID, \"read:user\",\n    permissions.WithResource(\"user\"),\n    permissions.WithResourceID(\"user-123\"),\n    permissions.WithOrganizationContext(orgID),\n)\n```\n\n### Role-Based Access\n\n```go\n// Require specific roles\nr.Use(sdk.RequireRoles(\"admin\", \"manager\"))\n\n// Check roles in handlers\nif !middleware.HasRole(r, \"admin\") {\n    http.Error(w, \"Admin access required\", http.StatusForbidden)\n    return\n}\n```\n\n### User Type Restrictions\n\n```go\n// Require specific user types\nr.Use(sdk.RequireUserType(\"internal\"))  // Internal users only\nr.Use(sdk.RequireUserType(\"external\"))  // External users only\nr.Use(sdk.RequireUserType(\"end_user\"))  // End users only\n```\n\n## Multi-Tenant Support\n\n### Organization Context\n\n```go\n// Require organization context\nr.Use(sdk.RequireOrganization())\n\n// Access organization in handlers\nfunc handler(w http.ResponseWriter, r *http.Request) {\n    org := middleware.GetOrganization(r)\n    if org == nil {\n        http.Error(w, \"Organization required\", http.StatusForbidden)\n        return\n    }\n    \n    // Use organization-scoped data\n    fmt.Printf(\"Organization: %s\\n\", org.Name)\n}\n```\n\n### Permission Context\n\n```go\n// Check permissions within organization context\nchecker := sdk.PermissionChecker()\nallowed, err := checker.Check(ctx, userID, \"manage:users\",\n    permissions.WithOrganizationContext(orgID),\n)\n```\n\n## Huma Framework Integration\n\n### Basic Setup\n\n```go\n// Apply authentication middleware\nprotectedGroup := huma.NewGroup(api, \"/api\")\nprotectedGroup.UseMiddleware(sdk.HumaMiddleware(api))\n\n// Permission-based endpoints\nadminGroup := huma.NewGroup(protectedGroup, \"/admin\")\nadminGroup.UseMiddleware(sdk.HumaRequirePermissions(api, \"admin:access\"))\n```\n\n### Permission Middleware\n\n```go\n// Flexible permission checking with resource context\nuserGroup := huma.NewGroup(protectedGroup, \"/users\")\nuserGroup.UseMiddleware(sdk.HumaPermissionMiddleware(api, \n    \"read:user\",     // permission\n    \"user\",          // resource type\n    \"userId\",        // path parameter for resource ID\n))\n```\n\n### Context Access\n\n```go\nfunc handler(ctx context.Context, input *Input) (*Output, error) {\n    humaCtx := ctx.Value(\"huma_context\").(huma.Context)\n    \n    // Get authentication context\n    user := frankHuma.GetUser(humaCtx)\n    org := frankHuma.GetOrganization(humaCtx)\n    \n    // Check permissions\n    if !frankHuma.HasPermission(humaCtx, \"read:data\") {\n        return nil, huma.Error403Forbidden(\"Insufficient permissions\")\n    }\n    \n    return \u0026Output{Body: \"success\"}, nil\n}\n```\n\n## Optional Authentication\n\nSupport both authenticated and anonymous users:\n\n```go\nr.Route(\"/api\", func(r chi.Router) {\n    r.Use(sdk.OptionalMiddleware())\n\n    r.Get(\"/content\", func(w http.ResponseWriter, r *http.Request) {\n        user := middleware.GetUser(r)\n        \n        if user != nil {\n            // Authenticated user - premium content\n            json.NewEncoder(w).Encode(map[string]interface{}{\n                \"content\": \"Premium content\",\n                \"user\":    user.Email,\n            })\n        } else {\n            // Anonymous user - basic content\n            json.NewEncoder(w).Encode(map[string]interface{}{\n                \"content\": \"Basic content\",\n                \"message\": \"Login for premium features\",\n            })\n        }\n    })\n})\n```\n\n## Error Handling\n\n### Custom Error Handler\n\n```go\ncustomErrorHandler := func(w http.ResponseWriter, r *http.Request, err error) {\n    w.Header().Set(\"Content-Type\", \"application/json\")\n    \n    if authErr, ok := err.(*middleware.AuthError); ok {\n        response := map[string]interface{}{\n            \"error\": map[string]interface{}{\n                \"code\":    authErr.Code,\n                \"message\": authErr.Message,\n                \"path\":    r.URL.Path,\n            },\n        }\n\n        switch authErr.Code {\n        case \"no_auth\":\n            w.WriteHeader(http.StatusUnauthorized)\n            response[\"error\"].(map[string]interface{})[\"hint\"] = \"Include Authorization header\"\n        case \"insufficient_permissions\":\n            w.WriteHeader(http.StatusForbidden)\n            response[\"error\"].(map[string]interface{})[\"hint\"] = \"Contact administrator\"\n        }\n\n        json.NewEncoder(w).Encode(response)\n        return\n    }\n\n    // Generic error\n    w.WriteHeader(http.StatusInternalServerError)\n    json.NewEncoder(w).Encode(map[string]interface{}{\n        \"error\": \"Internal server error\",\n    })\n}\n\n// Use custom error handler\ncfg := config.LoadFromEnv()\nmiddlewareConfig := cfg.ToMiddlewareConfig(sdk.Client())\nmiddlewareConfig.ErrorHandler = customErrorHandler\n\nr.Use(middleware.Auth(middlewareConfig))\n```\n\n## Context Utilities\n\n### HTTP Request Context\n\n```go\n// Get authentication context\nauthCtx := middleware.GetAuthContext(r)\nuser := middleware.GetUser(r)\norg := middleware.GetOrganization(r)\nuserID := middleware.GetUserID(r)\norgID := middleware.GetOrganizationID(r)\n\n// Check authentication status\nif middleware.IsAuthenticated(r) {\n    // User is authenticated\n}\n\n// Check user type\nif middleware.IsUserType(r, \"admin\") {\n    // User is admin type\n}\n\n// Check permissions and roles\nif middleware.HasPermission(r, \"read:data\") {\n    // User has permission\n}\n\nif middleware.HasRole(r, \"manager\") {\n    // User has role\n}\n```\n\n### Huma Context\n\n```go\n// Get authentication context\nuser := frankHuma.GetUser(ctx)\norg := frankHuma.GetOrganization(ctx)\nuserID := frankHuma.GetUserID(ctx)\norgID := frankHuma.GetOrganizationID(ctx)\n\n// Check authentication status\nif frankHuma.IsAuthenticated(ctx) {\n    // User is authenticated\n}\n\n// Check permissions and roles\nif frankHuma.HasPermission(ctx, \"read:data\") {\n    // User has permission\n}\n```\n\n## Advanced Usage\n\n### Multiple SDK Instances\n\n```go\n// Different configurations for different route groups\npublicSDK, _ := frank.New(\u0026config.Config{\n    BaseURL:  frankAuthURL,\n    APIKey:   apiKey,\n    Optional: true,\n})\n\nmemberSDK, _ := frank.New(\u0026config.Config{\n    BaseURL:         frankAuthURL,\n    APIKey:          apiKey,\n    RequiredRoles:   []string{\"member\"},\n})\n\nadminSDK, _ := frank.New(\u0026config.Config{\n    BaseURL:           frankAuthURL,\n    APIKey:            apiKey,\n    RequiredUserType:  \"internal\",\n    RequiredRoles:     []string{\"admin\"},\n})\n\n// Use different SDKs for different routes\nr.Route(\"/public\", func(r chi.Router) {\n    r.Use(publicSDK.OptionalMiddleware())\n    // Public routes\n})\n\nr.Route(\"/member\", func(r chi.Router) {\n    r.Use(memberSDK.Middleware())\n    // Member routes\n})\n\nr.Route(\"/admin\", func(r chi.Router) {\n    r.Use(adminSDK.Middleware())\n    // Admin routes\n})\n```\n\n### Direct Client Usage\n\n```go\n// Use the client directly for custom operations\nclient := sdk.Client()\n\n// Verify tokens manually\nauthResult, err := client.VerifyJWT(ctx, token)\nif err != nil {\n    return err\n}\n\n// Check permissions\ncheckResult, err := client.CheckPermission(ctx, client.PermissionCheckRequest{\n    UserID:     userID,\n    Permission: \"read:users\",\n    Resource:   \"user\",\n    ResourceID: \"user-123\",\n})\n\n// Get user information\nuser, err := client.GetUser(ctx, userID)\norg, err := client.GetOrganization(ctx, orgID)\nroles, err := client.GetUserRoles(ctx, userID, \u0026orgID)\npermissions, err := client.GetUserPermissions(ctx, userID, \u0026orgID)\n```\n\n## Best Practices\n\n### 1. Environment-Based Configuration\n\nUse environment variables for different deployment environments:\n\n```bash\n# Development\nexport FRANK_AUTH_BASE_URL=\"https://dev-api.frankauth.com\"\nexport FRANK_AUTH_LOG_LEVEL=\"debug\"\nexport FRANK_AUTH_LOG_REQUESTS=\"true\"\n\n# Production\nexport FRANK_AUTH_BASE_URL=\"https://api.frankauth.com\"\nexport FRANK_AUTH_LOG_LEVEL=\"warn\"\nexport FRANK_AUTH_CACHE_ENABLED=\"true\"\n```\n\n### 2. Path-Based Authentication\n\nSkip authentication for specific paths:\n\n```bash\nexport FRANK_AUTH_SKIP_PATHS=\"/health,/metrics,/public/*\"\n```\n\n### 3. Graceful Error Handling\n\nAlways provide meaningful error messages and appropriate HTTP status codes.\n\n### 4. Permission Granularity\n\nUse specific permissions rather than broad roles when possible:\n\n```go\n// Good\nr.Use(sdk.RequirePermissions(\"read:user\", \"write:user\"))\n\n// Less specific\nr.Use(sdk.RequireRoles(\"admin\"))\n```\n\n### 5. Organization Context\n\nAlways verify organization context for multi-tenant applications:\n\n```go\nr.Use(sdk.Middleware())\nr.Use(sdk.RequireOrganization())\n```\n\n## Troubleshooting\n\n### Common Issues\n\n1. **Invalid API Key**: Ensure your API key is correctly set and has the required permissions\n2. **Network Issues**: Check if your application can reach the Frank Auth API\n3. **Permission Denied**: Verify that the user has the required permissions or roles\n4. **Organization Context**: Ensure the user belongs to the required organization\n\n### Debug Logging\n\nEnable debug logging to troubleshoot issues:\n\n```bash\nexport FRANK_AUTH_LOG_LEVEL=\"debug\"\nexport FRANK_AUTH_LOG_REQUESTS=\"true\"\n```\n\n### Testing Authentication\n\nTest different authentication methods:\n\n```bash\n# Test JWT\ncurl -H \"Authorization: Bearer $JWT_TOKEN\" http://localhost:8080/api/profile\n\n# Test API Key\ncurl -H \"X-API-Key: $API_KEY\" http://localhost:8080/api/profile\n\n# Test Session\ncurl -b \"session_token=$SESSION_TOKEN\" http://localhost:8080/api/profile\n```\n\n## Examples\n\nSee the `examples/` directory for comprehensive examples including:\n\n- Basic HTTP server integration\n- Huma framework integration\n- Advanced permission checking\n- Multi-tenant applications\n- Custom error handling\n- Optional authentication\n- Configuration management\n\n## Contributing\n\n1. Fork the repository\n2. Create a feature branch\n3. Make your changes\n4. Add tests\n5. Submit a pull request\n\n## License\n\nMIT License - see LICENSE file for details.\n\n## Support\n\n- Documentation: [https://docs.frankauth.com/go-sdk](https://docs.frankauth.com/go-sdk)\n- Issues: [https://github.com/your-org/frank-go-sdk/issues](https://github.com/your-org/frank-go-sdk/issues)\n- Support: [support@frankauth.com](mailto:support@frankauth.com)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxraph%2Ffrank-go-sdk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxraph%2Ffrank-go-sdk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxraph%2Ffrank-go-sdk/lists"}