Overview

Authentication & Authorizations

This section covers the login flow and the main authorization levels: access UI, metadata (table/column/menu), and route-guard on the Angular host side.

1) Login

Reference screen:

  • manual__auth_login__01.png: login screen (username/password + auth actions).

Captcha (enablement):

  • Captcha is enabled when both keys are populated:
  • captcha_public_key
  • captcha_private_key
  • The pre-login configuration is exposed by GET /api/Meta/AuthConfig (captchaEnabled, captchaSiteKey).

Registration (enablement):

  • Registration is exposed in the UI when registrationEnabled = true in AuthConfig.
  • In the backend registration is off by default: it is enabled only with AppSettings:registrationEnabled = true. default-role-id must point to an existing role without admin/superadmin rights, otherwise registration is refused.
  • At login, the Register link appears; the register form includes username, email, password, and (if active) captcha.

Password recovery:

  • At login, the Forgot password? link is available.
  • The reset request uses MetaService.requestPasswordReset and sends an email with a link containing a token.
  • Minimum keys to configure for the email flow:
  • email-host
  • email-port
  • email-ssl
  • email-user
  • email-pwd
  • email-sender-address-registration
  • site-url (base URL used to build the reset link).

Recommended AppSettings (extract):

Snippet 1JSON
{
  "AppSettings": {
    "captcha_public_key": "your-public-key",
    "captcha_private_key": "your-private-key",

    "registrationEnabled": "true",
    "email-sender-address-registration": "no-reply@your-domain.tld",

Login attempt limit

MetaService.login checks the limit before verifying credentials and captcha. Beyond the limit it answers HTTP 429 errors.auth.login_rate_limited, with the seconds to wait in retryAfterSeconds. The response does not say which limit was hit, so as not to reveal whether the username exists.

  • Per user (always on, not configurable): 5 wrong passwords in 5 minutes on the same username lock that user for 1 minute. A successful login resets the counter. Protects against brute force on a single account.
  • Per IP (loginRateLimitPerIpPerMinute, since 1.7.16): maximum number of attempts per minute, successful or not, from the same IP; once exceeded, the IP is locked for 1 minute. Default 0 = off (also with a non-numeric or negative value). On installations exposed to the internet set it, e.g. 30: it is the only defense against credential stuffing, that is many different usernames tried from the same IP, which the per-user limit does not stop.
  • Exempt IPs (loginRateLimitExemptIps, since 1.7.16): comma-separated IPs treated like loopback, so never limited or counted. Meant for stress tests that log in hundreds of users from the same IP (a VirtualBox VM in NAT sees the host as 10.0.2.2). The match is exact; the IPv4-mapped form ::ffff:10.0.2.2 is equivalent to 10.0.2.2.
  • Loopback (127.0.0.1, ::1, ::ffff:127.x.x.x): never limited.
  • Both keys are read at every login, so a change applies without a restart if the configuration reloads by itself (otherwise recycle the application pool).
  • Up to 1.7.15 the per-IP limit was fixed at 30 attempts per minute, with only loopback exempt.
Snippet 2JSON
{
  "AppSettings": {
    "loginRateLimitPerIpPerMinute": "30",
    "loginRateLimitExemptIps": "10.0.2.2, 192.168.1.50"
  }
}

1bis) Admin flag model (server + client)

The framework uses three flags with increasing granularity, evaluated from most restrictive to most permissive:

FlagDB sourceScopeUsed in
isSuperAdminruoli.superadmin (bit)Maximum privilege: may modify project metadataServer gate on every metadata mutation (RawHelpers.checkAdmin) + client UI gate (UserInfoService.isUserAdmin / isCurrentUserAdmin)
isRoleAdminruoli.admin (bit)Nominal "Administrator" role (e.g. seed id_ruolo=2)Per-route fallback grant only (does not bypass checkAdmin)
isAdmin (legacy)utenti.isAdmin (bit)Historical per-user flagPer-route fallback grant only (does not bypass checkAdmin)

The two applicative semantics:

  • "Metadata mutation" gate (scaffolding, board content save, edit column/table metadata, etc.):

- server: RawHelpers.checkAdmin(uid) — throws AuthenticationException("Need administrative rights!") if !user.isSuperAdmin.

- client: UI hides/disables actions read via UserInfoService.isUserAdmin(userLike) which returns userLike.isSuperAdmin.

- About ~40 endpoints are affected.

  • Per-route fallback grant (when a table has md_grant_by_default = false and no explicit user/role/azienda grant exists):

- server: applyTableRestrictions — grants view/edit/insert/delete if user.hasAdminGrant, where:

Snippet 3text
    hasAdminGrant = isSuperAdmin OR isRoleAdmin OR isAdmin

- The cascade is permissive for backward compatibility: a superadmin always passes, but a role with admin=1 or a user with legacy utenti.isAdmin=1 also stays authorized.

The k-user payload (cookie / sessionStorage) after login carries both flags:

Snippet 4JSON
{
  "user_id": 100274,
  "user_name": "admin",
  "role": "Admin",
  "role_id": 1,
  "isAdmin": true,
  "isSuperAdmin": true,

Operational notes:

  • The legacy utenti.isAdmin flag is still populated on the user model but should no longer drive new authorization decisions: prefer isSuperAdmin (gate) or hasAdminGrant (permissive fallback).
  • On the Angular host, the roleRouteCanMatchGuard uses the role label (string "Admin" / "Amministratore" / etc.) to match against FRAMEWORK_ROUTE_ROLE_RULES. It is an applicative routing decision, independent of the DB flags.

1ter) AsmxProxy calls: login required by default

Since 1.7.13 every method reachable through /api/Meta/AsmxProxy/{service}.{method} requires a valid session, except those declared anonymous. This applies to MetaService, scaffolding and the application services in WEB_UI_CRAFTER.ProjectData.Servizi.

  • [AsmxAnonymous] (namespace WEB_UI_CRAFTER.Helpers): method callable without login, such as login, me, GetTranslation, registrationStatus.
  • [AsmxAdmin]: method reserved to administrators (superadmin role). The scaffolding class is marked as a whole: all its methods, on every DBMS, are reserved to administrators.
  • [AsmxFirstRun]: anonymous only while firstRun is true, then reserved to administrators.
  • The proxy only invokes service classes: a fully qualified class name from other namespaces is refused.

An application service that must answer before login has to be marked with [AsmxAnonymous]; otherwise it answers 401 errors.auth.unauthenticated.

Session, logout and user list

  • With enableCookieAuthentication = true the session token, its expiry and its replacement by a new login are checked server-side on every provider, PostgreSQL and Oracle included.
  • With enableCookieAuthentication = "signed" (since 1.7.21) the k-user cookie belongs to the server and is HMAC-signed: several sessions per user as with false, but a cookie without a valid signature, expired or edited answers 401 errors.auth.unauthenticated. With false the privileges and role declared in the cookie are compared with the DB at every request. Comparison of the three modes and the cookie-signing-key key in AppSettings.
  • The user_id of the k-user cookie is accepted only if it is an integer; an unreadable cookie answers 401 errors.auth.unauthenticated.
  • logoutSession, logout and logOutForce act only on the caller's session; closing another user's session requires administrator rights.
  • The user list no longer returns the session token and IP: client code must not rely on them.

2) Authorization Metadata: Tables, Columns, Menu

