FForm Platform
enzh-CN

Mobile, push and DNN integration

Deliver respondent-friendly mobile experiences and integrate FormPlatform into an existing DNN portal without sacrificing its independent modern runtime.

Respondent External Authentication

Purpose

FormPlatform supports local respondent credentials plus Google and Facebook sign-in. Google uses OpenID Connect authorization-code flow with PKCE. Facebook uses the official ASP.NET Core Facebook OAuth handler because Facebook does not expose a general-purpose standards-compliant OIDC login endpoint.

Both providers end in the existing `Respondent` authentication cookie. The rest of the application therefore continues to use the same respondent authorization policy and client store.

Identity model

The management database contains `survey_external_identity`:

ColumnPurpose
`id`FormPlatform-owned native UUID identity (UUID v7)
`provider`Normalized provider name (`google` or `facebook`)
`provider_subject`Provider's stable subject identifier
`email`Current provider email, if released
`display_name`Name shown in the respondent UI
`picture_url`Optional profile image URL
`created_at`First sign-in time
`last_login_at`Most recent sign-in time

`(provider, provider_subject)` is unique. Email is informational and is never used to merge accounts. Provider access and refresh tokens are not stored.

For an authenticated external respondent, `survey_external_identity.id` is written to the deployment response table's `respondent_id`. The raw Google/Facebook subject is never written to a response. Public unauthenticated submissions keep `respondent_id = NULL`.

Existing anonymous response tables receive the nullable `respondent_id` column automatically when they are next opened or submitted. New anonymous response tables include it from creation.

Access rules

  • Local respondents continue to see deployments assigned through their respondent lists.
  • External respondents see active anonymous deployments only: `list_id IS NULL`, start time reached, and end time not passed.
  • External respondents cannot use an external identity to enter a list-assigned deployment.
  • Public users can still open and submit active anonymous deployments without signing in.
  • When a repeatable anonymous deployment is opened by a signed-in Google/Facebook identity, the server loads that identity's latest response as the starting data. Submitting a previously completed response creates a new response; an `in_progress` response continues updating until completion. A public visitor without an identity still receives a blank form for each repeatable submission.
  • For an anonymous deployment configured as a single-record deployment, the existing global one-record rule remains. Only the identity that owns that record (or the public `NULL` identity for a public answer) may reopen or update it.
  • Authorized system users keep the assisted-entry routes. The Survey Responses workspace must be readable, the target Survey form must grant `submissions` (or the user must have the SurveyAssistant default), and department scope is enforced throughout.

Server routes

RouteFunction
`GET /api/respondent/external/providers`Returns providers that are enabled and completely configured
`GET /api/respondent/external/google/login`Starts Google OIDC login
`GET /api/respondent/external/facebook/login`Starts Facebook login
`GET /api/respondent/external/callback`Converts the temporary external principal into a FormPlatform respondent session
`GET /api/respondent/me`Returns local/external identity kind and provider information

`returnUrl` is accepted by the two login routes, but only application-local paths are allowed. Invalid or external URLs fall back to `/respondent/dashboard`.

Configuration

The current configuration shape is:

{
  "RespondentExternalAuthentication": {
    "Google": {
      "Enabled": true,
      "ClientId": "google-client-id",
      "ClientSecret": "google-client-secret"
    },
    "Facebook": {
      "Enabled": true,
      "ClientId": "facebook-app-id",
      "ClientSecret": "facebook-app-secret"
    }
  }
}

A provider is exposed to the login page only when `Enabled` is true and both credentials are non-empty.

Register these callback URLs in the provider consoles, replacing the origin with the deployed public HTTPS origin:

  • Google: `https://your-host/signin-respondent-google`
  • Facebook: `https://your-host/signin-respondent-facebook`

The checked-in `appsettings.json` contains disabled empty placeholders. Client secrets should not be committed. Moving the values to environment variables, .NET Secret Manager, or a production secret store is a separate configuration-hardening step.

Request flow

1. The login page calls `/api/respondent/external/providers` and displays only configured buttons.

2. The browser navigates to the provider login endpoint.

3. ASP.NET Core creates correlation/state protection and redirects to Google or Facebook.

4. The provider returns to its handler callback path.

5. The handler validates the provider response and creates a short-lived `Respondent.External` cookie.

6. `/api/respondent/external/callback` extracts the stable subject and upserts `survey_external_identity`.

7. FormPlatform discards the temporary cookie and signs the existing `Respondent` cookie with the internal ID.

8. The browser returns to a validated local URL, normally `/respondent/dashboard`.

Operational verification

With `DatabaseMigrations:Enabled=true`, start the application once so the Core migration creates the external identity table. Then verify:

1. Only configured provider buttons appear on `/respondent/login`.

2. Successful login redirects to `/respondent/dashboard`.

3. `/api/respondent/me` reports `identityKind: "external"` and the correct provider.

4. The dashboard lists only currently active deployments whose `list_id` is null.

5. A submitted anonymous response stores a 36-character internal ID in `respondent_id`.

