Credmon() and API Basic Authentication

Started by spl, July 10, 2025, 12:15:12 PM

Previous topic - Next topic

spl

Maybe OT, but relates to Kirby's recent post about Restful queries. Answers provided were based on simple API queries, but neglected authentication. 3 types
  • Basic - user and password
  • bearer - requires oAuth
  • API Key - requires access+secret keys

Focus on the first one. My experience has been creating user/pw as base64, either passing as -auth parameter in query, or securing in Credentials.

No longer have access to APi's to test with Basic, but simple question: does the CredMon() function in latest release support storing [which I assume it does] and retrieving Creds for Basic if required for API queries.

[EDIT]
But found a url: https://httpbin.org/basic-auth/user/pass
will authenticate with user= user pw= pass. I'll try some code, but expect Tony could/will do better.

Stan - formerly stanl [ex-Pundit]

td

You can store a base-64 encoded username and password using CredMon. Keep in mind that the stored credentials can only be accessed by the user account that stored the credentials.
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

spl

Quote from: td on July 10, 2025, 01:52:12 PMYou can store a base-64 encoded username and password using CredMon. Keep in mind that the stored credentials can only be accessed by the user account that stored the credentials.

Yep.
Stan - formerly stanl [ex-Pundit]

spl

Run into an issue trying to code the auth access for versions prior to inclusion of credman() [sorry I used credmon() in subject, but it was typed that way in list of improvements]
   CredMon( command [, target [, user_type [, secret_def]]]) 
     Reads and writes credentials to the Windows Credential Manager. Returns @True, @False, or an
     array depending on the command.

Anywho... the code below will error for variable type. Not sure, since it is a COM request, that an objecttype() for credentials is required, or request should be made with .net... or something else. I'll put together the request using Credman() as well as code with my Std_Out udf [2-3 lines of PS] as  I know the results should return a valid request.
;Winbatch 2025B
;CLR - create Basic Authorization for Web Requests
;Stan Littlefield - 7/11/2025
;
;=================================================
IntControl(73,1,0,0,0)
Gosub udfs
;create base64 credential pair
user = 'user'
pw   = 'pass'
pair = user:":":pw
ObjectClrOption("useany","System")
oEncode = ObjectClrNew('System.Text.Encoding')
oCvt    = ObjectClrNew('System.Convert')
oAsc    = oEncode.ASCII
bUser   = oCvt.ToBase64String(oAsc.GetBytes(user))
bPW     = oCvt.ToBase64String(oAsc.GetBytes(pw))
bPair   = oCvt.ToBase64String(oAsc.GetBytes(pair))
;this should work for earlier versions of WB
;but for latest release: ret = CredMan('Domain', cURL, user, pw) 
Message("Request Header Auth",bPair) ;display credential

cUrl = "https://httpbin.org/basic-auth/user/pass"
request = Createobject("Msxml2.XMLHTTP.6.0")
request.Open("GET", cUrl, @False )
request.SetRequestHeader("Authorization", "Basic ":bPair) ;errors
request.Send()
reply = request.ResponseText
ObjectClose(request)
Message("Authorized?",reply)
Exit

:WBERRORHANDLER
geterror()
request=0
Terminate(@TRUE,"Error Encountered",errmsg)
;=================================================
:udfs
#DefineSubRoutine geterror()
   wberroradditionalinfo = wberrorarray[6]
   lasterr = wberrorarray[0]
   handlerline = wberrorarray[1]
   textstring = wberrorarray[5]
   linenumber = wberrorarray[8]
   errmsg = "Error: ":lasterr:@LF:textstring:@LF:"Line (":linenumber:")":@LF:wberroradditionalinfo
   Return(errmsg)
#EndSubRoutine
Return
;=================================================
Stan - formerly stanl [ex-Pundit]

td

Quote from: spl on July 11, 2025, 04:54:38 AMRun into an issue trying to code the auth access for versions prior to inclusion of credman() [sorry I used credmon() in subject, but it was typed that way in list of improvements]
   CredMon( command [, target [, user_type [, secret_def]]]) 
     Reads and writes credentials to the Windows Credential Manager. Returns @True, @False, or an
     array depending on the command.


The typo was corrected shortly after release. The documentation and the online release notes contain the correct spelling.

Thanks for the post. A little feedback is always appreciated.


You cannot use "Domain" to save credentials in this case because Windows thinks you are trying to save Windows network credentials. The target name must be either a NetBIOS or DNS domain name. If the target is compliant with that requirement, the password is encrypted and can only be read by the system and not by the user. I use the "Domain" verb to set up shares on virtual machines for testing on various versions of Windows.

