Skip to content

Auth0 Setup Tutorial

Auth0 Setup Tutorial: Tenant, Angular App, API, Google Login, Optional Passkeys, and Email OTP

Section titled “Auth0 Setup Tutorial: Tenant, Angular App, API, Google Login, Optional Passkeys, and Email OTP”

This guide is for a small community tool that wants production-style authentication without building password auth from scratch.

The target setup:

Angular UI
-> Auth0 Universal Login
-> Google login through Auth0
-> Auth0 issues tokens
-> ASP.NET Core API validates Auth0 access token
-> API maps Auth0 user to local app user by Auth0 sub

Optional later:

Passkeys
Email OTP passwordless
Account linking, if multiple login methods should become one Obaki account

Important decision:

Use Auth0 sub as identity.
Use email only as profile/contact data.
Do not merge users by email automatically.

Primary references used while preparing this guide:

Your Auth0 account container. It owns your domain, applications, APIs, users, connections, roles, actions, logs, and security settings.

Example tenant domain:

jjosh-dev.us.auth0.com

The client application that sends the user to Auth0 for login.

For Angular, choose:

Single Page Application

A registered API resource in Auth0. This represents your ASP.NET Core API. It gives you an audience value that your Angular app requests and your .NET API validates.

Example API identifier:

https://api.jjosh.dev

This does not need to be a real reachable URL, but it should be stable, unique, and controlled by you.

An identity source Auth0 can use for login.

Examples:

Google OAuth2
Username-Password-Authentication
email passwordless
Facebook
GitHub
Microsoft

For Obaki, start with Google.

Passkeys are configured on Auth0 database connections. Email OTP is configured as an Auth0 passwordless connection. Google is a social connection.

Auth0-hosted login page. Your app redirects users there instead of building its own login form.

Use Universal Login unless you have a strong reason not to.

Use them correctly.

ID token
- For the frontend app
- Tells the UI who signed in
- Contains identity/profile claims
- Do not use it to call your API
Access token
- For the API
- Sent as Bearer token
- Validated by ASP.NET Core
- Contains audience, issuer, subject, scopes, and optionally permissions/custom claims

Your Angular app should call your API with the access token, not the ID token.

The stable subject claim from the token.

Example values:

google-oauth2|123
auth0|456
email|user@example.com

Obaki should store the full sub value as the external identity key.


Go to:

https://auth0.com/signup

Create an account.

Use your real developer email.

When prompted, create a tenant.

Recommended naming:

jjosh-dev

Use a region close to your expected users if Auth0 gives you a region choice.

For a Philippine community tool, any nearby supported region is acceptable. Do not over-optimize this early.

In Auth0 Dashboard:

Applications > Applications > Your Application > Settings

You will see a domain like:

YOUR_TENANT.REGION.auth0.com

Store it as:

AUTH0_DOMAIN=YOUR_TENANT.REGION.auth0.com

Do not include https:// in the plain domain environment value unless your code expects it.


Part B: Create the Angular application in Auth0

Section titled “Part B: Create the Angular application in Auth0”

In Auth0 Dashboard:

Applications > Applications > Create Application

Use:

Name: Obaki Web
Application Type: Single Page Application

Choose Angular if Auth0 asks for technology.

From the application settings page, copy:

Domain
Client ID

You need these in Angular.

Do not expose a client secret in Angular. A SPA is a public client. It cannot safely keep secrets.

For local Angular development, use:

Allowed Callback URLs:
http://localhost:4200
Allowed Logout URLs:
http://localhost:4200
Allowed Web Origins:
http://localhost:4200

If your Angular callback route is /callback, use this instead:

Allowed Callback URLs:
http://localhost:4200/callback

Keep the configured Auth0 callback URL and Angular SDK redirect URI exactly aligned.

When deployed, add your real frontend URL.

Example:

Allowed Callback URLs:
http://localhost:4200, https://jjosh.dev
Allowed Logout URLs:
http://localhost:4200, https://jjosh.dev
Allowed Web Origins:
http://localhost:4200, https://jjosh.dev

If the app is deployed to a subdomain:

https://app.jjosh.dev

use that exact value.

Avoid wildcard callback URLs in production.

Bad:

https://*.jjosh.dev

Better:

https://app.jjosh.dev

Auth0 warns that callback URLs and allowed origins should use exact URLs, and production apps should not rely on localhost or broad wildcard patterns.

