Admin

A complete admin panel with its own authentication system, sidebar layout, sortable data tables with pagination, user management, and user impersonation. Admin authentication uses passwordless magic links with an email allowlist, keeping admin access separate from the main app's user accounts.

v1.8.3
Level: Advanced
36
2376
10

Jump to file

Install Elixir deps

Flop provides backend pagination, sorting, and filtering for Ecto queries. Flop.Phoenix adds LiveView components for sortable table headers and pagination links. The assets.build and assets.deploy aliases are updated to also build the admin CSS and JS bundles.

Install JS deps

The @phx-hook/copy-to-clipboard hook enables one-click copy buttons throughout the admin UI.

Admin CSS

A separate Tailwind CSS bundle for the admin panel with its own DaisyUI theme configuration. Includes the copied custom variant for copy-to-clipboard visual feedback. Sources only admin web files to keep the bundle independent from the main app.

Admin JS

A separate JavaScript bundle registering the CopyToClipboard hook alongside colocated hooks. The admin panel gets its own LiveSocket instance.

Flop configuration

Flop requires a repo configuration. The admin allowlist and session duration are also configured here, with an empty allowlist by default (no access in production unless explicitly configured).

Dev configuration

In development, the allowlist uses ~r/.+/ to allow any email. The admin asset watchers (esbuild and tailwind for the my_app_admin profile) are added alongside the existing app watchers. The live reload patterns include my_app_admin_web files.

User scope changes

The Scope struct gains an impersonated_by field to track which admin is impersonating. Scope.impersonating?/1 checks whether the current session is impersonated.

Admin Accounts context

The core admin authentication logic:

email_allowed?/1 checks the email against the configured allowlist, which supports both exact string matches and regex patterns.

get_or_create_admin_user_by_email!/1 auto-provisions admin users on first login if their email is allowed. This means you don't need to manually create admin accounts.

deliver_login_instructions/2 generates a magic link token and sends the login email. The login flow intentionally doesn't reveal whether an email is authorized, always showing the same success message.

AdminUser schema

A minimal schema with just an email. No password fields since admin auth is passwordless via magic links.

AdminUserNotifier

Sends magic link login emails to admin users. Uses the same Swoosh mailer as the main app.

AdminUserToken schema

Handles two token types: session tokens (random bytes stored directly) and magic link tokens (hashed for email delivery). Magic links expire after 15 minutes. Session duration is configurable via the :session_duration_days config.

Admin Scope

A separate scope struct for admin operations, parallel to MyApp.Accounts.Scope . Contains the admin_user field.

Query modules

Shared Flop options for admin list views: filterable by email, sortable by email and inserted_at, paginated with 20 items per page, defaulting to newest first.

Runs Flop.validate_and_run/3 against the User schema with the shared options. Params from the URL (page, sort, filters) flow directly through Flop.

Same pattern for admin users.

Admin web module

A separate web module for the admin interface, parallel to MyAppWeb . Imports MyAppAdminWeb.CoreComponents and aliases Flop.Filter for use in templates. Uses the same endpoint and router as the main app, but with its own layout and component modules.

Admin auth plug and LiveView hooks

Handles admin session management with plugs and LiveView on_mount hooks:

Admin core components

Shared admin UI components built with DaisyUI:

copy_button/1 - Inline copy-to-clipboard button that shows the value, displays a clipboard icon on hover, and swaps to a checkmark on copy. Auto-generates the required LiveView hook ID.

data_table/1 - Wraps Flop.Phoenix.table with admin styling: bordered container, hover highlights, whitespace-nowrap cells, and sortable column headers. Views only define columns and actions.

pagination/1 - Custom DaisyUI pagination using join buttons. Builds page links with ellipsis, previous/next buttons, and only renders when there are multiple pages.

format_boolean/1 , format_date/1 , format_datetime/1 - Display helpers that handle nil values with a "-" fallback, keeping templates clean.

Admin layout components

Layout components including admin_ui/1 , the main authenticated layout with the sidebar navigation, flash messages, and content area. Configures sidebar items for Dashboard, Users, and Admin Users pages.

Sidebar layout

A responsive sidebar with mobile drawer support. On desktop, it's a fixed 72-unit wide panel. On mobile, it slides in as an overlay with backdrop. Built with Phoenix.LiveView.JS commands for show/hide transitions.

Admin root layout

The admin HTML shell loads the admin-specific CSS and JS bundles. Includes the same theme persistence script as the main app.

Session controller

Handles the POST from the confirmation page (consuming the magic link token and creating a session) and DELETE for logout.

Impersonation controller

create/2 starts impersonation by storing the admin's session token in impersonating_admin_token and logging in as the target user via the main app's UserAuth .

delete/2 ends impersonation by clearing the user session and redirecting back to the admin panel.

Magic link confirmation

When a user clicks the magic link in their email, this page verifies the token and shows a "Log in" button. The form submits to the session controller to complete the login flow.

Admin users index

Same table pattern as the users index, listing admin users with sortable columns and search.

Login LiveView

The admin login page with a single email input. Shows a helpful notice when running with the local mail adapter. The submit_magic handler checks the allowlist, auto-provisions the admin user if needed, and sends the magic link.

Admin users show

Displays admin user details with copyable ID and email.

Dashboard

A minimal dashboard page that serves as the admin landing page at /admin .

Users index

Lists all app users with sortable columns, email search, and pagination. Uses <.data_table> and <.pagination> components. Email addresses have copy buttons. Rows are clickable to navigate to the show page.

The search handler builds a Flop filter with :ilike_and for partial email matching and pushes a URL patch, keeping search state in the URL.

Users show

Displays user details with copyable ID and email fields. Includes an "Impersonate" button that lets admins log in as any user.

App layouts

Aliases Scope for use in the impersonation banner template.

Impersonation banner

When a session is impersonated, a warning banner appears at the top of every page showing which user is being impersonated and which admin is doing it, with a "Stop impersonating" button.

Router

Admin routes are mounted under /admin with their own pipeline ( admin_browser ) that uses the admin root layout and auth plug. Two live sessions separate authenticated and unauthenticated admin pages. Impersonation routes use POST/DELETE for create/destroy.

User auth changes

fetch_current_scope_for_user and mount_current_scope now check for an impersonating_admin_token in the session and load the admin user into the scope's impersonated_by field.

renew_session preserves both admin_user_token and impersonating_admin_token across session renewals, so impersonation survives session token rotation.

require_sudo_mode is bypassed for impersonated sessions, since the admin has already authenticated.

Migration

Creates admin_users and admin_users_tokens tables. Admin users only have an email field since authentication is passwordless. The tokens table handles both session tokens and magic link tokens.