PLC Tag AD Authentication

Hello I am trying to figure out if there is a way I can have a username and password entered in a PLC and have Ignition authenticate the user using an already configured active directory. I know this is not the ideal way to do things just looking for some temporary solutions.

I would think that's possible. One thing you'll have to be careful with is that each client would need to point to a unique set of PLC tags (unless you will only have a single client, then that's easier). But essentially, you'd need to trigger a login using a PLC tag that's set up with a Client Tag Change Script. In that client tag change script, you'd then read the values of the username and password entries to automatically log the new user in using system.security.switchUser.
(I believe this only works in Vision - I don't think there's a way to handle this with Perspective since perspective uses Identity Providers instead of the classic login.)

This looks along the right path but what I am really looking to do is write a value to the plc based on whether the username and password are correct. For example if I enter a valid combination then a 1 would be written to the PLC tag or a specific value based on what AD group the user is a part of.

You'll probably want to use this instead so that it doesn't actually log the user into the Ignition client, but instead just validates the login is correct.

Although, this won't check roles, but only validates the login (user/password) are correct. I don't know if there's a way to validate roles without actually logging in the user, but there might be an undocumented way of doing so.

Can this be used in tag script? This is what I entered into the script but does not seem to work.

def valueChanged(tag, tagPath, previousValue, currentValue, initialChange, missedEvents):
	CV = currentValue.value
	currentUser = "[WTA]Authentication/Username/STRING"
	password = "[WTA]Authentication/Password/STRING"
	if CV == 1:
		valid = system.security.validateUser(currentUser, password)
		if valid:
	   		system.tag.write("[WTA]Authentication/Authentication_Level", 17)
		else:
	   		system.tag.write("[WTA]Authentication/Authentication_Level", 99)

Regarding your question, are you proposing to hold a username and plain-text password in the PLC?
This seems very insecure, as anyone with the ability to view the PLC status could read it and have the ability to log in to any company systems that AD gives them rights for. I doubt you'll win Cyber Security Team recognition award - or not the good kind.

2 Likes

While I agree with @Transistor on this from a cybersecurity standpoint, I'm guessing you're trying to workaround some other system that you need some sort of authentication with AD that's otherwise not possible. Ideally if you were only using Ignition, you could use the currently logged in user in Ignition to send a value to the PLC for the authentication level, but that's between you and your cybersecurity team (if you have one).

Here's an updated script. You need to read the values of your currentUser and currentPass (you can't just use the string values as they'll try to use that literal tag as the username and password). Then you can use writeAsync to update your PLC (system.tag.write is a deprecated function unless you're using an older version of Ignition like 7.9).

def valueChanged(tag, tagPath, previousValue, currentValue, initialChange, missedEvents):
	CV = currentValue.value
	currentUser = "[WTA]Authentication/Username/STRING"
	currentPass = "[WTA]Authentication/Password/STRING"
	username, password = [tag.value for tag in system.tag.readBlocking([currentUser, currentPass])]
	if CV == 1:
		valid = system.security.validateUser(username, password, 'default')
		if valid:
			system.tag.writeAsync(["[WTA]Authentication/Authentication_Level"], [17])
		else:
			system.tag.writeAsync(["[WTA]Authentication/Authentication_Level"], [99])

Edit: Added the 'default' user source as an example for the validateUser function.

Yes I agree with @Transistor as well but these are the situations we get in sometimes.

I've added the below script as suggested but I still do not get any results.

def valueChanged(tag, tagPath, previousValue, currentValue, initialChange, missedEvents):
	CV = currentValue.value
	currentUser = system.tag.readAsync("[.]Username").value
	currentPass = system.tag.readAsync("[.]Password").value
	username, password = [tag.value for tag in system.tag.readBlocking([currentUser, currentPass])]
	if CV == 1:
		valid = system.security.validateUser(username, password)
		if valid:
			system.tag.writeAsync(["[.]Authentication_Level"], [17])
		else:
			system.tag.writeAsync(["[.]Authentication_Level"], [99])

The manual suggests using a authProfile as an optional argument for gateway scope scripting (system.security.validateUser | Ignition User Manual).

Since you're executing from a Tag Script, there is no associated project, so I would try to specify an authProfile and see what happens.

I wondered that as well but was unsure what the authProfile would be. Is the name found on the gateway as the System Identity Provider?

It would be the name of the user source that you're authenticating against.

Example using the default internal user source:
valid = system.security.validateUser(username, password, 'default')

This did not work either. Thanks for the input, I'll keep trying.

Well, it does work. I can confirm it does. You need to revisit your earlier code because there are fundamental issues with it. Specifically,

username, password = [tag.value for tag in system.tag.readBlocking([currentUser, currentPass])]

readBlocking accept a list of tagPaths, but you are passing a list of tag values (currentUser and currentPass)

This may work better for you:

username, password = [tag.value for tag in system.tag.readBlocking(["[.]Username", "[.]Password"])]

As @code_skin pointed out, since you didn't copy my code and made changes to it on your own, you converted the currentUser and currentPass to actual values vs I was modifying what you gave me and passed the tag paths into the readBlocking function. (You changed those lines to read the tags individually, which isn't a big deal on something small like this, but typically you'd read them in one call rather than 2 separate calls.) When doing it that way, you don't need the extra line I was using then to read those values.

My mistake, it is working now. Thank you both for your help, greatly appreciated.