Step 5. Enable only the intended connections

Section titled “Step 5. Enable only the intended connections”

Inside the application settings, look for a tab or section named:

Connections

Recommended early setup:

Enable:
- Google
Disable at first:
- Username-Password-Authentication
- email passwordless
- Facebook

Add passkeys or email OTP later only when you intentionally want extra login methods.


In Auth0 Dashboard:

Applications > APIs > Create API

Use:

Name: Obaki API
Identifier: https://api.jjosh.dev
Signing Algorithm: RS256

Use RS256. Auth0’s ASP.NET Core API quickstart uses the API identifier as the audience, and Auth0’s default API signing algorithm is RS256.

Record:

AUTH0_DOMAIN=YOUR_TENANT.REGION.auth0.com
AUTH0_AUDIENCE=https://api.jjosh.dev

The Angular app will request this audience.

The ASP.NET Core API will validate this audience.

In the API settings, add permissions/scopes.

Start minimal:

read:me
manage:own_notifications
admin:manage_notifications

Use scopes for API capability checks. Use your own database for domain-specific ownership rules.

Example:

Scope says: this token can manage notification rules.
Database says: this specific rule belongs to this specific user.

Do both.


In Auth0 Dashboard:

Branding > Universal Login

Set:

Application name
Logo
Primary color
Background color

Keep branding simple.

Avoid custom login-page work until login is working end-to-end.

Recommended first phase:

Default Universal Login
Logo
App name
Simple brand color
Google connection only

Step 3. Test redirects before adding login methods

Section titled “Step 3. Test redirects before adding login methods”

Before tuning providers, test that the Auth0 application redirects correctly.

Use Auth0’s built-in test button or Angular quickstart flow.


Google should be the first login method for this project.

Why:

- Users already understand Google login
- No Obaki-owned passwords
- Lower setup friction than account linking
- Auth0 still issues standard OIDC tokens to Angular and the API

Go to:

https://console.cloud.google.com

Create or select a project.

Suggested name:

Obaki Auth

In Google Cloud Console:

APIs & Services > OAuth consent screen

Configure:

App name: Obaki
User support email: your email
Developer contact email: your email
Audience: External
Authorized domain: jjosh.dev, if deploying under jjosh.dev

For basic login, request only basic OIDC scopes:

openid
profile
email

Avoid sensitive or restricted scopes.

You do not need Drive, Gmail, Calendar, or other Google API scopes for simple sign-in.

Step 3. Create OAuth client credentials in Google

Section titled “Step 3. Create OAuth client credentials in Google”

In Google Cloud Console:

APIs & Services > Credentials > Create Credentials > OAuth client ID

Use:

Application type: Web application
Name: Auth0 Google Login

Add authorized redirect URI:

https://YOUR_AUTH0_DOMAIN/login/callback

Example:

https://jjosh-dev.us.auth0.com/login/callback

This is not your Angular callback URL.

Google redirects back to Auth0. Auth0 then redirects back to your Angular app.

Copy:

Google Client ID
Google Client Secret

In Auth0 Dashboard:

Authentication > Social > Create Connection > Google / OAuth2

Paste:

Client ID
Client Secret

Enable basic profile and email.

Use minimal scopes:

openid profile email

Step 5. Enable Google connection for your Angular app

Section titled “Step 5. Enable Google connection for your Angular app”

In the Google connection settings, enable it for:

Obaki Web

Or go to:

Applications > Applications > Obaki Web > Connections

Enable Google.

Use:

Authentication > Social > Google > Try Connection

Then test from Angular.

Expected result:

Angular redirects to Auth0
Auth0 shows Google option
User signs in with Google
Auth0 redirects back to Angular
Angular shows authenticated state
Angular can request API access token
API accepts the Bearer token
/api/me creates or refreshes the local Obaki user

Expected Auth0 subject shape:

google-oauth2|...

The exact value does not matter. Obaki stores and matches the full sub.

Cause:

Google authorized redirect URI does not match Auth0 callback URL.

Fix:

Google Cloud Console > Credentials > OAuth Client > Authorized redirect URIs

Make sure this exact value exists:

https://YOUR_AUTH0_DOMAIN/login/callback

Usually the Angular app did not request the API audience.

Angular must request:

audience: https://api.jjosh.dev

The API must validate:

Authority: https://YOUR_AUTH0_DOMAIN/
Audience: https://api.jjosh.dev

