Options for 'RunAs' in APIs

Finally exploring APIs and have an authentication related question. I’m hosting PowerShell Universal in IIS (AppPool set to Local System) and using OIDC authentication via Azure AD. Is there is a way to “execute” an API in the context of a user instead of the AppPool identity? I have a Form on a page with a button that calls an API to unlock an AD account. I’d like to execute the API call as the user of the page (e.g. $User). Is that supported/possible? I’m having a couple issues:

  1. If I set the API Authentication to ‘Disabled’, I can only get it to execute as ‘NT Authority\SYSTEM’.
  2. When Authentication is ‘Enabled’, no matter what account I use (even my own admin account), I only get 401 Unauthorized errors.

Is it possible for an API to “RunAs” another identity?

Product: PowerShell Universal
Version: 2.3.1

What command are you using to unlock the account. If something like set-aduser you can pass credentials using -Credential. This will allow you to pull the creds from a secret variable.
I am not sure if this helps you, but giving the users themself access would allow them to bypass PSU. Not sure if this would be desired.

I’m using the AD module’s Unlock-ADAccount cmdlet. Right now, the user’s have the rights to perform all these tasks directly within ADUC/PowerShell, but as we build out functionality in PSU then we may pull those permissions back. The main reason/purpose behind using their own credentials was for auditing purposes–to know who issued the command, in case it’s done incorrectly and we need to follow up with more training with that user. We accomplish this now by delegating Execute rights to the script(s) so it shows in the run history who ran it (we also capture the $User variable and log it within the script). But APIs don’t seem to have that same audit trail, though admittedly I may just be missing where that information is stored.

You can pass creds using Unlock-ADAccount -Credential and pulling from vault also. Me personally, I would have the api script write to a database with the data you want to log( like who ran it and what they ran, and any errors… ect. If you don’t have a databases you can append to a file. Then you could always use a dashboard to create a pretty front end to the logging( with the ability to have a form that you can type in a user and you can see how many times they used it and also if they got any error from it). I am not sure you can have the api run as the user( someone can correct me if i am wrong).

If you are worried about the file getting to long you can create a task to clean it up where older then date - 90 days or whatever you want

Hope this helps.

That’s a reasonable compromise, however the built-in $User variable doesn’t exist/passthrough to the API. Would you just force it to be submitted as a parameter (e.g. part of the API URL call)?

If it is not available then I would say you have to pass it. I am still newer to PSU so apologize. If you are using page in a form to present to the user, I wonder if instead of using an api but a scripts with give you better flexibility. I am going to play with it some now and see if I can get the username back.

Ok so I created a script, in the script I put return $UAJob.identity.name then tested just running the script. It kicked back my username. I then had a form that used this script. When i ran the form it output my username ( tested running the script with default and with a service account). Also the script shows in jobs like you wanted. So if you are not tied to using an api you can just have a form call the script and you get everything you want.
Let me know.

Yeah, I’m using scripts now and using this same block just modified for each script/parameter requirements:

$Credential = Get-UAVariable -Name '<VarName>' # Account to execute script (e.g. "RunAs" this user)
$Script = Get-UAScript -Name '<ScriptName.ps1>' # Name of actual script to run
$InvokeParams = @{ # Parameters for Invoke-UAScript command
    Script = $Script
    Param1 = '<Param1 Value>'
    Param2 = '<Param2 Value>'
    Credential = $Credential
Invoke-UAScript @InvokeParams | Tee-Object -Variable job | Wait-UAJob # Waits for completion and stores job info in '$Job' var
$Data = Get-UAJobPipelineOutput -Job $Job # Retrieves data from $Job to update element later

The reason I was looking at transitioning to APIs was around speed–I’ve noticed the API executes much quicker than a script that needs to spin up a separate PowerShell process to execute in that context. Again, I’m just starting with APIs and perhaps I’m trying to use them in an unintended way, or scripts may just be better given my use case/need. I just loved the idea of a 1 or 2 lines for the REST API call as opposed to having to build all the above to do the same task.

Completely understand. I am still figuring it all out.
I tried to call the api and figure out how to pass the username. tried using a hidden field and though maybe $user would work as it says that is a dashboard variable but guess it does not work on pages. Would love to figure this out as it would be useful in the future.

Something else, I have not looked at but may help you. Persistent runtime.
Endpoints - PowerShell Universal
It looks like it keeps it open so it does not need to launch a new one each time. If you try it please let me know how it works out for you :slight_smile:

Will do. I think for right now I’ll stick with Scripts and only rely on API for retrieval of information where I don’t need to track any information on the user interacting with the API.

Something I do find interesting. The global variable $username works in a text box on a page. So if I put a text box on the page and put $username I see my username. Putting $username in the default value of a field in a form does not work. Maybe a bug? If this is supposed to work then this would solve your problem. Will do some more testing

** looks like default value does not work… or maybe does not work as I would expect it to. When you put a default value in a textbox and then run the form the textbox does not have that as a value. Figured maybe it is behind the scene so sent it to a script to output and the script shows nothing was sent to it.

In our project we use APIs because we have to pass information between several networks. HTTPS offers a smart way to do this. ActiveDirectory and Exchange have their own network. And we have a server that runs Powershell Universal with APIs to execute all the actions needed on AD accounts. So we have GET, POST, PUT and DELETE APIs.
The body of the HTTP request contains the data (or the url for GET requests).
The account that runs the AD commands in the APIs is the account that runs the Powershell Universal service.
You should keep integration of PSU in IIS for data presentation and forms to users, and run a PSU service dedicated to AD operations. No need of IIS for this.