6. That ID resolves to `survey_external_identity.id`; it is not the provider subject.

7. The same external session receives 403 for a list-assigned deployment.

---

Respondent mobile PWA and push notifications

The respondent application can be installed as a Progressive Web App (PWA). The mobile dashboard should use a cursor-paged ItemRenderer/repeater presentation rather than an administration DataGrid, so survey assignments remain readable on a narrow screen.

Dashboard card contract

`GET /api/respondent/deployments` returns only deployments authorized for the current Respondent identity. Each row includes the deployment `name`, `formId`, effective `formName`, raw `completionStatus`, localizable `completionStatusText`, dates, repeat flag, and authorized `openUrl`. Completion status has exactly three dashboard values:

  • `not_started`: no response exists for this respondent and deployment;
  • `in_progress`: the respondent's latest response is a saved draft;
  • `completed`: the respondent's latest response was completed.

The status lookup uses the same respondent-bound response selection as the deployment viewer. It runs only after deployment/list/time/department filtering, so form labels and progress never expose an unassigned deployment. Form summaries are loaded once per request; dynamic response status is read only for the current page.

The default `Survey_Respondent_Dashboard` card renders **Form** and **Progress** as independent `staticContent` children. A Designer may hide or delete either child without changing the API contract. The ItemRenderer localizes `completionStatusText` through the existing `surveyStatus.*` message keys and also localizes `formName` when a code-owned form exposes an i18n display-name key. Existing platform card templates receive the fields once through `respondent-dashboard-card-details-v2`; a replaced renderer/card body is treated as deliberate customization and is not rewritten.

The dashboard notification ItemRenderer keeps its display scope in the API URL as `?location=dashboard`. Collection paging and search parameters are merged into that existing query, so the request contains one `?` and the server receives the exact `dashboard` value. The server continues to reject any location other than `login` or `dashboard`.

`/api/respondent/notifications` is deliberately anonymous-capable because the login page also reads global notices. For a dashboard request it explicitly authenticates the separate Respondent cookie rather than relying on the application's default management-cookie principal. Only a valid local respondent contributes its stored department ancestor chain; anonymous, external, or invalid identities remain global-only.

Push registration

Push notifications use the browser Push API, service worker, and VAPID keys. A respondent opens the dashboard over HTTPS, explicitly enables notifications, grants the browser permission, and the client posts its subscription to FormPlatform. The server stores the subscription against the local respondent or external identity and can send a title, body, and relative dashboard/deployment URL.

Push availability is controlled by the browser and device. Some mobile browsers require Google Play Services or system-level notification permission; an accepted browser permission does not guarantee the push service accepts registration. The UI must surface registration/send failures without breaking dashboard use.

Sending authorization

The `/api/admin/survey/push/*` prefix is retained for compatibility, but sending is not Administrator-only. A system user must have effective `Read` access to the `Survey_Push_Notification` system form. Respondent/deployment pickers and the final subscription query are filtered again by the actor's server-resolved department subtree; a forged respondent or deployment ID cannot expand the recipient set. Administrator remains the recovery principal and omits the department predicate.

Security and deployment

VAPID public/private keys are server secrets. Never place the private key in the Vue bundle, form schema, or public CMS content. HTTPS is mandatory for service workers and normal Push API use; localhost is the limited development exception. Use the public reverse-proxy origin consistently so service-worker scope, callback links, and authentication cookies resolve to the same site.

Chinese edition: RESPONDENT_MOBILE_PWA.zh-CN.md.

---

DNN portal bridge and SSO

FormPlatform remains an independent .NET application when integrated with DNN. The recommended topology is one public origin with two backend applications:

https://portal.example.com/              DNN
https://portal.example.com/formplatform/ FormPlatform behind IIS/Nginx reverse proxy

The DNN PortalBridge module supplies DNN navigation, entry permissions, diagnostics, a signed short-lived SSO ticket, and mapping from DNN users/roles to FormPlatform system users/roles. FormPlatform validates the ticket and maintains its own application cookie. Respondent accounts and external Google/Facebook respondent authentication remain separate from DNN system-user SSO.

Installation principles

Use the unified DNN setup package rather than compiling FormPlatform into DNN/.NET Framework. The setup publishes FormPlatform as a separate .NET service/application, installs the net48 DNN bridge ZIP, configures `/formplatform/` reverse proxying, writes protected secrets, and verifies DNN, FormPlatform, database, and SSO health.

The bridge and FormPlatform must share matching SSO issuer/audience/signing-secret configuration. The secret is protected at rest; never put it in the DNN module page settings or a public client script. Use the settings page to configure the FormPlatform public address, SSO enablement, and role mappings.

Reverse proxy

The proxy must preserve `Host` and `X-Forwarded-Proto`; FormPlatform must know the public `https` scheme and `/formplatform` path base when it generates redirects, asset URLs, payment callbacks, and license-origin checks. Kestrel should listen only on loopback when a reverse proxy terminates TLS.

Chinese edition: DNN_SSO.zh-CN.md.