Do not assume every provider always gives every claim.

For Google, request:

openid profile email

For the backend, do not depend on email as identity. Use sub.


Add passkeys later only if you intentionally want a non-Google login path.

Passkeys can be useful when:

- A user does not want to use Google
- You want a phishing-resistant fallback
- You want passwordless login without relying on a social provider

Passkeys have an identity tradeoff:

Google user: google-oauth2|123
Passkey/database user: auth0|456

Those can be different Auth0 users even when the visible email address is the same.

In Auth0 Dashboard, open the database connection you want to use for Obaki.

Common default:

Username-Password-Authentication

This name is confusing. For this setup, think of it as the Auth0 database connection that owns passkey users.

In the database connection settings, enable or configure passkeys.

Use the Auth0 passkey policy defaults at first unless you have a clear reason to change them.

If Auth0 asks where passkeys are available, enable them for:

Obaki Web

Do not add custom passkey code to Angular. Let Auth0 Universal Login own the browser and device ceremony.

Expected signup flow:

Angular redirects to Auth0
Auth0 asks for email
User creates passkey
Auth0 redirects back to Angular
Angular can request API access token
API accepts the Bearer token
/api/me creates or refreshes a local Obaki user by Auth0 sub

If the same person previously used Google, do not expect passkey login to automatically land on the same Obaki user until account linking exists.


Only add email OTP if Google alone is too restrictive and passkeys are not enough.

Email OTP is useful when:

- A user cannot or will not use Google
- A user cannot create a passkey
- You want a simple fallback without storing passwords

Email OTP has the same identity tradeoff:

Google user: google-oauth2|123
Email passwordless user: email|same-address@example.com
Passkey/database user: auth0|456

Those can be different Auth0 users even when the visible email address is the same.

Step 1. Create or configure email passwordless

Section titled “Step 1. Create or configure email passwordless”

In Auth0 Dashboard:

Authentication > Passwordless

Choose email one-time password.

Do not choose magic links for this Universal Login setup. Auth0 documents magic links as Classic Login only.

Use Auth0’s built-in sender for early testing if acceptable for your tenant.

Before production, configure the sender/from address according to Auth0’s email-provider requirements and your domain ownership.

Do not use Obaki’s contact-email system for Auth0 login codes. Auth0 should send login codes itself.

Step 3. Enable email passwordless for Obaki Web

Section titled “Step 3. Enable email passwordless for Obaki Web”

Enable the email passwordless connection for:

Obaki Web

Keep other optional connections disabled unless intentionally supported.

Expected flow:

Angular redirects to Auth0
User enters email
Auth0 sends one-time code
User enters code
Auth0 redirects back to Angular
Angular can request API access token
API accepts the Bearer token
/api/me creates or refreshes a local Obaki user by Auth0 sub

Record the resulting sub during testing.

If the same email already has a Google user, do not expect email OTP to automatically land on the same local Obaki user.


Part H: Same-email and account-linking issue

Section titled “Part H: Same-email and account-linking issue”

Yes, this is the important issue.

You can capture an email claim when Auth0 gives one, but you cannot use email as the stable account key.

Reasons:

- A user may type a different email later
- Providers can omit email
- Email can change
- Email verification state can vary
- Auth0 treats different connections as separate identities by default
- Passwordless email users are separate from database/social users unless linked

Correct Obaki rule:

Local Users table key: Auth0 sub
Local Users email: optional profile data

Do not do this:

Find local user by email and silently attach a new Auth0 sub

That can let the wrong person into an existing account if email ownership or provider behavior is misunderstood.

Do this instead:

Treat new Auth0 sub as a new local user.
Add account linking later only after both identities are authenticated.

Auth0 account linking can associate identities after the user proves control of both accounts. That is the right later feature if you support multiple login methods.


The current backend shape already fits the Google-first scheme.

Current good behavior:

/api/me reads the token sub claim
GetCurrentUser stores it as AppUser.Auth0UserId
users.auth0_user_id has a unique index
email, display name, and avatar are profile fields only
JWT validation checks issuer, audience, signature, and lifetime

That means Google login does not require a backend refactor.

Current limitation for future account linking:

Notification subscriptions are owned by a hash of the current Auth0 sub.

That is fine for Google-only login.

If you later let the same person sign in through Google, passkey, and email OTP before linking them, Obaki will treat each distinct Auth0 sub as a distinct account. Their subscriptions will not automatically appear across login methods.

