373 lines
8.3 KiB
Markdown
373 lines
8.3 KiB
Markdown
|
|
# Jitsi JWT Authentication Implementation
|
||
|
|
|
||
|
|
## Overview
|
||
|
|
|
||
|
|
The system now uses JWT (JSON Web Token) authentication for Jitsi meetings, providing proper moderator privileges and secure meeting access. This replaces the previous URL parameter approach with industry-standard JWT tokens.
|
||
|
|
|
||
|
|
## How It Works
|
||
|
|
|
||
|
|
### JWT Token Structure
|
||
|
|
|
||
|
|
Each meeting link includes a JWT token that contains:
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"context": {
|
||
|
|
"user": {
|
||
|
|
"name": "John Doe",
|
||
|
|
"email": "john@example.com",
|
||
|
|
"moderator": "true" // "true" for admin, "false" for regular users
|
||
|
|
}
|
||
|
|
},
|
||
|
|
"room": "booking-123-1234567890-abc123",
|
||
|
|
"aud": "jitsi",
|
||
|
|
"iss": "your-app-id",
|
||
|
|
"sub": "your-jitsi-domain.com",
|
||
|
|
"exp": 1234567890 // 24 hour expiry
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Meeting URL Format
|
||
|
|
|
||
|
|
**With JWT (Recommended):**
|
||
|
|
```
|
||
|
|
https://meet.jit.si/booking-123-abc?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||
|
|
```
|
||
|
|
|
||
|
|
**Fallback (No JWT configured):**
|
||
|
|
```
|
||
|
|
https://meet.jit.si/booking-123-abc#userInfo.displayName="John Doe"
|
||
|
|
```
|
||
|
|
|
||
|
|
## Configuration
|
||
|
|
|
||
|
|
### Option 1: Public Jitsi (meet.jit.si)
|
||
|
|
|
||
|
|
For testing or using public Jitsi servers, leave JWT configuration empty:
|
||
|
|
|
||
|
|
```env
|
||
|
|
JITSI_BASE_URL=https://meet.jit.si
|
||
|
|
JITSI_API_KEY=
|
||
|
|
JITSI_APP_ID=
|
||
|
|
```
|
||
|
|
|
||
|
|
**Note:** Public Jitsi doesn't enforce JWT authentication, so the system will fall back to URL parameters. Moderator privileges won't work properly on public servers.
|
||
|
|
|
||
|
|
### Option 2: Self-Hosted Jitsi with JWT
|
||
|
|
|
||
|
|
For production with proper moderator control, configure your self-hosted Jitsi:
|
||
|
|
|
||
|
|
```env
|
||
|
|
JITSI_BASE_URL=https://meet.yourdomain.com
|
||
|
|
JITSI_API_KEY=your_jwt_secret_key_here
|
||
|
|
JITSI_APP_ID=your_app_id_here
|
||
|
|
```
|
||
|
|
|
||
|
|
## Setting Up Self-Hosted Jitsi with JWT
|
||
|
|
|
||
|
|
### 1. Install Jitsi Meet
|
||
|
|
|
||
|
|
Follow the official guide: https://jitsi.github.io/handbook/docs/devops-guide/devops-guide-quickstart
|
||
|
|
|
||
|
|
### 2. Enable JWT Authentication
|
||
|
|
|
||
|
|
Edit `/etc/prosody/conf.avail/yourdomain.com.cfg.lua`:
|
||
|
|
|
||
|
|
```lua
|
||
|
|
VirtualHost "yourdomain.com"
|
||
|
|
authentication = "token"
|
||
|
|
app_id = "your_app_id"
|
||
|
|
app_secret = "your_jwt_secret_key"
|
||
|
|
allow_empty_token = false
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3. Configure Jicofo
|
||
|
|
|
||
|
|
Edit `/etc/jitsi/jicofo/sip-communicator.properties`:
|
||
|
|
|
||
|
|
```properties
|
||
|
|
org.jitsi.jicofo.auth.URL=XMPP:yourdomain.com
|
||
|
|
```
|
||
|
|
|
||
|
|
### 4. Restart Services
|
||
|
|
|
||
|
|
```bash
|
||
|
|
systemctl restart prosody
|
||
|
|
systemctl restart jicofo
|
||
|
|
systemctl restart jitsi-videobridge2
|
||
|
|
```
|
||
|
|
|
||
|
|
### 5. Update Your .env File
|
||
|
|
|
||
|
|
```env
|
||
|
|
JITSI_BASE_URL=https://meet.yourdomain.com
|
||
|
|
JITSI_API_KEY=your_jwt_secret_key
|
||
|
|
JITSI_APP_ID=your_app_id
|
||
|
|
```
|
||
|
|
|
||
|
|
## API Response Examples
|
||
|
|
|
||
|
|
### GET /api/bookings
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"bookings": [
|
||
|
|
{
|
||
|
|
"id": 123,
|
||
|
|
"jitsi_room_id": "booking-123-1234567890-abc123",
|
||
|
|
"jitsi_room_url": "https://meet.jit.si/booking-123-1234567890-abc123",
|
||
|
|
"personalized_meeting_url": "https://meet.jit.si/booking-123-1234567890-abc123?jwt=eyJhbGc..."
|
||
|
|
}
|
||
|
|
]
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### GET /api/admin/bookings
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"bookings": [
|
||
|
|
{
|
||
|
|
"id": 123,
|
||
|
|
"jitsi_room_id": "booking-123-1234567890-abc123",
|
||
|
|
"jitsi_room_url": "https://meet.jit.si/booking-123-1234567890-abc123",
|
||
|
|
"admin_meeting_url": "https://meet.jit.si/booking-123-1234567890-abc123?jwt=eyJhbGc..."
|
||
|
|
}
|
||
|
|
]
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### GET /api/meetings/:id/link
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"booking_id": 123,
|
||
|
|
"meeting_url": "https://meet.jit.si/booking-123-1234567890-abc123?jwt=eyJhbGc...",
|
||
|
|
"display_name": "John Doe",
|
||
|
|
"is_admin": false
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## User vs Admin Differences
|
||
|
|
|
||
|
|
### Regular User Token
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"context": {
|
||
|
|
"user": {
|
||
|
|
"name": "John Doe",
|
||
|
|
"email": "john@example.com",
|
||
|
|
"moderator": "false"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Permissions:**
|
||
|
|
- Can join meeting
|
||
|
|
- Can share screen
|
||
|
|
- Can mute/unmute themselves
|
||
|
|
- Cannot kick participants
|
||
|
|
- Cannot end meeting for all
|
||
|
|
|
||
|
|
### Admin User Token
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"context": {
|
||
|
|
"user": {
|
||
|
|
"name": "Dr. Smith",
|
||
|
|
"email": "dr.smith@example.com",
|
||
|
|
"moderator": "true"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Permissions:**
|
||
|
|
- All regular user permissions
|
||
|
|
- Can kick participants
|
||
|
|
- Can mute other participants
|
||
|
|
- Can end meeting for all
|
||
|
|
- Can start/stop recording (if configured)
|
||
|
|
- Can enable/disable lobby
|
||
|
|
|
||
|
|
## Email Notifications
|
||
|
|
|
||
|
|
All email notifications now include JWT-authenticated links:
|
||
|
|
|
||
|
|
### Meeting Info Email
|
||
|
|
```html
|
||
|
|
<a href="https://meet.jit.si/booking-123?jwt=eyJhbGc...">Join Meeting</a>
|
||
|
|
```
|
||
|
|
|
||
|
|
### Reminder Email
|
||
|
|
```html
|
||
|
|
<a href="https://meet.jit.si/booking-123?jwt=eyJhbGc...">Join Meeting</a>
|
||
|
|
```
|
||
|
|
|
||
|
|
## Security Features
|
||
|
|
|
||
|
|
### Token Expiration
|
||
|
|
- Tokens expire after 24 hours
|
||
|
|
- Users must request a new link after expiration
|
||
|
|
- Prevents unauthorized access to old meetings
|
||
|
|
|
||
|
|
### Moderator Control
|
||
|
|
- Only users with `is_admin: true` get moderator tokens
|
||
|
|
- Moderator status is cryptographically signed in JWT
|
||
|
|
- Cannot be tampered with by users
|
||
|
|
|
||
|
|
### Room Isolation
|
||
|
|
- Each booking gets a unique room ID
|
||
|
|
- Room name is embedded in JWT
|
||
|
|
- Token only works for the specified room
|
||
|
|
|
||
|
|
## Testing
|
||
|
|
|
||
|
|
### Test JWT Generation
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Start the server
|
||
|
|
go run cmd/server/main.go
|
||
|
|
|
||
|
|
# Get a meeting link as regular user
|
||
|
|
curl -H "Authorization: Bearer <user_token>" \
|
||
|
|
http://localhost:8080/api/meetings/123/link
|
||
|
|
|
||
|
|
# Get a meeting link as admin
|
||
|
|
curl -H "Authorization: Bearer <admin_token>" \
|
||
|
|
http://localhost:8080/api/admin/bookings
|
||
|
|
```
|
||
|
|
|
||
|
|
### Verify JWT Token
|
||
|
|
|
||
|
|
Use https://jwt.io to decode and verify your tokens:
|
||
|
|
|
||
|
|
1. Copy the JWT from the meeting URL
|
||
|
|
2. Paste into jwt.io debugger
|
||
|
|
3. Verify the payload contains correct user info
|
||
|
|
4. Check moderator field is "true" for admins
|
||
|
|
|
||
|
|
### Test Moderator Privileges
|
||
|
|
|
||
|
|
1. Create two bookings
|
||
|
|
2. Join one as regular user
|
||
|
|
3. Join another as admin
|
||
|
|
4. Verify admin can:
|
||
|
|
- See moderator controls
|
||
|
|
- Kick participants
|
||
|
|
- End meeting for all
|
||
|
|
|
||
|
|
## Troubleshooting
|
||
|
|
|
||
|
|
### Issue: "Room locked" or "Authentication failed"
|
||
|
|
|
||
|
|
**Cause:** JWT not configured or invalid
|
||
|
|
|
||
|
|
**Solution:**
|
||
|
|
1. Check `JITSI_API_KEY` and `JITSI_APP_ID` are set
|
||
|
|
2. Verify they match your Jitsi server configuration
|
||
|
|
3. Ensure Jitsi server has JWT authentication enabled
|
||
|
|
|
||
|
|
### Issue: Admin doesn't have moderator privileges
|
||
|
|
|
||
|
|
**Cause:** JWT not being used or moderator claim not set
|
||
|
|
|
||
|
|
**Solution:**
|
||
|
|
1. Verify `JITSI_API_KEY` is configured
|
||
|
|
2. Check admin user has `is_admin: true` in database
|
||
|
|
3. Decode JWT token and verify `moderator: "true"`
|
||
|
|
|
||
|
|
### Issue: Token expired error
|
||
|
|
|
||
|
|
**Cause:** JWT token older than 24 hours
|
||
|
|
|
||
|
|
**Solution:**
|
||
|
|
1. Request a new meeting link
|
||
|
|
2. Tokens are generated fresh on each API call
|
||
|
|
3. Consider reducing expiry time if needed
|
||
|
|
|
||
|
|
### Issue: Fallback to URL parameters
|
||
|
|
|
||
|
|
**Cause:** JWT configuration missing
|
||
|
|
|
||
|
|
**Solution:**
|
||
|
|
1. This is expected behavior when JWT not configured
|
||
|
|
2. For production, configure JWT authentication
|
||
|
|
3. URL parameters work but don't enforce moderator privileges
|
||
|
|
|
||
|
|
## Migration from URL Parameters
|
||
|
|
|
||
|
|
### Before (URL Parameters)
|
||
|
|
```
|
||
|
|
https://meet.jit.si/room#userInfo.displayName="John"&config.startWithAudioMuted=false
|
||
|
|
```
|
||
|
|
|
||
|
|
**Problems:**
|
||
|
|
- No real moderator enforcement
|
||
|
|
- Anyone can modify URL parameters
|
||
|
|
- No security
|
||
|
|
|
||
|
|
### After (JWT Authentication)
|
||
|
|
```
|
||
|
|
https://meet.jit.si/room?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||
|
|
```
|
||
|
|
|
||
|
|
**Benefits:**
|
||
|
|
- Cryptographically signed tokens
|
||
|
|
- True moderator enforcement
|
||
|
|
- Secure and tamper-proof
|
||
|
|
- Industry standard
|
||
|
|
|
||
|
|
## Best Practices
|
||
|
|
|
||
|
|
1. **Use Self-Hosted Jitsi**: For production, always use self-hosted Jitsi with JWT
|
||
|
|
2. **Rotate Secrets**: Regularly rotate your `JITSI_API_KEY`
|
||
|
|
3. **Monitor Token Usage**: Log token generation for audit purposes
|
||
|
|
4. **Set Appropriate Expiry**: 24 hours is reasonable, adjust based on needs
|
||
|
|
5. **Test Moderator Controls**: Regularly verify admin privileges work correctly
|
||
|
|
|
||
|
|
## Advanced Configuration
|
||
|
|
|
||
|
|
### Custom Token Expiry
|
||
|
|
|
||
|
|
Edit `internal/services/jitsi_service.go`:
|
||
|
|
|
||
|
|
```go
|
||
|
|
ExpiresAt: jwt.NewNumericDate(time.Now().Add(2 * time.Hour)), // 2 hour expiry
|
||
|
|
```
|
||
|
|
|
||
|
|
### Additional JWT Claims
|
||
|
|
|
||
|
|
Add custom claims to the JWT:
|
||
|
|
|
||
|
|
```go
|
||
|
|
type JitsiClaims struct {
|
||
|
|
Context JitsiContext `json:"context"`
|
||
|
|
Room string `json:"room"`
|
||
|
|
Avatar string `json:"avatar,omitempty"` // User avatar URL
|
||
|
|
jwt.RegisteredClaims
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Multiple Jitsi Servers
|
||
|
|
|
||
|
|
Support multiple Jitsi servers by environment:
|
||
|
|
|
||
|
|
```go
|
||
|
|
func (j *jitsiService) getJitsiURL() string {
|
||
|
|
if os.Getenv("ENVIRONMENT") == "production" {
|
||
|
|
return "https://meet.production.com"
|
||
|
|
}
|
||
|
|
return "https://meet.staging.com"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## References
|
||
|
|
|
||
|
|
- [Jitsi JWT Documentation](https://jitsi.github.io/handbook/docs/devops-guide/devops-guide-docker#authentication)
|
||
|
|
- [JWT.io](https://jwt.io)
|
||
|
|
- [Jitsi Meet API](https://jitsi.github.io/handbook/docs/dev-guide/dev-guide-iframe)
|