WebHare 5.8 - user management/wrdauth changes

53 views
Skip to first unread message

Arnold Hendriks

unread,
May 13, 2025, 3:25:42 PMMay 13
to General WebHare developers discussion
I'm working on merging the HareScript and TypeScript wrdauth implementations - they currently use different hooks, APIs and cookie formats, and I'll be forwarding as much HareScript APIs as possible to TypeScript. I'm also merging frontend and tollium's authentication so that Tollium will use standard WRD authentication pages and the standard WRD pages will support enrolling and using two-factor authentication.

This is going to require some unavoidable backwards incompatible changes so be on the lookout for issues with 5.8.

You may have already run into the requirement to set wdauthAccountStatus to { status: "active" , since: Temporal.Now.instant() }, or the harescript equivalent:

  IF(GetWebhareVersionNumber() >= 50800)
    userrec := CELL[...userrec, wrdauth_account_status := [ status := "active" ]];

Support for 'supportobject' (a WRDAuthSupportBase) is now also planned to be removed - instead a TypeScript-based 'customizer' will be required to hook into authentiation behavior

There may also be some other fallout so if you need to run or CI against 5.8 it's best to keep a close eye on the commit log


Wouter Hendriks

unread,
May 26, 2025, 5:09:23 AMMay 26
to General WebHare developers discussion, Arnold Hendriks
I've added a "customizer" because I got the exception:

"supportobjectname= is set but customizer= is not. This may imply critical login restrictions/data have not been ported for WH 5.8!".

However, in the most basic implementation:

import type { AuthCustomizer } from "@webhare/auth";

export class TestAuthCustomizer implements AuthCustomizer {
}

It throws:

Error:
/Users/wouter/projects/webhare/whtree/modules/wrd/js/internal/queries.ts 77,13: Exception EXCEPTION: Could not find attribute 'organization' in type 'wrdPerson'.
Stack trace:
/Users/wouter/projects/webhare/whtree/modules/wrd/js/internal/queries.ts 77,13 (createSelectMap)
/Users/wouter/projects/webhare/whtree/modules/wrd/js/internal/queries.ts 144,30 (runSimpleWRDQuery)
node:internal/process/task_queues 105,5 (process.processTicksAndRejections)
/Users/wouter/projects/webhare/whtree/modules/wrd/js/internal/schema.ts 503,80 (async WRDSchema.getFields)
/Users/wouter/projects/webhare/whtree/jssdk/auth/src/identity.ts 1367,25 (async buildPublicAuthData)
/Users/wouter/projects/webhare/whtree/jssdk/auth/src/identity.ts 1400,21 (async prepCookies)
/Users/wouter/projects/webhare/whtree/jssdk/auth/src/identity.ts 1417,10 (async prepareLogin)
/Users/wouter/projects/webhare/whtree/jssdk/auth/src/identity.ts 1473,26 (async prepareLoginCookies)
/Users/wouter/projects/webhare/whtree/modules/platform/js/nodeservices/calljs.ts 15,12 (async CallJSService.invoke)
/Users/wouter/projects/webhare/whtree/jssdk/services/src/backendservicerunner.ts 185,22 (async WebHareService._onMessage)
wh::javascript.whlib 198,25 (CALLJS)
mod::wrd/lib/internal/auth/legacy-api.whlib 349,14 (WRDAUTH#__CREATELOGINCOOKIES)
mod::wrd/lib/internal/auth/legacy-api.whlib 784,29 (WRDAUTH#FINALIZELOGIN)
mod::wrd/lib/internal/auth/legacy-api.whlib 542,18 (WRDAUTH#SERVICELOGINBYID)
wh::internal/transbase.whlib 643,18 (BUILTINTRANSACTIONBASE#EXECUTEINWORK)
mod::wrd/lib/internal/auth/legacy-api.whlib 933,38 (WRDAUTH#LOGINBYID)
mod::wrd/lib/internal/auth/webdesignplugin.whlib 735,57 (WRDAUTHPLUGIN#LOGINBYID)
mod::mobieclick/webdesigns/mobieclick/pages/login/login.whlib 41,11 (LOGINROUTER)
mod::publisher/lib/internal/pagerendering.whlib 76,102 (EXECUTEROUTER)
mod::publisher/lib/internal/pagerendering.whlib 120,19 (RUNTHEPAGE)
mod::system/lib/internal/webserver/whfsexecute.whlib 107,3 (DOWHFSEXECUTE)
mod::system/scripts/internal/webserver/whfsexecute.whscr 57,1

Seems to be triggered by auth->LoginByID



Arnold Hendriks

unread,
May 26, 2025, 6:45:20 AMMay 26
to General WebHare developers discussion, Wouter Hendriks, Arnold Hendriks
What is the full <wrdauth> rule, especially cachefields= ?

Wouter Hendriks

unread,
May 26, 2025, 8:09:52 AMMay 26
to General WebHare developers discussion, Arnold Hendriks, Wouter Hendriks
Ah, sorry, my bad:

cachefields="wrd_id wrd_contact_email organization wrd_firstname wrd_lastname"

That field is gone in the code (it is no longer in wrdschema.xml), guess i've re-created the WRD schema locally at some point.

Updated, works now, great, thanks.

One question though, my current WHLIB loginsupport does "UpdateWebCookie". What would be the way to do this from TS? `setCookie` doesn't work due to `document is not defined.`. Just use `loadlib` for now, or is there a direct way?

Arnold Hendriks

unread,
May 26, 2025, 8:17:14 AMMay 26
to General WebHare developers discussion, Wouter Hendriks, Arnold Hendriks
The customizer is not necessarily running in a context that has direct access to set cookies. The proper way to supply additional context about the user is onFrontendUserInfo in the customizer, and this will be set in a cookie matching the lifetime of the actual login cookie

  /** Invoked when the user logged in to the frontend, returned to clientside JavaScript */
  onFrontendUserInfo?: (params: FrontendUserInfoParameters<S>) => Promise<object> | object;

Wouter Hendriks

unread,
May 26, 2025, 12:44:54 PMMay 26
to General WebHare developers discussion, Arnold Hendriks, Wouter Hendriks
Well I needed the cookie-setter because I couldn't find a way to get the logged in user (ID) in my TS RPC:

<webservice name="rpc_ts" service="js/rpc.ts#RPC" transports="jsonrpc">

What my code was doing, was save the (encrypted) user ID in a cookie, which I could then read in the rpc.ts file using `this.#req.getCookie()`.

If I understand correctly, I can now use getUserInfo() from "@webhare/frontend", which returns the same data as onFrontendUserInfo. If my RPCs need the user's login info, I can send it to the RPC.

While that works, a better way would be to get the user's ID/info server side, eg in rpc.ts. Obviously I can't use getUserInfo server-side, but is there an other way to find out the user's data in rpc.ts? Maybe something with the `router.WebRequest` object that's passed in the constructor?

Arnold Hendriks

unread,
May 26, 2025, 2:10:26 PMMay 26
to General WebHare developers discussion, Wouter Hendriks, Arnold Hendriks
On Monday, May 26, 2025 at 6:44:54 PM UTC+2 Wouter Hendriks wrote:
Well I needed the cookie-setter because I couldn't find a way to get the logged in user (ID) in my TS RPC:

Wouter Hendriks

unread,
May 26, 2025, 2:50:43 PMMay 26
to General WebHare developers discussion, Arnold Hendriks, Wouter Hendriks
Ah, great, this works:

const userInfo = await getRequestUser(this.#req, pathName, mobieclickSchema);

Where this.#req is set in the constructor:

  constructor(req: router.WebRequest) {
    this.#req = req;
  }

About the pathName: would you generally advice to just always send it in the RPC request (as `location.pathname`), or use something like

const referrer = req.headers.get("referer");
const referrerPathname = referrer ? new URL(referrer).pathname : "";

Arnold Hendriks

unread,
May 26, 2025, 3:08:12 PMMay 26
to General WebHare developers discussion, Wouter Hendriks, Arnold Hendriks
I'd use WH 5.7 RPC (https://www.webhare.dev/manuals/typescript/rpcservice/) and use getOriginURL from the RPCContext, as rpc() automatically passes sufficient information to derive the origin url.

Or look at that code for what it takes to properly implement it.

Wouter Hendriks

unread,
May 30, 2025, 6:19:00 AMMay 30
to General WebHare developers discussion, Arnold Hendriks, Wouter Hendriks
Is there a way to use the LoginByID in the new auth setup? Is that something you need the `implements AuthCustomizer` class for?

I have an external (Google) login for which I currently have a HareScript RPC that runs `auth->LoginByID`, but it's currently failing with this error:

No WRD schema defined for URL http://localhost:8001/wh_services/mobieclick/rpc/LoginExternal
    at prepareLogin (/Users/wouter/projects/webhare/whtree/jssdk/auth/src/identity.ts:1412:11)
    at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
    at async prepareLoginCookies (/Users/wouter/projects/webhare/whtree/jssdk/auth/src/identity.ts:1473:26)
    at async CallJSService.invoke (/Users/wouter/projects/webhare/whtree/modules/platform/js/nodeservices/calljs.ts:15:12)
    at async WebHareService._onMessage (/Users/wouter/projects/webhare/whtree/jssdk/services/src/backendservicerunner.ts:185:22)
    at CALLJS (wh::javascript.whlib:198:25)
    at WRDAUTH#__CREATELOGINCOOKIES (mod::wrd/lib/internal/auth/legacy-api.whlib:349:14)
    at WRDAUTH#FINALIZELOGIN (mod::wrd/lib/internal/auth/legacy-api.whlib:784:29)
    at WRDAUTH#SERVICELOGINBYID (mod::wrd/lib/internal/auth/legacy-api.whlib:542:18)
    at BUILTINTRANSACTIONBASE#EXECUTEINWORK (wh::internal/transbase.whlib:643:18)
    at WRDAUTH#LOGINBYID (mod::wrd/lib/internal/auth/legacy-api.whlib:933:38)
    at WRDAUTHPLUGIN#LOGINBYID (mod::wrd/lib/internal/auth/webdesignplugin.whlib:735:57)
    at RPC_LOGINEXTERNAL (mod::mobieclick/lib/rpc.whlib:105:38)
    at DOCHECKEDVARARGSCALL (mod::system/lib/internal/remoting/support.whlib:184:23)
    at EXECUTESERVICECALL (mod::system/lib/internal/remoting/server.whlib:361:21)
    at ControlledCall.<anonymous> (http://localhost:8001/!1748600095/.wh/ea/ap/mobieclick.mobieclick/ap.mjs:3893:25)
    at Generator.next (<anonymous>)
    at fulfilled (http://localhost:8001/!1748600095/.wh/ea/ap/mobieclick.mobieclick/ap.mjs:67:24)

(Or is this something I need to fix in siteprl?)

Arnold Hendriks

unread,
May 30, 2025, 6:29:39 AMMay 30
to General WebHare developers discussion, Wouter Hendriks, Arnold Hendriks
    at WRDAUTHPLUGIN#LOGINBYID (mod::wrd/lib/internal/auth/webdesignplugin.whlib:735:57)
    at RPC_LOGINEXTERNAL (mod::mobieclick/lib/rpc.whlib:105:38)

With which URL was the webdesignplugin created/initialized here? Likely not the http://localhost:8001/wh_services/mobieclick/rpc/LoginExternal URL picked up by typescript.  

Wouter Hendriks

unread,
May 30, 2025, 7:12:21 AMMay 30
to General WebHare developers discussion, Arnold Hendriks, Wouter Hendriks

Arnold Hendriks

unread,
May 30, 2025, 7:29:18 AMMay 30
to General WebHare developers discussion, Wouter Hendriks, Arnold Hendriks
No, the question is with which URL was the WRDAUTHPLUGIN here

    at WRDAUTHPLUGIN#LOGINBYID (mod::wrd/lib/internal/auth/webdesignplugin.whlib:735:57)
    at RPC_LOGINEXTERNAL (mod::mobieclick/lib/rpc.whlib:105:38)

initialized. It doesn't appear out of thin air.

Both TS and HS have to separately resolve the request URL (and possibly the incoming webserver-id, which may be the culprit here on non-standard port numbers) to an objectid/folderid/siteid (usually LookupPublisherURL) and then use a getApplyTester... API to actually find the <wrdauth> rule. It looks like HS manages to do this, but TS doesn't, but you'll have to debug to find where this is the case

Wouter Hendriks

unread,
May 31, 2025, 6:52:02 AMMay 31
to General WebHare developers discussion, Arnold Hendriks, Wouter Hendriks
I'm afraid I don't quite understand your question, do you mean where I’m doing this?

`rpc->api->auth->LoginByID`

That 'auth' is a `webdesign->GetWRDAuthPlugin()`. If I abort on that `rpc->api->auth`, I get:

+PVT_WEBCONTEXT: OBJECT #3107648 [94] MOBIECLICKDESIGN
+ALLOW_OG_IMAGES: FALSE
+ASSETCACHEBUSTER: '1748628681'
+BASEURL: 'http://localhost:8001/wh_services/mobieclick/rpc/LoginExternal'

Is that baseurl the one you’re looking for?

Arnold Hendriks

unread,
May 31, 2025, 10:21:59 AMMay 31
to General WebHare developers discussion, Wouter Hendriks, Arnold Hendriks
What does LookupPublisherURL give for that http://localhost:8001/wh_services/mobieclick/rpc/LoginExternal address - does it a return a site? I assume not, and that baseurl is forwarded to TypeScript, and it needs a url that can be looked up to get site/file/folder info

How does your RPC get a webdesign? wh_services don't open a webdesign by themselves

Wouter Hendriks

unread,
May 31, 2025, 4:14:23 PMMay 31
to General WebHare developers discussion, Arnold Hendriks, Wouter Hendriks
Indeed, LookupPublisherURL can't find it:

 +FILE: 0
 +FOLDER: 0
 +ISPREVIEW: FALSE
 +SITE: 0
 +WEBSERVER: 2
 +__DIRECTFILE: 0'.


How does RPC get a webdesign: so, it runs `api->auth->LoginByID`. `auth` is a property in my API:

`PUBLIC PROPERTY auth(GetAuth, -);`

`GetAuth` gets the auth object using `this->webdesign->GetWRDAuthPlugin()`.

Webdesign is a property, the getter runs finds the webdesign using GetWebDesign(this->site->id).

`this->site` is also a property, the getter is doing a "OpenSiteByName".




Arnold Hendriks

unread,
Jun 3, 2025, 10:23:45 AMJun 3
to General WebHare developers discussion, Wouter Hendriks, Arnold Hendriks
oops, looks like i forgot to actually post my reply. attempt #2 - I think the problem is here in webdesign.whlib

IF(__errorrequesturl != "")
this->baseurl := __errorrequesturl;
ELSE IF(CellExists(__pageinfo, 'absolutebaseurl')) //The URL passed to GetWebDesignForURL
this->baseurl := __pageinfo.absolutebaseurl;
ELSE IF(IsRequest())
this->baseurl := GetRequestURL();
ELSE
this->baseurl := this->pvt_targetobject->url;


Does using GetWebdesignForURL instead fix it ?

Not sure if there's an easy way to backwards-compatibly fix for this, the problem is that TS needs to repeat the URL lookup and thus needs to get the actual URL (TS can't access GetRequestURL()) but simply updating this->baseurl to use the targetobject's URL would probably fix your specific use case but break unknown others.

I'd say you should want to avoid doing manual Login calls where possible in favor of just redirecting to a login page (so you don't have to deal with OIDC logins, password requirements, 2FA) but I can image that's not always desirable especially in case of a B2C App (although even then I see plenty of mobile apps just opening a webview for authentication exactly for these reasons).. The current webdesignplugin->Login API you're invoking does not support any of the new wrdauth features and will at best return a URL to navigate the user to.

We may need to set up a TS login API you could invoke with a wrdschema + languagecode (but not URL) that takes login and password - that could replace the wrdauth webdesign plugin call you're using, but it would still be difficult to support all of wrdauth's features

Wouter Hendriks

unread,
Jun 8, 2025, 6:54:45 AMJun 8
to General WebHare developers discussion, Arnold Hendriks, Wouter Hendriks
Hmm can't find out where to put GetWebdesignForURL?

But if a fix would break other things it doesn't really matter I guess. What I'm trying to do is fix a "login with Google' button. Is there an other way to do this, without LoginByID? Any 'new wrdauth features' that I could use?

Arnold Hendriks

unread,
Jun 8, 2025, 7:55:01 AMJun 8
to General WebHare developers discussion, Wouter Hendriks, Arnold Hendriks

And (almost?) all pieces to do this in the frontend should be there, expect for a nice/clean/simple way to trigger the flow - mostly because we're not sure yet what we want here

Triggering it should be possible through either startSSOLogin (custom login forms), using GenerateLoginRequest from the HS wrdauth plugin or using an authpages loginpage and passing in loginmethods like applicationportal.whlib does

Wouter Hendriks

unread,
Jun 24, 2025, 2:44:30 PMJun 24
to General WebHare developers discussion, Arnold Hendriks, Wouter Hendriks
Tried to add, but all the commands mentioned in https://www.webhare.dev/reference-next/internals/openid-connect-relying-party/ seems to have changed :-)

Got it working through the WRD interface. I've followed the steps from "To configure logging in with Google on your WebHare interface" and set the REDIRECT_URI to the correct URL (since I'm using Ngrok to expose my localhost, I've set that URL).

Works like a charm. Next challenge: getting it to work in own code.

Arnold Hendriks

unread,
Jun 25, 2025, 3:56:44 AMJun 25
to General WebHare developers discussion, Wouter Hendriks, Arnold Hendriks
On Tuesday, June 24, 2025 at 8:44:30 PM UTC+2 Wouter Hendriks wrote:
Tried to add, but all the commands mentioned in https://www.webhare.dev/reference-next/internals/openid-connect-relying-party/ seems to have changed :-)
All? Just the oidc-add one. Anyway I updated the ones I missed
 

Got it working through the WRD interface. I've followed the steps from "To configure logging in with Google on your WebHare interface" and set the REDIRECT_URI to the correct URL (since I'm using Ngrok to expose my localhost, I've set that URL).
Note that in oauth2 the callback URL doesn't need to be publicly reachable.

Arnold Hendriks

unread,
Jun 25, 2025, 6:35:46 PMJun 25
to General WebHare developers discussion, Wouter Hendriks, Arnold Hendriks
On Tuesday, June 24, 2025 at 8:44:30 PM UTC+2 Wouter Hendriks wrote:
 Next challenge: getting it to work in own code.

frontend - startSSOLogin("TAG")

backend - in theory, especially with a page protecting just itself

RECORD func := plugin->GenerateLoginRequest("oidc", "TAG", GetRequestURL());
ExecuteSubmitInstruction(func);

protecting other urls (access rules) - might need some more elegant solutions (and there's still the matter of us trying to get rid of running user Harescripts during accesschecks as its just too fragile)

Wouter Hendriks

unread,
Jun 27, 2025, 7:33:25 AMJun 27
to General WebHare developers discussion, Arnold Hendriks, Wouter Hendriks
Ok, got it working in the frontend. What I did:

- added `<import definitionfile="mod::wrd/data/wrdschemas/authschema_oidc.xml" />` to my wrdschema.xml

- added WRD entity "WRDAUTH_OIDC_CLIENT" to my WRD schema from CLI:

wh auth add-idp -s mobieclick:mobieclick --additionalscopes email,profile --metadataurl https://accounts.google.com/.well-known/openid-configuration --title "Login with Google" --loginfield email GOOGLE xxxxx.apps.googleusercontent.com GOCSPX-xxxxx

- added lookupUsername to the `implements AuthCustomizer` class:

  async lookupUsername(params: LookupUsernameParameters): Promise<number | null> {
    await whdb.beginWork();

    const externalAccount = await harescript.loadlib("mod::mobieclick/lib/api.whlib").MCEnsureExternalAccount({
      wrd_firstname: params.jwtPayload?.given_name || "",
      wrd_lastname: params.jwtPayload?.family_name || "",
      wrd_contact_email: params.jwtPayload?.email || "",
    }, { externaltype: "google" }) as { accountid: number; is_new: boolean };

    await whdb.commitWork();

    return externalAccount.accountid;
  }

- called `await startSSOLogin("GOOGLE")` (with `import { startSSOLogin } from "@webhare/frontend";`)

All seems to work fine.
Reply all
Reply to author
Forward
0 new messages