If account linking becomes a real product feature, plan one small backend follow-up:

Centralize current-user resolution for protected endpoints.
Prefer local AppUser.Id as the ownership key for user-owned data.
Add a merge/migration path for subscriptions created under secondary Auth0 subjects.

Do not refactor to email-based identity.


Part J: Recommended Auth0 settings for this project

Section titled “Part J: Recommended Auth0 settings for this project”
Single Page Application

For Angular SPA, use Auth0 Angular SDK. It uses Authorization Code Flow with PKCE under the hood.

Do not implement OAuth manually.

Use:

RS256

Start:

Google

Optional later:

Passkeys
Email OTP passwordless

Avoid early:

Facebook
SMS passwordless
Username/password as a promoted login method
Automatic account linking

Do not require MFA for normal community users early.

Use MFA later for admin accounts if your Auth0 plan and configuration support it.

Use Auth0 RBAC for broad API permissions only.

Use your database for ownership and domain rules.

Example:

Auth0 permission: manage:own_notifications
Database rule: notification rule belongs to current local user

Before releasing publicly:

[ ] Auth0 Angular application created as SPA
[ ] Auth0 API created with stable audience
[ ] Allowed Callback URLs include production frontend URL
[ ] Allowed Logout URLs include production frontend URL
[ ] Allowed Web Origins include production frontend URL
[ ] No broad wildcard callback URLs
[ ] Google social connection uses your own Google client ID/secret
[ ] Google OAuth consent screen configured
[ ] API validates issuer and audience
[ ] API requires HTTPS in production
[ ] API CORS allows only your frontend origin
[ ] Local app user is keyed by Auth0 `sub`, not email
[ ] Email OTP passwordless is disabled unless intentionally supported
[ ] Passkeys are disabled unless intentionally supported
[ ] If optional login methods are enabled, duplicate-account behavior is documented
[ ] Account linking is not automatic or email-only
[ ] Admin-only endpoints require an admin policy
[ ] Abuse limits exist for community-created data
[ ] Logs do not print raw tokens
[ ] Secrets are stored outside source control

Example Auth0 values:

AUTH0_DOMAIN=jjosh-dev.us.auth0.com
AUTH0_AUDIENCE=https://api.jjosh.dev
AUTH0_CLIENT_ID=abc123YOURCLIENTID

Example Angular environment:

export const environment = {
production: false,
apiBaseUrl: 'https://localhost:5001',
auth0: {
domain: 'jjosh-dev.us.auth0.com',
clientId: 'abc123YOURCLIENTID',
authorizationParams: {
audience: 'https://api.jjosh.dev',
redirect_uri: window.location.origin
}
}
};

Example API appsettings:

{
"Auth0": {
"Enabled": true,
"Domain": "jjosh-dev.us.auth0.com",
"Audience": "https://api.jjosh.dev",
"ClaimNamespace": "https://jjosh.dev/claims"
},
"Cors": {
"AllowedOrigins": [
"http://localhost:4200"
]
}
}

Do this in order.

1. Auth0 tenant exists
2. Angular SPA application exists
3. Angular app can redirect to Auth0 and back
4. Auth0 API exists
5. Angular requests access token with API audience
6. ASP.NET Core API validates token
7. Protected /api/me endpoint works
8. Google connection works
9. Google login returns a stable google-oauth2 Auth0 sub
10. Local user row is created on first authenticated API call
11. Account-owned notification endpoints require authentication
12. Existing subscriptions remain attached across Google logouts/logins
13. Optional passkey login creates a known, tested Auth0 sub shape
14. Optional email OTP creates a known, tested Auth0 sub shape
15. Optional duplicate-user behavior is accepted or account linking is implemented
16. Admin endpoints reject normal users

Do not add passkeys or email OTP before Google login works.

Do not add RBAC before basic JWT validation works.

Do not add account linking before one-provider login works.


Use this first:

Auth0 tenant
Angular SPA app
Auth0 API audience
Google social connection
ASP.NET Core JWT validation
Local Users table keyed by Auth0 sub

Add this second only if needed:

Passkey login
Email OTP passwordless fallback
Admin role
RBAC permissions
Custom claims, if needed
Account linking, if multiple login methods should share one Obaki account

Avoid this early:

Custom login UI
App-owned password auth
Facebook login
SMS login
Account linking automation
Organizations
SAML/enterprise connections
Fine-grained authorization product