Screens taken from existing metadata documentation:

  • metadata-integration__metadata-editor-auth-table__desktop.png: table authorizations.
  • metadata-integration__metadata-editor-auth-table-edit-popup__desktop.png: table authorization edit popup.
  • metadata-integration__metadata-editor-auth-column__desktop.png: column authorizations.
  • metadata-integration__metadata-editor-auth-column-edit-popup__desktop.png: column authorization edit popup.
  • metadata-integration__metadata-menu-management__desktop.png: metadata menu management.
  • metadata-integration__metadata-menu-management-edit-popup__desktop.png: menu item edit popup.

Metadata involved:

  • _Metadati_Utenti_Autorizzazioni_Tabelle: view/edit/insert/delete permissions at the route/table level.
  • _Metadati_Utenti_Autorizzazioni_Colonne: field-level permissions (e.g., editability/required per role or user).
  • _metadati__menu: menu item visibility/navigation in combination with route permissions.
  • md_record_restriction_key_user_field_list (route): per-user, per-role or per-company record restriction for non-admin users. It applies to reads (grids, lookups, OData) and, since 1.7.21, to writes too: updateRecord, deleteRecord and deleteRecordByID on a row the restriction hides from the user return 403 errors.auth.route_read_forbidden (reason row_not_visible); before, the UPDATE/DELETE WHERE clause only contained the key. Same rule over OData (404): see OData.

3) Route-Guard Usage (Host Project)

Objective: block reserved routes based on user roles and redirect to /unauthorized.

Host files to modify (WuicTest example):

  • C:\src\Wuic\WuicTest\wwwroot\src\app\routing\route-role-map.ts
  • C:\src\Wuic\WuicTest\wwwroot\src\app\routing\role-route.guard.ts
  • C:\src\Wuic\WuicTest\wwwroot\src\app\wuic-bridges\routes.ts
  • C:\src\Wuic\WuicTest\wwwroot\src\app\app.routes.ts
  • C:\src\Wuic\WuicTest\wwwroot\src\app\component\unauthorized\unauthorized.component.ts

Example 1: role/route rules map

Snippet 5ts
export const FRAMEWORK_ROUTE_ROLE_RULES: RouteRoleRule[] = [
  { key: 'designer', routePattern: 'designer', roles: ['admin'] },
  { key: 'workflow-designer', routePattern: 'workflow-designer', roles: ['admin'] },
  { key: 'dashboard', routePattern: ':route/dashboard', roles: ['admin'] },
  { key: 'report-designer', routePattern: ':route/report-designer', roles: ['admin'] }
];

Example 2: attaching guards to routes

Snippet 6ts
{
  path: ':route/dashboard',
  loadComponent: () => import('wuic-framework-lib-src/component/designer/designer.route.component').then((m) => m.DesignerRouteComponent),
  canMatch: [menuRouteAccessCanMatchGuard, roleRouteCanMatchGuard],
  canActivate: [menuRouteAccessCanActivateGuard, roleRouteCanActivateGuard],
  data: { breadcrumbs: 'dashboard', roleRuleKey: 'dashboard' }
}

Example 3: redirect to unauthorized page

Snippet 7ts
return router.createUrlTree(['/unauthorized'], {
  queryParams: { from: attemptedPath || '/' }
});

Operational notes:

  • roleRuleKey in route.data is preferable when the route contains dynamic parameters.
  • As a fallback, the guard can use route.path for direct match.
  • The unauthorized page must be registered in the host routes and show the requested route (queryParam from).

Screenshot

autenticazione-autorizzazioni / login main
autenticazione-autorizzazioni / login main