Reputation: 61
Normally for scheduled scripts I save a hash file to disk for the credentials to be used by the script as follows:
$Credential = Get-Credential [email protected]
$Credential.Password | ConvertFrom-SecureString | Set-Content "C:\admin.pwd" $Username = "[email protected]"
$Password = Get-Content "C:\admin.pwd" -ErrorAction stop | ConvertTo-SecureString
$Credential = New-Object System.Management.Automation.PSCredential($Username,$Password)
The following Oath token request works if the password element in the Body is entered in plain text, but it is not working if I use the variable $Credential.Password . Is there a way to get this to work, or secure the password otherwise?
The Error that the below token request generatedes:
Error: Invoke-RestMethod : {"error":"invalid_grant","error_description":"AADSTS50126: Error validating credentials due to invalid username or password..."error_uri":"login.microsoftonline.com/error?code=50126"}
## Request an access token
# Define AppId, secret and scope, your tenant name and endpoint URL
$AppId = 'AppIdHere'
$AppSecret = 'AppSecretHere'
$Scope = "https://outlook.office365.com/.default"
$TenantName = "Domain.onmicrosoft.com"
$Url = "https://login.microsoftonline.com/$TenantName/oauth2/v2.0/token"
# Add System.Web for urlencode
Add-Type -AssemblyName System.Web
# Create body
$Body = @{
client_id = $AppId
client_secret = $AppSecret
scope = $Scope
grant_type = 'password'
username = '[email protected]'
password = $Credential.Password
}
# Splat the parameters for Invoke-Restmethod for cleaner code
$PostSplat = @{
ContentType = 'application/x-www-form-urlencoded'
Method = 'POST'
# Create string by joining bodylist with '&'
Body = $Body
Uri = $Url
}
# Request the token for user!
$Request = Invoke-RestMethod @PostSplat
$Request.access_token
##########
===========================
Updated script based on answer from thepip3r, and Microsoft support:
Password and secret are passed in plain text on the wire, but are not exposed in the script, and have a degree of security saved as hash files
Adjusted to not save the password or secret into variables to increase security from attacks that can gain access to memory (MS support recommended)
Option to use a certificate for the Azure Registered App rather than an App Secret, to increase security on the wire
Alternative option is to use "Azure Automation", which allows to run scripts from within O365, which should be much more secure. Another possible alternative may be Azure Functions.
# One time AppID\Secret hash save to file:
## $AppCredential = Get-Credential 'AppIdHere'
## $AppCredential.Password | ConvertFrom-SecureString | Set-Content "C:\App.pwd"
# One time Admin hash save to file:
## $Credential = Get-Credential [email protected]
## $Credential.Password | ConvertFrom-SecureString | Set-Content "C:\admin.pwd"
$AppId = 'AppIdHere'
$AppS = Get-Content "C:\App.pwd" | ConvertTo-SecureString
$AppCredential = New-Object System.Management.Automation.PSCredential($AppId,$AppS)
$Username = "[email protected]"
$Password = Get-Content "C:\admin.pwd" | ConvertTo-SecureString
$Credential = New-Object System.Management.Automation.PSCredential($Username,$Password)
### Request an access token ###
$Scope = "https://outlook.office365.com/.default"
$TenantName = "usablelife.onmicrosoft.com"
$Url = "https://login.microsoftonline.com/$TenantName/oauth2/v2.0/token"
# Add System.Web for urlencode
Add-Type -AssemblyName System.Web
# Request the token!
$Request = Invoke-RestMethod -Body @{
client_id = $AppId
client_secret = $AppCredential.GetNetworkCredential().Password
scope = $Scope
grant_type = 'password'
username = $Username
password = $Credential.GetNetworkCredential().Password
} `
-ContentType 'application/x-www-form-urlencoded' `
-Method 'POST' `
-Uri "https://login.microsoftonline.com/$TenantName/oauth2/v2.0/token"
Upvotes: 0
Views: 544
Reputation: 2935
So... I'm going to provide this as an answer because I want people to understand this being an example the problem with storing a password in a file (even as a ciphertext string value) with Get-Credential.
@mbromb, this will give you a way to test whether or not the value you're retrieving is the proper value:
on your $Credential object (last line), run: $Credential.GetNetworkCredential().Password
This will be the PLAINTEXT value of whatever you put into the prompts with Get-Credential initially. So, you can verify if after getting it initially, writing it to a file, reading it back in, and converting it to a securestring object worked as intended.
To try and draw a more straight-line to this problem: if I find your 'admin.pwd' file, it's extremely trivial to produce the plaintext from it.
Caveat: You can secure this value by supplying a protected key for this encryption process using either the -Key or -SecureKey properties on the ConvertTo/From-SecureString cmdlets. Key takes a byte array (preferably cryptographically random with sufficient entropy for your needs) and SecureKey accepts a string (password) and generates the byte array from your password.
Caveat-to-the-caveat: If you're already trying to store the password to a file, password protecting the stored-password probably isn't the right answer...
Upvotes: 2