First Commit
This commit is contained in:
@@ -0,0 +1,322 @@
|
||||
# JWT API Authentication for Odoo 19
|
||||
|
||||
Modul `grt_jwt_token` menyediakan autentikasi JWT untuk aplikasi eksternal yang ingin mengakses endpoint API Odoo dengan pola:
|
||||
|
||||
1. Aplikasi eksternal mengirim `client_id` dan `client_secret`.
|
||||
2. Odoo memvalidasi API Client.
|
||||
3. Odoo mengembalikan JWT access token.
|
||||
4. Aplikasi eksternal memakai token tersebut sebagai `Authorization: Bearer <token>` untuk mengakses endpoint API yang dilindungi.
|
||||
|
||||
Modul ini cocok untuk integrasi API dan SSO berbasis token antar aplikasi. Untuk login otomatis ke UI web Odoo, masih diperlukan flow tambahan yang membuat Odoo session secara eksplisit.
|
||||
|
||||
## Fitur
|
||||
|
||||
- Menu aplikasi utama: **JWT Auth**.
|
||||
- Model konfigurasi API Client.
|
||||
- Generate `client_id` otomatis.
|
||||
- Generate dan rotasi `client_secret`.
|
||||
- Penyimpanan secret sebagai hash PBKDF2, bukan plaintext.
|
||||
- Token JWT dengan claim `iss`, `aud`, `exp`, `iat`, `jti`, `sub`, dan `client_id`.
|
||||
- Decorator `@check_jwt_auth` untuk melindungi controller custom.
|
||||
- Contoh endpoint protected: `/api/ifc/projects`.
|
||||
|
||||
## Struktur Modul
|
||||
|
||||
```text
|
||||
grt_jwt_token/
|
||||
+-- __manifest__.py
|
||||
+-- __init__.py
|
||||
+-- README.md
|
||||
+-- controllers/
|
||||
| +-- __init__.py
|
||||
| +-- main.py
|
||||
+-- models/
|
||||
| +-- __init__.py
|
||||
| +-- api_client.py
|
||||
+-- security/
|
||||
| +-- ir.model.access.csv
|
||||
+-- static/
|
||||
| +-- description/
|
||||
| | +-- icon.png
|
||||
| | +-- icon_128.png
|
||||
| +-- src/img/
|
||||
| +-- jwt_auth_icon.png
|
||||
+-- views/
|
||||
+-- api_client_views.xml
|
||||
```
|
||||
|
||||
## Konfigurasi Odoo
|
||||
|
||||
Tambahkan `jwt_secret` di file `odoo.conf`.
|
||||
|
||||
```ini
|
||||
[options]
|
||||
jwt_secret = ganti_dengan_secret_panjang_minimal_32_karakter
|
||||
```
|
||||
|
||||
Ketentuan:
|
||||
|
||||
- Minimal 32 karakter.
|
||||
- Jangan pakai secret contoh di production.
|
||||
- Simpan secret hanya di server.
|
||||
- Setelah mengubah `odoo.conf`, restart Odoo.
|
||||
|
||||
Contoh konfigurasi lokal:
|
||||
|
||||
```ini
|
||||
db_name = odoo19
|
||||
http_port = 8071
|
||||
jwt_secret = W2V8bVN38YJZJQhcew8Fcgw6vG5Ab79hbRzD3SpjJvGQxNPvKcNFQ8syb6h9PqRr
|
||||
```
|
||||
|
||||
## Instalasi
|
||||
|
||||
1. Pastikan folder `grt_jwt_token` berada di `addons_path`.
|
||||
2. Restart Odoo.
|
||||
3. Buka **Apps**.
|
||||
4. Update Apps List jika modul belum muncul.
|
||||
5. Install **JWT API Authentication**.
|
||||
|
||||
Atau lewat CLI:
|
||||
|
||||
```powershell
|
||||
$env:PYTHONPATH='C:\odoo19\server'
|
||||
.\.venv\Scripts\python.exe C:\odoo19\server\odoo-bin -c .\odoo.conf -d odoo19 -i grt_jwt_token --stop-after-init
|
||||
```
|
||||
|
||||
Untuk update modul:
|
||||
|
||||
```powershell
|
||||
$env:PYTHONPATH='C:\odoo19\server'
|
||||
.\.venv\Scripts\python.exe C:\odoo19\server\odoo-bin -c .\odoo.conf -d odoo19 -u grt_jwt_token --stop-after-init
|
||||
```
|
||||
|
||||
## Membuat API Client
|
||||
|
||||
1. Login sebagai Administrator.
|
||||
2. Buka app **JWT Auth**.
|
||||
3. Buat record baru.
|
||||
4. Isi:
|
||||
- **Application Name**: nama aplikasi eksternal.
|
||||
- **Odoo User**: user Odoo yang akan dipakai ketika token digunakan.
|
||||
- **Token Lifetime (seconds)**: masa berlaku token, default `7200`.
|
||||
5. Simpan.
|
||||
6. Salin `Client ID` dan `New Client Secret`.
|
||||
|
||||
Penting: `client_secret` hanya ditampilkan saat dibuat atau setelah tombol **Regenerate Secret** ditekan. Setelah halaman ditutup, secret tidak bisa dilihat lagi karena database hanya menyimpan hash.
|
||||
|
||||
## Endpoint Token
|
||||
|
||||
URL:
|
||||
|
||||
```text
|
||||
POST /api/auth/token
|
||||
```
|
||||
|
||||
Content-Type:
|
||||
|
||||
```text
|
||||
application/json
|
||||
```
|
||||
|
||||
Body:
|
||||
|
||||
```json
|
||||
{
|
||||
"client_id": "CLIENT_ID_ANDA",
|
||||
"client_secret": "CLIENT_SECRET_ANDA"
|
||||
}
|
||||
```
|
||||
|
||||
`secret_key` juga diterima sebagai alias dari `client_secret` untuk kompatibilitas integrasi.
|
||||
|
||||
Response sukses:
|
||||
|
||||
```json
|
||||
{
|
||||
"access_token": "JWT_TOKEN",
|
||||
"expires_in": 7200,
|
||||
"token_type": "Bearer"
|
||||
}
|
||||
```
|
||||
|
||||
Response gagal:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "Invalid credentials."
|
||||
}
|
||||
```
|
||||
|
||||
## Contoh Request Token
|
||||
|
||||
PowerShell:
|
||||
|
||||
```powershell
|
||||
$body = @{
|
||||
client_id = "CLIENT_ID_ANDA"
|
||||
client_secret = "CLIENT_SECRET_ANDA"
|
||||
} | ConvertTo-Json
|
||||
|
||||
Invoke-RestMethod `
|
||||
-Uri "http://localhost:8071/api/auth/token" `
|
||||
-Method Post `
|
||||
-Body $body `
|
||||
-ContentType "application/json"
|
||||
```
|
||||
|
||||
cURL:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8071/api/auth/token \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"client_id":"CLIENT_ID_ANDA","client_secret":"CLIENT_SECRET_ANDA"}'
|
||||
```
|
||||
|
||||
## Mengakses Endpoint Protected
|
||||
|
||||
Contoh endpoint:
|
||||
|
||||
```text
|
||||
GET /api/ifc/projects
|
||||
```
|
||||
|
||||
Header:
|
||||
|
||||
```text
|
||||
Authorization: Bearer JWT_TOKEN
|
||||
```
|
||||
|
||||
PowerShell:
|
||||
|
||||
```powershell
|
||||
$headers = @{
|
||||
Authorization = "Bearer JWT_TOKEN"
|
||||
}
|
||||
|
||||
Invoke-RestMethod `
|
||||
-Uri "http://localhost:8071/api/ifc/projects" `
|
||||
-Method Get `
|
||||
-Headers $headers
|
||||
```
|
||||
|
||||
cURL:
|
||||
|
||||
```bash
|
||||
curl http://localhost:8071/api/ifc/projects \
|
||||
-H "Authorization: Bearer JWT_TOKEN"
|
||||
```
|
||||
|
||||
Response contoh:
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "JWT token is valid.",
|
||||
"user_id": 2,
|
||||
"user_name": "Administrator",
|
||||
"client_id": "CLIENT_ID_ANDA",
|
||||
"data": [
|
||||
"Proyek_IFC_Gedung_A",
|
||||
"Proyek_IFC_Infrastruktur_B"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Melindungi Controller Custom
|
||||
|
||||
Import decorator dari controller modul:
|
||||
|
||||
```python
|
||||
from odoo import http
|
||||
from odoo.http import request
|
||||
from odoo.addons.grt_jwt_token.controllers.main import check_jwt_auth, json_response
|
||||
|
||||
|
||||
class MyApiController(http.Controller):
|
||||
|
||||
@http.route('/api/my/resource', type='http', auth='public', methods=['GET'], csrf=False)
|
||||
@check_jwt_auth
|
||||
def my_resource(self, **kwargs):
|
||||
return json_response({
|
||||
'user_id': request.env.user.id,
|
||||
'user_name': request.env.user.name,
|
||||
})
|
||||
```
|
||||
|
||||
Saat token valid, decorator akan:
|
||||
|
||||
- Decode dan validasi JWT.
|
||||
- Memastikan client masih aktif.
|
||||
- Memastikan user Odoo masih aktif.
|
||||
- Mengisi `request.jwt_payload`.
|
||||
- Mengisi `request.jwt_client`.
|
||||
- Mengisi `request.jwt_user_id`.
|
||||
- Menjalankan `request.update_env(user=user_id)`.
|
||||
|
||||
## Claim JWT
|
||||
|
||||
Token berisi claim berikut:
|
||||
|
||||
| Claim | Isi |
|
||||
| --- | --- |
|
||||
| `iss` | Issuer, default `odoo` |
|
||||
| `aud` | Audience, default `odoo-api` |
|
||||
| `exp` | Waktu kedaluwarsa token |
|
||||
| `iat` | Waktu token diterbitkan |
|
||||
| `jti` | ID unik token |
|
||||
| `sub` | ID user Odoo dalam format string |
|
||||
| `client_id` | Client ID API Client |
|
||||
|
||||
## Keamanan
|
||||
|
||||
Rekomendasi production:
|
||||
|
||||
- Gunakan HTTPS.
|
||||
- Gunakan `jwt_secret` yang panjang dan random.
|
||||
- Jangan commit `jwt_secret` production ke repository.
|
||||
- Buat satu API Client per aplikasi eksternal.
|
||||
- Mapping API Client ke user Odoo dengan hak akses minimum.
|
||||
- Rotasi secret secara berkala dengan tombol **Regenerate Secret**.
|
||||
- Nonaktifkan API Client yang tidak digunakan.
|
||||
- Batasi masa berlaku token sesuai kebutuhan.
|
||||
- Tambahkan rate limiting di reverse proxy jika endpoint dibuka ke internet.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Error: JWT secret is not configured
|
||||
|
||||
Penyebab:
|
||||
|
||||
- `jwt_secret` belum ada di `odoo.conf`.
|
||||
- Panjang `jwt_secret` kurang dari 32 karakter.
|
||||
- Odoo belum direstart setelah config diubah.
|
||||
|
||||
Solusi:
|
||||
|
||||
1. Tambahkan `jwt_secret` yang valid.
|
||||
2. Restart Odoo.
|
||||
|
||||
### Error: Invalid credentials
|
||||
|
||||
Penyebab:
|
||||
|
||||
- `client_id` salah.
|
||||
- `client_secret` salah.
|
||||
- API Client tidak aktif.
|
||||
- User Odoo yang dipetakan tidak aktif.
|
||||
|
||||
### Error: Token has expired
|
||||
|
||||
Token melewati `Token Lifetime (seconds)`. Minta token baru melalui `/api/auth/token`.
|
||||
|
||||
### Menu JWT Auth belum muncul
|
||||
|
||||
Jika modul sudah ter-install tapi menu belum terlihat:
|
||||
|
||||
1. Logout.
|
||||
2. Hapus local storage browser untuk `webclient_menus` dan `webclient_menus_version`.
|
||||
3. Login ulang atau pakai private/incognito window.
|
||||
|
||||
## Catatan Batasan
|
||||
|
||||
Modul ini belum membuat session login web Odoo otomatis. Token JWT dipakai untuk akses endpoint API yang memakai `@check_jwt_auth`. Jika membutuhkan SSO penuh ke UI Odoo, perlu tambahan flow session seperti OAuth/OIDC callback atau endpoint login session yang divalidasi dengan signature.
|
||||
Reference in New Issue
Block a user