[SalesForce] Is it possible to set per user Named Credentials via Apex

I need to integrate with an external system that cannot fully support OAuth 2.0 server flow, so we're looking at just doing a basic username/password flow.

So, I think I need to set up Named Credentials with an Identity Type of "Per User" and Authentication Protocol of "Password Authentication".

What I want to do is have them press a custom button in the UI that takes them to a custom Lightning Component which does the callout. I'm fine with writing that component and using Named Credentials to merge in the authentication values. But, the first time, they won't have entered their external username/password into SF yet. And the external system forces a password change every 30 days, so they will have to keep updating them in SF.

The question is, how do I then allow users to set their own username/password values? I gather that these end up being stored as an ExternalDataUserAuth but it seems like it can only be set up by getting the user to visit a standard page associated with their profile. I tried creating/updating them from anonymous Apex with something like this:

update new ExternalDataUserAuth(Id = '0XUyyyyyyyyyy',
                               Username = 'aidan',
                               Password = 'foobar');

And got the response:

DML operation Update not allowed on ExternalDataUserAuth

Really, I just want my custom component to try the callout, and if the authentication fails, get them to update the username/password on the SF side without having to leave the custom component.

This seems like such a simple thing, but my research so far has got me nowhere.

Best Answer

As Adrian pointed out, it has been established that we can do this from Apex via the Metadata API. That makes it quite a lot of work, though.

Possible to update NamedCredential from Apex?

For my purposes, this is a temporary measure while the external system sorts out doing OAuth 2.0 server flow. So, I query the ExternalDataUserAuth table:

List<ExternalDataUserAuth> existingAuths = [
        SELECT Id
        FROM ExternalDataUserAuth
        WHERE ExternalDataSource.Name = :systemName
        AND UserId = :UserInfo.getUserId()
];

If there is one, I pop up a window like this:

window.open('/' + component.get('v.externalDataUserAuthId') + '/e', 'Enter Login Details', 'width=900,height=600');

If there is no existing ExternalDataUserAuth record, you can just use ExternalDataUserAuth.getSObjectType().getDescribe().getKeyPrefix() instead of the Id to create one.

It takes a bit of user-training to explain this (and we give them information before popping up the window), but this seems like a good easy solution for now.