From the help file:
'...if you create a "Domain" credential with a  NetBIOS or DNS domain name of the network host as the target, the secret can only be read by authentication packages like NTML or Kerberos.'

also

"The function enables you to read credentials using the "Read" command but the credential's secret is usually returned as an empty string in the output array. This is because the secret is passed to a registered cryptographic services provider for encryption and storage. The provider used is determined but the credential type and the application name or server name in the target parameter."

The function documentation will be modified to make this clearer. Sorry for any confusion.

There appears to be an intermittent bug in the function that sometimes causes the password to include the first part of the target name appended to the end of the password. It will get fixed and a new version will be released as soon as testing is completed.

Thanks for the post. Good feedback is appreciated.
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

spl

Sorry, wasn't meaning to come off as obnoxious or petty. So, credman() should propably be 'Generic', and would it really matter if sending out a credential to a group, where it might be set up as a hard-coded exe, or imported from a secure-string xml which could be timed to expire.

Regardless, the posted script fails, although in my opinion it is written correctly. Below is a Std_Out script, which I tested and works. Having been spoiled by PS after grasping how it handles .net - it will work in WB, just not pure WB.
;Basic Authentication Test with web url
;save as ..\Std_Out_BasicAuth.wbt
;should just return authenticated user as True
;might error that site is temporartily unavailable
;Stan Littlefield 7/11/2025
;==========================================================
Gosub udfs
;NOTE use :: to correctly display : 
args = $"
$string = 'user::pass'
$bytes = [System.Text.Encoding]::::UTF8.GetBytes($string)
$base64Auth = [System.Convert]::::ToBase64String($bytes)
$headers = @{'Authorization' = 'Basic ' + $base64Auth}
$r=Invoke-RestMethod -Uri https:://httpbin.org/basic-auth/user/pass -Headers $headers -Method Get
$r
$"
cmd="Powershell"
msg='Basic Auth Test'

BoxOpen("Running...",cmd:" ":args:@LF:"PLEASE WAIT...MAY TAKE SOME TIME")
TimeDelay(3)
vals = Get_stdout():@LF:"Script Completed"
Message(msg,vals)  

Exit
;==========================================================
:udfs
#DefineSubroutine Get_stdout()
ObjectClrOption("useany","System")
objInfo = ObjectClrNew("System.Diagnostics.ProcessStartInfo")
Output=""
timeOut = ObjectType("I2",5000)
objInfo.FileName = cmd
objInfo.RedirectStandardError = ObjectType("BOOL",@TRUE)
objInfo.RedirectStandardOutput = ObjectType("BOOL",@TRUE)
objInfo.UseShellExecute = ObjectType("BOOL",@FALSE)
objInfo.CreateNoWindow = ObjectType("BOOL",@TRUE)
objInfo.Arguments = args
oProcess = ObjectClrNew("System.Diagnostics.Process")
oProcess.StartInfo = objInfo
BoxShut()
oProcess.Start()
oProcess.WaitForExit(timeout)
STDOUT = oProcess.StandardOutput.ReadToEnd()
STDERR = oProcess.StandardError.ReadToEnd()
Output = Output:STDOUT:@CRLF
If STDERR<>""
   Output = Output:"STDERR:":STDERR:@CRLF
Endif
oProcess = 0
objInfo = 0

Return (Output)
#EndSubroutine
Return
;==========================================================
Stan - formerly stanl [ex-Pundit]

td

Quote from: spl on July 11, 2025, 09:47:42 AMSorry, wasn't meaning to come off as obnoxious or petty.

Didn't think you were being either. Just appreciated the feedback.

Quote from: spl on July 11, 2025, 09:47:42 AMRegardless, the posted script fails,

You're so enamored with PowerShell that you're forgetting WIL syntax and not giving it a fair chance.  <grin>

request.SetRequestHeader("Authorization", :"Basic ":bPair)

Works for me. You just need to tell WIL that "Basic" is not a variant type by placing a colon in front of it.
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

spl

Quote from: td on July 11, 2025, 01:16:26 PMYou're so enamored with PowerShell

Well, I said spoiled, not enamored. Always some special char gets in the way. Fixed and worked for me. As did request = Createobject("WinHttp.WinHttpRequest.5.1").

Guess the journey is worth the destination.
Stan - formerly stanl [ex-Pundit]

td

"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

cssyphus

QuoteThe typo was corrected shortly after release. The documentation and the online release notes contain the correct spelling.
Still spelled as credman() on `https://docs.winbatch.com/` - although searching for credmon() brings up the correct page.


QuoteI am spoiled by WinBatch.
Something about which every user of this board can heartily agree (parenthetically acknowledging that I have gratefully learned much from Stan's PS posts)

To quote Kirby: "So much goodness" !

SMF spam blocked by CleanTalk