Skip to content

User login with Profile (OAuth providers + username + linked accounts) #29

@TheTechArch

Description

@TheTechArch

Goal

Enable end-users to sign in to EVKX.net using Gmail (Google), Facebook, GitHub, LinkedIn, and Microsoft, create a single EVKX profile with a unique username, and optionally link multiple provider accounts to the same profile. Persist basic profile settings (language, units, country scope) and user authorization scopes in Azure PostgreSQL using Entity Framework Core, under a dedicated profile schema.

Why

  • Reduce friction for sign-in and cover a broad set of users with common providers.
  • Ensure EVKX has a stable internal user identity independent of any external provider.
  • Improve personalization (language/units/country-filter defaults).
  • Enable controlled access to features and admin tooling using a scope-based authorization model.

Scope

Authentication & Providers

  • Support sign-in with: Google (Gmail), Facebook, GitHub, LinkedIn, Microsoft.
  • Provider must provide email (required for account creation / linking).
  • Store provider identity for stable linking (provider/issuer + subject/provider user id).

Profile creation & username

  • First-time login requires the user to select/create a unique username.
  • Enforce uniqueness (case-insensitive recommended).
  • Reserve/deny problematic usernames (admin, support, evkx, etc.) and optionally apply a profanity list.

Account linking (one profile, many providers)

  • A profile can have multiple linked login methods (e.g. Google + Microsoft + GitHub).

  • A user can log in via any linked provider and end up in the same profile.

  • Safe linking flow:

    • User must already be authenticated to link another provider (recommended), or
    • Verified email match with explicit confirmation (higher risk; avoid if possible).

Profile settings

Store and expose:

  • Preferred language (e.g. en, nb, de, etc.)
  • Preferred units: Metric / Imperial
  • Default EV search country filter (optional; e.g. NO, DE, US)

Scopes & authorization

  • EVKX uses a scope-based authorization model to control access to features and admin capabilities.
  • Users receive default scopes automatically (configured in a scope catalog).
  • Site admins can grant additional scopes and revoke them later.
  • Store a durable audit trail for scope changes (who, when, why).
  • Expose effective scopes in API so UI can enable/disable features cleanly.

Examples of scope names (illustrative):

  • profile.read, profile.write
  • ev.search.advanced
  • ev.compare
  • ev.spec.edit
  • content.edit
  • admin.site

Acceptance criteria

Authentication / Profile

  • User can sign in with Google, Facebook, GitHub, LinkedIn, Microsoft.
  • On first login, user is prompted to create a unique username before continuing.
  • Email is collected from provider and stored; login fails gracefully if email is missing/unverified (policy decided below).
  • User can link an additional provider account from profile settings and then log in using any linked provider to the same profile.
  • Profile settings are persisted and returned via API; UI reflects current settings.
  • Language/units/country defaults apply to relevant UI pages (at minimum: EV search defaults).
  • Basic audit logging exists for: login, linking/unlinking, username creation/change.

Scopes

  • A scope catalog exists with support for default scopes.
  • On profile creation, user receives all scopes marked as default.
  • Admin can grant an additional scope to a user.
  • Admin can revoke a previously granted scope.
  • System exposes the user’s effective scopes via API (/me/scopes or included in /me).
  • Admin scope changes are auditable (grantedBy, grantedAt, revokedAt, reason).
  • At least one real feature is protected by scopes end-to-end (API + UI gating), e.g. “EV Spec Admin access”.

UX notes

First-time login flow

  1. “Sign in with …”
  2. If no profile exists → “Create username”
  3. Profile created → continue to site

Profile page includes

  • Connected accounts list (Google, Facebook, GitHub, LinkedIn, Microsoft)
  • “Link new provider”
  • Settings: Language, Units, Default country filter

Admin UI (for scopes)

  • User lookup/search

  • User details: profile + linked providers + scopes

  • Scope management:

    • List effective scopes
    • Grant scope (dropdown from catalog)
    • Revoke scope (requires reason if desired)

Data model (suggested)

Storage: Azure PostgreSQL, schema profile, accessed via EF Core.

UserProfile (profile.user_profile)

  • id (GUID)
  • username (unique, normalized)
  • email (primary)
  • preferredLanguage
  • preferredUnits (Metric/Imperial)
  • defaultCountry (nullable)
  • createdAt, updatedAt

UserLogin (profile.user_login)

  • id (GUID)
  • userProfileId (FK)
  • provider (string/enum: google, facebook, github, linkedin, microsoft)
  • providerSubject (string)
  • issuer (string, recommended)
  • email (as seen from provider, optional)
  • createdAt

