strapi-keycloak-passport
Version:
Keycloak authentication provider for the Strapi v5 administration panel.
312 lines (231 loc) • 11.8 kB
Markdown
# Strapi Keycloak Passport Plugin
Enterprise-grade Keycloak authentication for Strapi v5 Admin Panel with full RBAC support.
[](https://www.npmjs.com/package/strapi-keycloak-passport)
[](https://opensource.org/licenses/MIT)
[](https://strapi.io/)
## Features
- **Single Sign-On (SSO)** - Replace Strapi's default admin login with Keycloak OAuth2
- **Keycloak 17+ Support** - Works with both modern (17+) and legacy Keycloak versions
- **Role Mapping** - Dynamically map Keycloak roles to Strapi admin roles via UI
- **Token Caching** - Intelligent caching with automatic refresh before expiry
- **Audit Logging** - Comprehensive audit trail for all authentication events
- **Strapi RBAC Integration** - Leverages Strapi's native Roles & Permissions
- **Secure by Default** - Error sanitization, HttpOnly cookies, input validation
## Requirements
- Node.js >= 20.0.0
- Strapi v5.x
- Keycloak 17+ (or legacy with configuration flag)
## Installation
```bash
yarn add strapi-keycloak-passport
```
or
```bash
npm install strapi-keycloak-passport
```
## Configuration
### Plugin Configuration
Add to your `config/plugins.js` (or `config/plugins.ts`):
```javascript
module.exports = ({ env }) => ({
'strapi-keycloak-passport': {
enabled: env.bool('KEYCLOAK_ENABLED', true),
config: {
// Required: Keycloak server URL (without trailing slash)
KEYCLOAK_AUTH_URL: env('KEYCLOAK_AUTH_URL', 'https://keycloak.example.com'),
// Required: Keycloak realm name
KEYCLOAK_REALM: env('KEYCLOAK_REALM', 'master'),
// Required: OAuth client credentials
KEYCLOAK_CLIENT_ID: env('KEYCLOAK_CLIENT_ID', 'strapi-admin'),
KEYCLOAK_CLIENT_SECRET: env('KEYCLOAK_CLIENT_SECRET'),
// Optional: Set to true for Keycloak versions < 17 (uses /auth prefix)
KEYCLOAK_LEGACY_MODE: env.bool('KEYCLOAK_LEGACY_MODE', false),
// Optional: Role configuration
roleConfigs: {
// Default Strapi role ID when no mapping exists
defaultRoleId: env.int('KEYCLOAK_DEFAULT_ROLE_ID', 1),
// Keycloak roles to exclude from the mapping UI
excludedRoles: [
'uma_authorization',
'offline_access',
'default-roles-master',
],
},
},
},
});
```
### Environment Variables
```bash
# Required
KEYCLOAK_AUTH_URL=https://keycloak.example.com
KEYCLOAK_REALM=master
KEYCLOAK_CLIENT_ID=strapi-admin
KEYCLOAK_CLIENT_SECRET=your-client-secret
# Optional
KEYCLOAK_ENABLED=true
KEYCLOAK_LEGACY_MODE=false
KEYCLOAK_DEFAULT_ROLE_ID=1
```
### Keycloak Version Compatibility
| Keycloak Version | `KEYCLOAK_LEGACY_MODE` | URL Format |
|------------------|------------------------|------------|
| 17+ (Quarkus) | `false` (default) | `https://keycloak.example.com/realms/{realm}/...` |
| < 17 (WildFly) | `true` | `https://keycloak.example.com/auth/realms/{realm}/...` |
## Keycloak Setup
### 1. Create an OAuth Client
1. Navigate to **Keycloak Admin Console** → **Clients**
2. Click **Create Client**:
- **Client ID**: `strapi-admin`
- **Client Protocol**: `openid-connect`
- **Client Authentication**: `On` (Confidential)
3. Configure settings:
- **Root URL**: `https://your-strapi-instance.com`
- **Valid Redirect URIs**: `https://your-strapi-instance.com/admin/*`
- **Web Origins**: `https://your-strapi-instance.com`
4. Go to **Credentials** tab and copy the **Client Secret**
### 2. Assign Required Roles to Client
For the plugin to fetch user roles, the client needs realm-management permissions:
1. Go to **Clients** → `strapi-admin` → **Service Account Roles**
2. Click **Assign role**
3. Filter by **realm-management** client
4. Assign these roles:
- `view-users`
- `view-realm`
- `manage-users` (optional, for future features)
### 3. Create Keycloak Roles
1. Go to **Realm Roles** → **Create Role**
2. Create roles that will map to Strapi admin roles:
- `STRAPI_SUPER_ADMIN`
- `STRAPI_EDITOR`
- `STRAPI_AUTHOR`
### 4. Assign Roles to Users
1. Go to **Users** → Select a user → **Role Mapping**
2. Assign the appropriate Keycloak role
## Role Mapping
### Managing Mappings via Admin UI
1. Log in to Strapi Admin Panel
2. Navigate to **Settings** → **Keycloak Passport**
3. Map Keycloak roles to Strapi admin roles
4. Click **Save**
### Example Mapping
| Keycloak Role | Strapi Role |
|---------------|-------------|
| `STRAPI_SUPER_ADMIN` | Super Admin (1) |
| `STRAPI_EDITOR` | Editor (2) |
| `STRAPI_AUTHOR` | Author (3) |
### How It Works
1. User logs in with Keycloak credentials
2. Plugin fetches user's Keycloak roles
3. Roles are mapped to Strapi roles using saved mappings
4. If no mapping exists, user gets the `defaultRoleId` role
5. User is created/updated in Strapi with assigned roles
## Authentication Flow
```
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ User │ │ Strapi │ │ Keycloak │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
│ 1. POST /admin/login │ │
│ (email + password) │ │
│─────────────────────────────────>│ │
│ │ │
│ │ 2. OAuth2 Password Grant │
│ │─────────────────────────────────>│
│ │ │
│ │ 3. Access Token │
│ │<─────────────────────────────────│
│ │ │
│ │ 4. GET /userinfo │
│ │─────────────────────────────────>│
│ │ │
│ │ 5. User Info + Roles │
│ │<─────────────────────────────────│
│ │ │
│ │ 6. Find/Create Admin User │
│ │ 7. Map Roles │
│ │ 8. Generate Strapi JWT │
│ │ │
│ 9. JWT Token + User │ │
│<─────────────────────────────────│ │
│ │ │
```
## API Endpoints
All endpoints are prefixed with `/strapi-keycloak-passport/`
| Method | Endpoint | Description | Auth |
|--------|----------|-------------|------|
| `POST` | `/admin/login` | Authenticate via Keycloak | No |
| `GET` | `/keycloak-roles` | Fetch available Keycloak & Strapi roles | Yes |
| `GET` | `/get-keycloak-role-mappings` | Get saved role mappings | Yes |
| `POST` | `/save-keycloak-role-mappings` | Save role mappings | Yes |
| `GET` | `/test-connection` | Test Keycloak connectivity | Yes |
| `GET` | `/token-cache-stats` | Get token cache statistics | Yes |
| `POST` | `/invalidate-token` | Invalidate cached admin token | Yes |
## Features in Detail
### Token Caching
Admin tokens are cached in-memory with automatic refresh:
- Tokens are cached using a composite key: `serverUrl:realm:clientId`
- Automatic refresh triggered 60 seconds before expiry
- Cache is invalidated when configuration changes
- Monitor cache via `/token-cache-stats` endpoint
### Audit Logging
All authentication events are logged to the database:
| Event | Logged Data |
|-------|-------------|
| Login Success | User email, Keycloak ID, assigned roles, IP, user agent |
| Login Failure | Attempted email, failure reason, IP, user agent |
| User Created | Email, Strapi ID, Keycloak ID, assigned roles |
| User Updated | Email, changed fields (roles, name, etc.) |
| Role Mapping Changed | Keycloak role, Strapi role, performed by |
Query audit logs via Strapi's Content Manager or API.
### Error Handling
The plugin implements a sanitized error pattern:
- **Internal errors** are logged with full details for debugging
- **Client responses** contain safe, generic messages
- Sensitive information (tokens, secrets) never exposed in responses
## Security Considerations
- **Client Secret**: Store securely in environment variables, never commit
- **Token Storage**: Admin tokens cached in-memory only, never persisted
- **Cookies**: HttpOnly, Secure (in production), SameSite protection
- **Input Validation**: All inputs validated before processing
- **Audit Trail**: All authentication events logged for compliance
## Troubleshooting
### "Failed to authenticate with Keycloak"
1. Verify `KEYCLOAK_AUTH_URL` is correct (no trailing slash)
2. Check if `KEYCLOAK_LEGACY_MODE` matches your Keycloak version
3. Confirm client secret is correct
4. Ensure client has proper service account roles
### "Failed to fetch roles from Keycloak"
1. Verify the client has `view-users` and `view-realm` roles assigned
2. Check Keycloak server logs for permission errors
### "User gets wrong role"
1. Check role mappings in Admin Panel
2. Verify user has the expected role in Keycloak
3. Check if role is in `excludedRoles` configuration
### Test Connection
Use the built-in connection test:
```bash
curl -X GET "https://your-strapi.com/strapi-keycloak-passport/test-connection" \
-H "Authorization: Bearer YOUR_ADMIN_TOKEN"
```
## Changelog
### v0.3.0
- Added Keycloak 17+ support with `KEYCLOAK_LEGACY_MODE` option
- Implemented token caching with automatic refresh
- Added comprehensive audit logging
- Refactored to centralized constants (no magic values)
- Implemented error sanitization pattern
- Added new endpoints: `/test-connection`, `/token-cache-stats`, `/invalidate-token`
- Full JSDoc documentation
- Simplified configuration (removed `KEYCLOAK_TOKEN_URL`, `KEYCLOAK_USERINFO_URL`)
### v0.2.x
- Initial release with basic Keycloak authentication
- Role mapping UI
- Strapi v5 compatibility
## Contributing
Contributions are welcome! Please read our contributing guidelines before submitting PRs.
## License
MIT License - see [LICENSE](LICENSE) for details.
## Support
- [GitHub Issues](https://github.com/LPIX-11/strapi-keycloak-passport/issues)
- [Documentation](https://github.com/LPIX-11/strapi-keycloak-passport#readme)