0
# Security System
1
2
Authentication, authorization, and security decorators providing comprehensive user management, role-based permissions, and access control for both traditional views and REST APIs. The security system supports multiple authentication backends and fine-grained permission control.
3
4
## Capabilities
5
6
### Security Decorators
7
8
Decorators that enforce security permissions and access control for view methods and API endpoints.
9
10
```python { .api }
11
from flask_appbuilder.security.decorators import (
12
has_access, has_access_api, protect, permission_name, limit, no_cache
13
)
14
15
@has_access
16
def has_access(f):
17
"""
18
Enable granular security permissions for view methods.
19
Redirects to login page on access denied.
20
21
Usage:
22
@expose('/admin/')
23
@has_access
24
def admin_view(self):
25
return self.render_template('admin.html')
26
"""
27
28
@has_access_api
29
def has_access_api(f):
30
"""
31
Enable security permissions for API methods.
32
Returns HTTP 401/403 on access denied instead of redirect.
33
34
Usage:
35
@expose('/api/data/')
36
@has_access_api
37
def get_data(self):
38
return self.response(200, data={"result": "success"})
39
"""
40
41
@protect(allow_browser_login=False)
42
def protect(allow_browser_login=False):
43
"""
44
Enable security permissions for API methods with JWT support.
45
46
Parameters:
47
- allow_browser_login: Allow Flask-Login session cookies
48
49
Usage:
50
@expose('/api/secure/')
51
@protect()
52
def secure_api(self):
53
return {"message": "JWT authenticated"}
54
55
@expose('/api/browser-ok/')
56
@protect(allow_browser_login=True)
57
def browser_api(self):
58
return {"message": "JWT or session authenticated"}
59
"""
60
61
@permission_name("custom_permission")
62
def permission_name(name):
63
"""
64
Override the permission name for a method.
65
Useful for aggregating methods under single permission.
66
67
Parameters:
68
- name: Custom permission name
69
70
Usage:
71
@expose('/reports/sales/')
72
@has_access
73
@permission_name("can_access_reports")
74
def sales_report(self):
75
return self.render_template('sales.html')
76
77
@expose('/reports/inventory/')
78
@has_access
79
@permission_name("can_access_reports")
80
def inventory_report(self):
81
return self.render_template('inventory.html')
82
"""
83
84
@limit("100/hour", key_func=lambda: request.remote_addr)
85
def limit(limit_value, key_func=None, per_method=False, methods=None,
86
error_message=None, exempt_when=None, override_defaults=True,
87
deduct_when=None, on_breach=None, cost=1):
88
"""
89
Rate limiting decorator for individual routes.
90
91
Parameters:
92
- limit_value: Rate limit string (e.g., "100/hour", "10/minute")
93
- key_func: Function to generate rate limit key
94
- per_method: Apply limit per HTTP method
95
- methods: HTTP methods to apply limit to
96
- error_message: Custom error message
97
- exempt_when: Function to determine exemption
98
- override_defaults: Override default rate limits
99
- deduct_when: Function to determine when to deduct
100
- on_breach: Callback on rate limit breach
101
- cost: Cost per request (default 1)
102
103
Usage:
104
@expose('/api/expensive/')
105
@limit("10/minute")
106
@has_access_api
107
def expensive_operation(self):
108
return {"result": "expensive computation"}
109
"""
110
111
@no_cache
112
def no_cache(view):
113
"""
114
Add no-cache headers to response.
115
116
Usage:
117
@expose('/sensitive-data/')
118
@has_access
119
@no_cache
120
def sensitive_data(self):
121
return self.render_template('sensitive.html')
122
"""
123
```
124
125
### Authentication Constants
126
127
Constants defining supported authentication types and methods.
128
129
```python { .api }
130
from flask_appbuilder.const import (
131
AUTH_OID, AUTH_DB, AUTH_LDAP, AUTH_REMOTE_USER, AUTH_OAUTH
132
)
133
134
# Authentication type constants
135
AUTH_OID = 0 # OpenID authentication
136
AUTH_DB = 1 # Database authentication
137
AUTH_LDAP = 2 # LDAP authentication
138
AUTH_REMOTE_USER = 3 # Remote user authentication (headers)
139
AUTH_OAUTH = 4 # OAuth authentication
140
141
# Usage in configuration
142
AUTH_TYPE = AUTH_DB # Use database authentication
143
AUTH_USER_REGISTRATION = True # Allow user self-registration
144
AUTH_USER_REGISTRATION_ROLE = "Public" # Default role for new users
145
146
# LDAP configuration example
147
AUTH_TYPE = AUTH_LDAP
148
AUTH_LDAP_SERVER = "ldap://ldap.server.com"
149
AUTH_LDAP_BIND_USER = "cn=admin,dc=example,dc=com"
150
AUTH_LDAP_BIND_PASSWORD = "password"
151
AUTH_LDAP_SEARCH = "ou=people,dc=example,dc=com"
152
AUTH_LDAP_UID_FIELD = "uid"
153
154
# OAuth configuration example
155
AUTH_TYPE = AUTH_OAUTH
156
OAUTH_PROVIDERS = [
157
{
158
'name': 'google',
159
'token_key': 'access_token',
160
'icon': 'fa-google',
161
'remote_app': {
162
'client_id': 'your-google-client-id',
163
'client_secret': 'your-google-client-secret',
164
'server_metadata_url': 'https://accounts.google.com/.well-known/openid_configuration',
165
'client_kwargs': {'scope': 'openid email profile'}
166
}
167
}
168
]
169
```
170
171
### Security Manager Integration
172
173
The security manager handles user authentication, authorization, and permission management.
174
175
```python { .api }
176
# Accessing security manager from AppBuilder
177
from flask_appbuilder import AppBuilder
178
179
appbuilder = AppBuilder(app, db.session)
180
security_manager = appbuilder.sm
181
182
# Common security manager methods
183
def get_user_by_id(user_id):
184
"""Get user by ID."""
185
186
def auth_user_db(username, password):
187
"""Authenticate user with database credentials."""
188
189
def auth_user_ldap(username, password):
190
"""Authenticate user with LDAP credentials."""
191
192
def auth_user_oauth(userinfo):
193
"""Authenticate user with OAuth provider info."""
194
195
def add_role(name):
196
"""Add new role to system."""
197
198
def add_permission(name):
199
"""Add new permission to system."""
200
201
def add_permissions_view(permissions_list, view_menu_name):
202
"""Add permissions for a view."""
203
204
def add_permissions_menu(permission_name):
205
"""Add menu permission."""
206
207
def security_cleanup():
208
"""Remove unused permissions."""
209
210
def get_user_menu_access(menu_names=None):
211
"""Get menu items user has access to."""
212
213
# User model properties (when using AUTH_DB)
214
class User(Model):
215
id = Column(Integer, primary_key=True)
216
username = Column(String(64), unique=True, nullable=False)
217
first_name = Column(String(64), nullable=False)
218
last_name = Column(String(64), nullable=False)
219
email = Column(String(120), unique=True, nullable=False)
220
password = Column(String(256))
221
active = Column(Boolean, default=True)
222
created_on = Column(DateTime, default=datetime.datetime.now)
223
changed_on = Column(DateTime, default=datetime.datetime.now)
224
```
225
226
### Permission System
227
228
Flask-AppBuilder uses a comprehensive permission system based on roles and permissions.
229
230
```python { .api }
231
# Permission naming convention
232
# Format: "can_<method_name>" on "<ViewClassName>"
233
234
# Examples of auto-generated permissions:
235
# - "can_list" on "PersonModelView"
236
# - "can_show" on "PersonModelView"
237
# - "can_add" on "PersonModelView"
238
# - "can_edit" on "PersonModelView"
239
# - "can_delete" on "PersonModelView"
240
241
# Custom permissions can be defined:
242
from flask_appbuilder.security.decorators import permission_name
243
244
class MyView(BaseView):
245
@expose('/custom/')
246
@has_access
247
@permission_name("can_access_custom")
248
def custom_method(self):
249
return "Custom functionality"
250
251
# Role management
252
class Role(Model):
253
id = Column(Integer, primary_key=True)
254
name = Column(String(64), unique=True, nullable=False)
255
permissions = relationship("Permission", secondary="ab_permission_view_role")
256
257
# Permission model
258
class Permission(Model):
259
id = Column(Integer, primary_key=True)
260
name = Column(String(100), unique=True, nullable=False)
261
262
# View menu model
263
class ViewMenu(Model):
264
id = Column(Integer, primary_key=True)
265
name = Column(String(250), unique=True, nullable=False)
266
267
# Permission view role association
268
class PermissionViewRole(Model):
269
id = Column(Integer, primary_key=True)
270
permission_view_id = Column(Integer, ForeignKey('ab_permission_view.id'))
271
role_id = Column(Integer, ForeignKey('ab_role.id'))
272
```
273
274
### JWT Authentication
275
276
Support for JSON Web Token authentication for API access.
277
278
```python { .api }
279
# JWT configuration
280
JWT_SECRET_KEY = 'your-jwt-secret-key' # Required for JWT
281
JWT_ACCESS_TOKEN_EXPIRES = datetime.timedelta(hours=1)
282
JWT_REFRESH_TOKEN_EXPIRES = datetime.timedelta(days=30)
283
284
# JWT authentication endpoints (automatically available)
285
# POST /api/v1/security/login - Get JWT tokens
286
# POST /api/v1/security/refresh - Refresh JWT token
287
288
# Using JWT in API requests
289
# Authorization: Bearer <jwt-token>
290
291
# Example API client usage
292
import requests
293
294
# Login to get tokens
295
login_response = requests.post('/api/v1/security/login', json={
296
'username': 'admin',
297
'password': 'password'
298
})
299
tokens = login_response.json()
300
301
# Use access token for API calls
302
headers = {'Authorization': f"Bearer {tokens['access_token']}"}
303
api_response = requests.get('/api/v1/myapi/', headers=headers)
304
305
# Refresh token when expired
306
refresh_response = requests.post('/api/v1/security/refresh', json={
307
'refresh_token': tokens['refresh_token']
308
})
309
new_tokens = refresh_response.json()
310
```
311
312
### Security Configuration Options
313
314
Key configuration settings for customizing the security system behavior.
315
316
```python { .api }
317
# Authentication configuration
318
AUTH_TYPE = AUTH_DB # Authentication method
319
AUTH_USER_REGISTRATION = False # Allow user self-registration
320
AUTH_USER_REGISTRATION_ROLE = "Public" # Default role for new users
321
AUTH_ROLES_SYNC_AT_LOGIN = True # Sync roles at each login (LDAP/OAuth)
322
323
# Password policy
324
AUTH_PASSWORD_COMPLEXITY_ENABLED = True
325
AUTH_PASSWORD_COMPLEXITY_VALIDATOR = r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$"
326
327
# Session configuration
328
PERMANENT_SESSION_LIFETIME = datetime.timedelta(hours=1)
329
SESSION_COOKIE_HTTPONLY = True
330
SESSION_COOKIE_SECURE = True # HTTPS only
331
SESSION_COOKIE_SAMESITE = 'Lax'
332
333
# CSRF protection
334
WTF_CSRF_ENABLED = True
335
WTF_CSRF_TIME_LIMIT = 3600 # 1 hour
336
337
# Rate limiting
338
RATELIMIT_ENABLED = True
339
RATELIMIT_STORAGE_URL = "redis://localhost:6379"
340
341
# OAuth specific settings
342
AUTH_OAUTH_USER_INFO_KEY_USERNAME = "email" # Map OAuth field to username
343
AUTH_OAUTH_USER_INFO_KEY_EMAIL = "email"
344
AUTH_OAUTH_USER_INFO_KEY_FIRST_NAME = "given_name"
345
AUTH_OAUTH_USER_INFO_KEY_LAST_NAME = "family_name"
346
347
# API security
348
FAB_API_SWAGGER_UI = True # Enable Swagger UI
349
FAB_API_SHOW_STACKTRACE = False # Hide stacktraces in production
350
```