Indexes

  • Unique: (provider, providerSubject) (or (issuer, providerSubject) depending on your design)
  • Unique: normalized username

Scopes data model

Scope catalog (profile.scope)

  • id (GUID)
  • name (unique, normalized)
  • description (string)
  • isDefault (bool)
  • createdAt, updatedAt

Indexes

  • Unique: (name)

User scope grants (profile.user_scope_grant)

  • id (GUID)
  • userProfileId (FK → profile.user_profile.id)
  • scopeId (FK → profile.scope.id)
  • grantType (string/enum): default, admin (optionally system)
  • grantedByUserProfileId (nullable FK → profile.user_profile.id)
  • grantedAt (timestamp)
  • revokedAt (nullable timestamp)
  • reason (nullable string)

Indexes / constraints

  • Unique active grant (Postgres partial unique index):

    • (userProfileId, scopeId) WHERE revokedAt IS NULL
  • Index: (userProfileId, revokedAt)

  • Index: (scopeId, revokedAt)

Default scope behavior

  • On new profile creation: insert grants for all scope.isDefault = true.
  • Optional: “sync defaults” job/endpoint to backfill new default scopes to existing users.

API (suggested)

Auth / profile

  • POST /auth/login/{provider} (challenge/start)
  • POST /auth/login/{provider}/callback (callback handling)
  • GET /me
  • PUT /me/settings
  • POST /me/link/{provider} (start linking)
  • POST /me/link/{provider}/callback
  • DELETE /me/link/{provider} (optional, with safeguards)

Scopes

  • GET /me/scopes (or include in GET /me)

Admin (scopes)

  • GET /admin/scopes (list scope catalog)
  • POST /admin/scopes (create/update scope catalog entries; optional)
  • GET /admin/users (search users; optional)
  • GET /admin/users/{userId}/scopes
  • POST /admin/users/{userId}/scopes/{scopeName} (grant; reason optional/required)
  • DELETE /admin/users/{userId}/scopes/{scopeName} (revoke; reason optional/required)

Tasks

Backend

  • Implement ASP.NET Core auth with external providers:

    • Google (Gmail)
    • Facebook
    • GitHub
    • LinkedIn
    • Microsoft
  • Implement provider login + callback handling

  • Implement profile bootstrap flow (pending username)

  • Implement username validation + reservation + normalization

  • Implement linking flow + storage of provider identities (issuer + subject)

  • Implement profile settings persistence + endpoints

  • Create PostgreSQL schema profile + EF Core migrations

  • Add audit logging events (login, linking, username actions)

Backend (scopes)

  • Implement Scope catalog model + migrations
  • Implement UserScopeGrant model + migrations (including partial unique index)
  • Seed initial scope catalog (including default scopes)
  • On profile creation: grant default scopes
  • Implement effective-scope resolution (active grants only)
  • Add authorization checks based on scopes (policy/handler approach recommended)
  • Implement admin endpoints for scope grant/revoke + audit trail

Frontend

  • Login UI (provider buttons)
  • First-time “Create username” screen
  • Profile settings page
  • Connected accounts UI + link/unlink actions
  • Apply defaults in EV search (language/units/country)
  • UI gating for at least one scoped feature (hide/disable actions based on scopes)

Admin UI (scopes)

  • Admin user search + user details view

  • Scope management UI:

    • List effective scopes
    • Grant scope (select from catalog)
    • Revoke scope (optional reason)
  • Display audit info for scope changes (who/when)

Security / Compliance

  • Decide email policy: must be verified? what if provider doesn’t return email?
  • Protect linking against account takeover (require authenticated session for linking)
  • Rate-limit username checks + login attempts
  • Ensure scope admin endpoints require an admin scope (e.g. admin.site)

Tests

  • Unit tests for username rules and normalization

  • Unit tests for linking rules and takeover protection

  • Unit tests for scope resolution (default, admin grants, revocation)

  • Integration tests for external login callbacks (mocked)

  • Integration tests for EF Core migrations (schema + constraints)

  • E2E flows:

    • New user creates profile + receives default scopes
    • Returning user login
    • Link second provider
    • Admin grants scope → feature becomes available

Open questions (add as checklist)

  • Email policy: must be verified? what if provider doesn’t return email?
  • Include issuer in identity uniqueness key for provider identities? (recommended for OIDC)
  • Allow changing username later? if yes, frequency limits?
  • Should default scopes be granted only at creation, or also synced when new defaults are added?
  • What is the initial minimal scope set for all users vs power-users/admins?

Sub-issues

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    Status

    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions