Update from Lesson 9: Workspace access protection and the Cloud Run API are now implemented. Continue to the backend deployment lesson for the current architecture and the Firestore work still pending. The account below preserves the Lesson 8 milestone.
Become SWE learning journal | 17 September 2026
Overview
We connected the existing Cloudflare Pages website to Auth0, defined Learner, Team and Admin access levels, built role-aware navigation, and applied the Become SWE logo to the website and hosted login experience. This lesson records that implementation and the work still required before private data can be introduced.
The central distinction is authentication versus authorization. Authentication identifies a user. Authorization decides which operation that user may perform. A working login form and a filtered sidebar do not, by themselves, protect an HTML page or an API.
Learning objectives
- Register a browser application and an API in Auth0.
- Configure callback URLs, logout URLs, origins, permissions and roles.
- Connect a custom login domain through Cloudflare DNS.
- Route each signed-in user to the right dashboard and show permitted navigation.
- Apply a consistent logo and favicon, then verify a fresh login flow.
- Separate completed frontend work from future backend enforcement.
Starting point and scope
Lesson 7 moved the plain HTML, CSS and JavaScript frontend to Cloudflare Pages. GitHub remains the source of truth and pushes to main trigger deployment. This lesson adds no framework or build system. The Auth0 SPA SDK runs in the browser even though the site contains multiple static HTML pages.
Earlier lessons discussed Google Cloud Run. Our current phase instead plans Cloudflare Workers or Pages Functions with D1; Google Cloud is not needed now. The eleven workspace pages remain public demonstrations with sample data and inactive management actions.
Tools and responsibilities
GitHub versions the source. Cloudflare Pages serves the site. Auth0 hosts login and manages identity, roles and API permissions. Cloudflare DNS connects the login hostname. A future trusted backend will validate tokens and enforce access; D1 will store application profiles, progress and audit events.
Configure the Auth0 application
In Applications > Applications, create or select SWE Ticker Bell as a Single Page Application. Inspect existing applications first: this tenant also serves an older application, so unrelated roles and settings must be preserved.
| Setting | Value for this project |
|---|---|
| Application type | Single Page Application |
| Allowed Callback URLs | https://swe.tickerbell.biz/ |
| Allowed Logout URLs | https://swe.tickerbell.biz/ |
| Allowed Web Origins | https://swe.tickerbell.biz |
| Allowed Origins CORS | https://swe.tickerbell.biz |
| SDK domain after custom-domain setup | login.tickerbell.biz |
Callback and logout entries are complete URLs. Origins contain scheme and hostname, without a path. Register development origins separately when a real local port is chosen; do not use broad wildcard allowlists.
The earlier setup entered /login/ as the Application Login URI. That route was not present in the inspected static repository. Before relying on it, implement a route that starts login or configure a suitable implemented route. Do not confuse that optional initiation route with the callback handled at the homepage.
Record the Domain and Client ID for browser configuration. They are public identifiers. A browser application must not contain a Client Secret or a Management API token. Keep passwords and verification codes out of source files, screenshots and the learning record.
Enable a login connection
Open Authentication > Database > Username-Password-Authentication and enable the SWE application. The session also showed Google enabled, but that is not evidence of a completed production social-login configuration or test. Use accounts controlled by the project team for initial role testing.
Create or find each test user and assign the intended SWE role. Preserve existing assignments belonging to other applications. Registering an account does not automatically grant the Learner role; scoped default-role automation remains future work.
Register the API and permissions
Create SWE Ticker Bell API with identifier https://api.swe.tickerbell.biz and signing algorithm RS256. The identifier is the audience that the frontend requests. It is not proof that an API service or DNS record exists at that address.
In the API settings, enable RBAC and Add Permissions in the Access Token. The permissions claim can include the permissions assigned to the user; the scope claim follows requested and granted scopes. The sidebar in this project reads permissions, not a role-name claim. See the Auth0 RBAC reference at the end of this lesson.
| Permission | Assign to these SWE levels |
|---|---|
| learner:access | Learner, Team, Admin |
| profile:manage | Learner, Team, Admin |
| progress:manage | Learner, Team, Admin |
| team:access | Team, Admin |
| content:manage | Team, Admin |
| learners:view | Team, Admin |
| admin:access | Admin |
| users:manage | Admin |
| roles:manage | Admin |
| logs:view | Admin |
| settings:manage | Admin |
Keep the three roles separate from the older application
Use the exact existing names: swe_learner, swe-team and swe_admin. The mixed hyphen and underscore spelling is intentional because these are the names created in this session. The website displays Learner, Team and Admin.
Roles are tenant-wide. Prefixing SWE roles avoids accidentally reusing the older admin role, but naming alone does not isolate applications. Assign only permissions from the SWE API and scope any future registration automation to the SWE Client ID.
Give Team the three Learner permissions plus its three additional permissions. Give Admin all eleven. Auth0 does not infer this inheritance from role names. Where API Access policies are configured, permit the intended user-delegated access for the SPA; do not enable machine access merely to make browser login work.
Preserve the eleven page access matrix
The sidebar follows the signed-in user's level, not the URL currently being viewed. Admin sees all eleven links even while visiting a Learner page. Team sees seven; Learner sees four. Highlight the current page without changing the menu's access scope.
| Page and route | Visible to |
|---|---|
| Learner dashboard /learner/ | Learner, Team, Admin |
| My profile /learner/profile/ | Learner, Team, Admin |
| My progress /learner/progress/ | Learner, Team, Admin |
| Saved lessons and downloads /learner/saved/ | Learner, Team, Admin |
| Team dashboard /team/ | Team, Admin |
| Lesson management /team/lessons/ | Team, Admin |
| Learner management /team/learners/ | Team, Admin |
| Admin dashboard /admin/ | Admin |
| Users and role management /admin/users/ | Admin |
| Activity logs and analytics /admin/activity/ | Admin |
| Platform settings /admin/settings/ | Admin |
The eleven permissions and eleven pages are separate inventories. A permission authorizes a type of action; it does not necessarily correspond to one page. For example, profile:manage must eventually be restricted to the caller's own profile by the backend.
Login and request flow
- The visitor clicks Log in on the Cloudflare-hosted website.
- The Auth0 SDK starts Universal Login using Authorization Code with PKCE.
- Auth0 returns to the allowlisted homepage callback.
- The SDK processes the callback and obtains tokens for the requested audience.
- The frontend chooses the highest permitted dashboard and updates navigation.
- In the future, the browser will send an access token to a trusted API, which validates it before returning protected data.
An ID token describes authentication to the client. An API access token is presented to the API. Neither a decoded token nor a hidden menu item replaces server-side authorization.
Connect the custom login domain
In Auth0 Branding > Custom Domains, configure login.tickerbell.biz and use the exact CNAME target provided by Auth0. In Cloudflare DNS, add CNAME name login, set that target, use DNS only and leave TTL Auto. Do not copy another tenant's target.
The website record swe points to swe-tickerbell.pages.dev and can remain proxied. These two records serve different services. The API audience is a third concept and does not require DNS merely to be registered in Auth0.
A login attempt previously encountered a gateway failure while the custom hostname lacked the required record. After the DNS correction, the hosted login form loaded. This sequence supports checking DNS early; it does not prove that every gateway error has the same cause. Confirm the custom-domain verification and certificate status in Auth0, then test HTTPS. Do not bypass certificate warnings.
Apply the logo and favicon
Use a publicly reachable HTTPS image. We published the project's supplied logo at https://swe.tickerbell.biz/assets/become-swe-logo.png and referenced it in the website header and favicon links.
The application logo alone did not replace the broken image on the hosted login screen. Auth0's shared Company Logo still pointed to an unavailable image. After approval to apply the branding to both applications, the shared logo was changed to Become SWE.
For the login favicon, open Universal Login > Update branding theme > Page > Favicon URL. Enter the image URL, leave the field so the editor registers the change, and select Save and publish. Verify that the editor reports the changes as live. Start a fresh login flow: an existing login transaction may retain its earlier appearance.
On each website page, include:
<link rel="icon" type="image/png"
href="/assets/become-swe-logo.png">
Branding is shared tenant configuration unless a specific override is used. Check the effect on older applications before publishing a shared change.
Connect the browser SDK
The implementation uses the Auth0 SPA SDK 2.25.0 pinned in the existing site. This records the tested project version, not a claim that it is the latest release. The SDK handles PKCE, redirects and token operations. Our code adds application-specific UI behavior.
const client = new window.auth0.Auth0Client({
domain: "login.example.com",
clientId: "YOUR_PUBLIC_SPA_CLIENT_ID",
cacheLocation: "memory",
authorizationParams: {
redirect_uri: window.location.origin + "/",
audience: "https://api.example.com",
scope: "openid profile email"
}
});
Replace the example domain, public Client ID and audience with values from your own application. Do not copy another project's identifiers or add a Client Secret. Initialize one SDK client per document and bind Log in and Log out before awaiting session restoration.
loginButton.addEventListener("click", () =>
client.loginWithRedirect());
logoutButton.addEventListener("click", () =>
client.logout({ logoutParams: {
returnTo: window.location.origin + "/"
}}));
const query = new URLSearchParams(location.search);
const callback = query.has("state") &&
(query.has("code") || query.has("error"));
if (callback) await client.handleRedirectCallback();
else await client.checkSession();
These excerpts explain the flow; the sanitized source file accompanying this lesson contains the UI checks and error handling. Clean callback parameters from the address after processing. On success, send Admin to /admin/, Team to /team/ and Learner to /learner/.
Because this site navigates between HTML documents, its in-memory SDK cache is recreated on each page. Session restoration therefore matters. Show loading, signed-out and error states explicitly. Never silently assume an elevated role when restoration fails.
Render navigation from access permissions
Mark links and section headings with data-access attributes and hide them before initialization. After the SDK supplies a token, decode its permissions for presentation only. Browser-side decoding does not validate a signature.
<h2 data-access="team:access" hidden>Team</h2>
<a href="/team/" data-access="team:access" hidden>
Team dashboard
</a>
The agreed display hierarchy expands an Admin access permission to the Team and Learner menu groups. The real role assignments also include all lower-level permissions. This UI expansion does not grant API permissions.
const visible = new Set(grantedPermissions);
if (visible.has("admin:access")) {
visible.add("team:access");
visible.add("learner:access");
} else if (visible.has("team:access")) {
visible.add("learner:access");
}
document.querySelectorAll("[data-access]")
.forEach(item => {
item.hidden = !visible.has(item.dataset.access);
});
Use a shared script on every workspace page and preserve the CSS rule [hidden] { display: none !important; }. On failure, keep restricted menu groups hidden and provide an accurate status message. On success, clear the sign-in prompt and highlight the current route.
Fix the empty sidebar without overstating the diagnosis
A screenshot showed an authenticated Admin header beside an empty sidebar that still said Sign in. The shared script was revised and its URL versioned on the homepage and all eleven workspace pages to avoid reusing an older cached script. Loading and failure messages were separated from signed-out state.
A stale script was a plausible explanation, not a proven browser-cache diagnosis. The repair was checked with mocked SDK responses against all eleven pages for each of the three roles. Those 33 combinations passed the expected 4, 7 and 11 link counts. They do not constitute live account or backend security tests.
Failures corrections and evidence
| Observation | Correction or remaining check |
|---|---|
| Auth0 guidance did not initially provide tenant access | A skill is guidance; an authorized browser or management connection is needed to act. |
| An older admin role already existed | Created separate SWE roles and preserved unrelated application settings. |
| Custom login hostname failed to load | Added the Auth0-provided DNS-only CNAME and retested the login form. |
| Sidebar remained empty after sign-in | Revised shared navigation, versioned the script and tested all page-role combinations. |
| Hosted logo was broken | Replaced the shared branding URL after approval; verified a fresh login page. |
| Favicon edit did not immediately appear | Committed the field change, saved the theme and checked a fresh login transaction. |
What was actually verified
- Role-navigation tests passed 33 page-and-role combinations using a mocked SDK and the actual sidebar markup.
- Automated checks covered current-page highlighting, dashboard callbacks, missing permissions, token failures and signed-out behavior.
- The deployed page referenced the updated shared script. The login entry point reached Auth0 Universal Login.
- The new logo loaded on the hosted login form, and the new favicon appeared in a fresh login page's icon reference.
- All 28 existing website HTML files referenced the logo favicon before this lesson was added.
An Admin sign-in was observed during the session. Complete live Learner, Team and Admin journeys, role changes, session expiry, disabled-user behavior and direct-URL denials were not verified as a complete suite.
Current limitations
The workspace HTML is still public demo content. User management, progress, analytics and settings are placeholders. There is no implemented trusted access boundary or D1 persistence in this milestone. Do not add private learner information while that remains true.
Next lesson and completion checklist
The next engineering milestone is a minimal Worker or Pages Functions API that validates access tokens and enforces permissions, followed by access tests with all three accounts. Protect page delivery as well as API responses wherever the page itself contains private content. Add D1 only after the identity-to-application flow is understood.
- Confirm the SPA URLs match the deployed origin and callback exactly.
- Confirm API audience, RS256, RBAC and the permissions claim settings.
- Verify all three SWE role assignments independently of older tenant roles.
- Test login, logout, reload and navigation using each role and a signed-out browser.
- Check missing, expired, modified and wrong-audience tokens at the backend.
- Validate signature, trusted issuer, audience, expiry and required permissions.
- Enforce ownership for personal profile and progress operations.
- Return 401 for absent or invalid authentication and 403 for insufficient permission.
- Add scoped default-Learner registration and privileged-account MFA deliberately.
- Keep passwords, tokens, signing keys and private user data out of public repositories.
Takeaways
Keep the 11 workspace pages and three levels consistent. Use permissions to drive the interface, but place actual authorization in trusted server code. Treat DNS, certificates, callback configuration, role assignment, browser state and branding as separate checks. A successful deployment is evidence of delivery, not proof of access protection.
Official references
Auth0 application settings
https://auth0.com/docs/get-started/applications/application-settings
Auth0 API RBAC and permissions claims
https://auth0.com/docs/get-started/apis/enable-role-based-access-control-for-apis
Auth0 SPA SDK and PKCE
https://auth0.com/docs/libraries/auth0-single-page-app-sdk
Auth0 custom domains
https://auth0.com/docs/customize/custom-domains/configure-features-to-use-custom-domains
Cloudflare Pages Functions
https://developers.cloudflare.com/pages/functions/
Lesson files
Read the online lesson at https://swe.tickerbell.biz/lesson8/. The learning repository contains this Markdown source, an editable Word document, a PDF and a sanitized browser implementation example. No real passwords, account emails, tokens or management credentials are included.