# 05 — Auth: Users Service + Role-Gated UI **Goal:** - Require login for certain actions. - Show/hide UI components based on user role. - Gate sensitive server functions so only specific roles can run them. --- ## 1) What You’ll Build - A login/logout button set. - UI elements that only appear for specific roles (`editor`, `admin`). - A server callable (`dangerous_action`) that only certain roles can call. - Friendly messages when a user tries to overstep their privileges. --- ## 2) Enable the Users Service In your Anvil app: 1. **App → Settings → Users Service → Enable**. 2. Add a few test users in the Users table: - Columns: `email`, `password`, `roles` (Text, Comma-separated, e.g., `editor`, `admin`). 3. Assign roles for each user. Example: - `alice@example.com` → roles: `editor` - `bob@example.com` → roles: `viewer` - `root@example.com` → roles: `admin` --- ## 3) Server-Side Role Helper Add to `ServerModule1` (or create `auth_helpers.py` if you prefer): ```python import anvil.server import anvil.users def require_role(*roles): """ Ensure the logged-in user has at least one of the given roles. """ user = anvil.users.get_user() if not user: raise PermissionError("You must be logged in.") user_roles = set((user.get('roles') or "").replace(" ", "").split(",")) if not set(roles).intersection(user_roles): raise PermissionError(f"You need one of these roles: {', '.join(roles)}") @anvil.server.callable def dangerous_action(): """ Only 'editor' or 'admin' can run this. """ require_role('editor', 'admin') return "Sensitive operation completed successfully." ```` --- ## 4) Client UI Setup On `Form1`: * Add two Buttons: * `button_login` → text: “Login” * `button_logout` → text: “Logout” * Add a Label: `label_user_info` (for current user/roles). * Add a Button: `button_danger` → text: “Run Dangerous Action” (editor/admin only). * Arrange them neatly in a ColumnPanel or RowPanel. --- ## 5) Client Code (Form1) ```python from anvil import * import anvil.server import anvil.users class Form1(Form1Template): def __init__(self, **properties): self.init_components(**properties) self.refresh_ui() def refresh_ui(self): """Refreshes UI based on login status and roles.""" user = anvil.users.get_user() if user: roles = (user.get('roles') or "").replace(" ", "") self.label_user_info.text = f"Logged in as: {user['email']} ({roles})" self.button_login.visible = False self.button_logout.visible = True # Show dangerous button only if editor/admin role_list = roles.split(",") self.button_danger.visible = any(r in ('editor', 'admin') for r in role_list) else: self.label_user_info.text = "Not logged in" self.button_login.visible = True self.button_logout.visible = False self.button_danger.visible = False def button_login_click(self, **event_args): """Log the user in.""" anvil.users.login_with_form() self.refresh_ui() def button_logout_click(self, **event_args): """Log the user out.""" anvil.users.logout() self.refresh_ui() def button_danger_click(self, **event_args): """Attempt to run the dangerous action.""" try: res = anvil.server.call('dangerous_action') Notification(res, style='success', timeout=3).show() except PermissionError as e: Notification(str(e), style='danger', timeout=3).show() except Exception as e: Notification(f"Error: {e}", style='danger').show() ``` --- ## 6) Test Scenarios 1. **Not logged in** → No dangerous button visible; clicking login prompts login form. 2. **Logged in as viewer** → Sees dangerous button hidden. 3. **Logged in as editor/admin** → Sees dangerous button; clicking it runs `dangerous_action` and shows success message. 4. **Force test**: Temporarily show dangerous button for all roles, click it as a viewer, verify server rejects with `PermissionError`. --- ## 7) Bonus: Role Badge * Add a Label next to `label_user_info` styled with a background color based on role. * Example: `editor` → blue badge, `admin` → red badge. --- ## 8) Demo Asset Record a short GIF showing: 1. Logging in as viewer → no dangerous button. 2. Logging in as editor → dangerous button appears and works. 3. Logging in as admin → same, but maybe with admin-only UI section. Save as: `/assets/auth-roles-demo.gif` Reference in this tutorial: ```markdown ![Auth Roles Demo](../assets/auth-roles-demo.gif) ``` --- ## 9) Takeaways * Roles allow fine-grained control without changing client code. * Always check roles **server-side** — the client can be modified by anyone. * Hide UI for UX; enforce on server for security. ---