188

With cURL, we can pass a username with an HTTP web request as follows:

$ curl -u <your_username> https://api.github.com/user

The -u flag accepts a username for authentication, and then cURL will request the password. The cURL example is for Basic authentication with the GitHub Api.

How do we similarly pass a username and password along with Invoke-WebRequest? The ultimate goal is to user PowerShell with Basic authentication in the GitHub API.

Shaun Luttin
  • 133,272
  • 81
  • 405
  • 467
  • $pair should be `$pair = "$($user):$($pass)"` Check the approved answer. I was using the above and it gave me too much pain – Bhavjot Oct 13 '17 at 04:35
  • None of the solutions that suggest the `-Credential` approach work as the correct auth header is not generated when the request is made. – StingyJack Mar 27 '19 at 13:32
  • @Shaun Luttin - This is a question..... and answer site, not a Question Answer site. This one user would prefer to see a succinct as possible question and answers other than what worked for your particular situation, but not having to read that twice (once in the edited Question, now come QuestionAnswer, and then again in answers). If the concern was the answer that helped you would not be closest to the question, StackExchange has functionality to bring the best/accepted answer to as close as possible to question already. – user66001 May 27 '20 at 13:11
  • 1
    @user66001 Thank you for the feedback. I have moved my answer-in-question to its own answer for later reference. I think this is an improvement. – Shaun Luttin May 27 '20 at 20:50
  • In `curl` I have to use the parameter `--anyauth` in order to work with user/password. Otherwise I get a permission error. – Timo Sep 28 '21 at 09:56

9 Answers9

234

I am assuming Basic authentication here.

$cred = Get-Credential
Invoke-WebRequest -Uri 'https://whatever' -Credential $cred

You can get your credential through other means (Import-Clixml, etc.), but it does have to be a [PSCredential] object.

Edit based on comments:

GitHub is breaking RFC as they explain in the link you provided:

The API supports Basic Authentication as defined in RFC2617 with a few slight differences. The main difference is that the RFC requires unauthenticated requests to be answered with 401 Unauthorized responses. In many places, this would disclose the existence of user data. Instead, the GitHub API responds with 404 Not Found. This may cause problems for HTTP libraries that assume a 401 Unauthorized response. The solution is to manually craft the Authorization header.

Powershell's Invoke-WebRequest does to my knowledge wait for a 401 response before sending the credentials, and since GitHub never provides one, your credentials will never be sent.

Manually build the headers

Instead you'll have to create the basic auth headers yourself.

Basic authentication takes a string that consists of the username and password separated by a colon user:pass and then sends the Base64 encoded result of that.

Code like this should work:

$user = 'user'
$pass = 'pass'

$pair = "$($user):$($pass)"

$encodedCreds = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($pair))

$basicAuthValue = "Basic $encodedCreds"

$Headers = @{
    Authorization = $basicAuthValue
}

Invoke-WebRequest -Uri 'https://whatever' -Headers $Headers

You could combine some of the string concatenation but I wanted to break it out to make it clearer.

briantist
  • 45,546
  • 6
  • 82
  • 127
  • 1
    As I said it works for Basic authentication, but I don't know what kind of authentication the GitHub API uses. You could post some details about what's expected and that might help us solve the issue. – briantist Jan 15 '15 at 19:59
  • The GitHub Api uses basic authentication as follows: https://developer.github.com/v3/auth/ – Shaun Luttin Jan 15 '15 at 23:01
  • 1
    Ah, it seems GitHub is (by their own admission) not following RFC, but Powershell is. I've edited the answer with more information and a workaround. – briantist Jan 15 '15 at 23:22
  • Good for you for perusing the RFC. I admire that. – Shaun Luttin Jan 15 '15 at 23:36
  • It works. I'm surprise by how verbose this is compared to cURL. – Shaun Luttin Jan 16 '15 at 02:34
  • 1
    Yeah, if you're going to be doing a lot of these kinds of calls, I'd recommend wrapping this in a function. As I said I really broke out all the pieces for clarity, but you *could* do it all on one line (it would just be messy). – briantist Jan 16 '15 at 03:04
  • I am trying to download a file using PowerShell with Invoke-WebRequest command. I tried your method to perform basic auth however I got this error: the term '' is not recognized as the name of a cmdlet. – Aref Karimi Jan 16 '15 at 08:12
  • 1
    @Aref, you should post a new question with the code you're using. If you do so and let me know about I'll take a look. – briantist Jan 16 '15 at 14:46
  • 2
    You'll need to manually build the headers if trying to authentication against the Visual Studio Team Services REST API too – Brent Robinson Sep 12 '16 at 08:48
  • 1
    It seems that the Bitbucket API version 2.0 also needs a manually created Authorization header. – Florian Winter May 02 '17 at 12:05
  • 1
    I love how people complain that powershell is so much harder than BASH, but in reality there's things like this that are just plain cleaner because of the Object Oriented style over string manipulation – Kellen Stuart Oct 15 '20 at 21:20
67

Use this:

$root = 'REST_SERVICE_URL'
$user = "user"
$pass= "password"
$secpasswd = ConvertTo-SecureString $pass -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential($user, $secpasswd)

$result = Invoke-RestMethod $root -Credential $credential
mfralou
  • 826
  • 6
  • 5
  • For some reason the selected answer didn't work for me when using it on TFS vNext, but this one did the trick. Thanks a lot! – Tybs Nov 01 '16 at 14:39
  • The selected answer did not work for running a powershell runbook on azure to initiate a triggered job but this answer did work. – Sam Mar 04 '19 at 22:23
  • 2
    Doesn't work for me. No headers get added according to Fiddler. – NickG Feb 18 '21 at 17:15
  • I can confirm that this method works well on PowerShell Core 7.3.2 on macOS. – Van Vangor Feb 13 '23 at 14:38
37

If someone would need a one liner:

iwr -Uri 'https://api.github.com/user' -Headers @{ Authorization = "Basic "+ [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes("user:pass")) }
Karol Berezicki
  • 652
  • 1
  • 8
  • 14
11

Invoke-WebRequest follows the RFC2617 as @briantist noted, however there are some systems (e.g. JFrog Artifactory) that allow anonymous usage if the Authorization header is absent, but will respond with 401 Forbidden if the header contains invalid credentials.

This can be used to trigger the 401 Forbidden response and get -Credentials to work.

$login = Get-Credential -Message "Enter Credentials for Artifactory"

                              #Basic foo:bar
$headers = @{ Authorization = "Basic Zm9vOmJhcg==" }  

Invoke-WebRequest -Credential $login -Headers $headers -Uri "..."

This will send the invalid header the first time, which will be replaced with the valid credentials in the second request since -Credentials overrides the Authorization header.

Tested with Powershell 5.1

Leonard Brünings
  • 12,408
  • 1
  • 46
  • 66
  • Is the basic authorization header necessary if you use `credential` where user and password is given? According to your answer, `-credential` will only work if it gets a `401` from `headers` before and therefore there must be `basic foo:bar` so that it is not anonymous ? – Timo Sep 29 '21 at 07:10
  • 1
    Yes, as I've written in my answer if mixed access is allowed you need to add a invalid credentials to the `Authorization` header trigger the `401` to then get `-Credential` to work. – Leonard Brünings Sep 29 '21 at 09:50
  • So if `foo:bar` is correct, I need to manipulate it to .e.g. `foobar:bar` for the `auth header key`.. – Timo Sep 29 '21 at 11:11
  • Sure, if someone picked the very secure user and password of `foo:bar`, then you need to use something different. – Leonard Brünings Sep 29 '21 at 13:09
10

I had to do this to get it to work:

$pair = "$($user):$($pass)"
$encodedCredentials = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($Pair))
$headers = @{ Authorization = "Basic $encodedCredentials" }
Invoke-WebRequest -Uri $url -Method Get -Headers $headers -OutFile Config.html
livy111
  • 131
  • 2
  • 9
6

Here is another way using WebRequest, I hope it will work for you

$user = 'whatever'
$pass = 'whatever'
$secpasswd = ConvertTo-SecureString $pass -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential($user, $secpasswd)
$headers = @{ Authorization = "Basic Zm9vOmJhcg==" }  
Invoke-WebRequest -Credential $credential -Headers $headers -Uri "https://dc01.test.local/"
Rafael Pazos
  • 61
  • 1
  • 1
  • Just in case anyone is wondering, the `-Headers` parameter expects a dictionary or hashtable of `[string],[string]`. You can actually just define an array - e.g. `@(Authorization = 'Basic ...')` like he did here an powershell will cast it for you – Kellen Stuart Oct 15 '20 at 21:33
  • @KellenStuart do not mix `basic` with `digest` which is used in the answer with the `credential` parameter. – Timo Sep 28 '21 at 15:28
5

This is what worked for our particular situation.

Notes are from Wikipedia on Basic Auth from the Client Side. Thank you to @briantist's answer for the help!

Combine the username and password into a single string username:password

$user = "shaunluttin"
$pass = "super-strong-alpha-numeric-symbolic-long-password"
$pair = "${user}:${pass}"

Encode the string to the RFC2045-MIME variant of Base64, except not limited to 76 char/line.

$bytes = [System.Text.Encoding]::ASCII.GetBytes($pair)
$base64 = [System.Convert]::ToBase64String($bytes)

Create the Auth value as the method, a space, and then the encoded pair Method Base64String

$basicAuthValue = "Basic $base64"

Create the header Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==

$headers = @{ Authorization = $basicAuthValue }

Invoke the web-request

Invoke-WebRequest -uri "https://api.github.com/user" -Headers $headers

The PowerShell version of this is more verbose than the cURL version is. Why is that? @briantist pointed out that GitHub is breaking the RFC and PowerShell is sticking to it. Does that mean that cURL is also breaking with the standard?

Shaun Luttin
  • 133,272
  • 81
  • 405
  • 467
3

another way is to use certutil.exe save your username and password in a file e.g. in.txt as username:password

certutil -encode in.txt out.txt

Now you should be able to use auth value from out.txt

$headers = @{ Authorization = "Basic $((get-content out.txt)[1])" }
Invoke-WebRequest -Uri 'https://whatever' -Headers $Headers
mayursharma
  • 126
  • 7
3

I know this is a little off the OPs original request but I came across this while looking for a way to use Invoke-WebRequest against a site requiring basic authentication.

The difference is, I did not want to record the password in the script. Instead, I wanted to prompt the script runner for credentials for the site.

Here's how I handled it

$creds = Get-Credential

$basicCreds = [pscredential]::new($Creds.UserName,$Creds.Password)

Invoke-WebRequest -Uri $URL -Credential $basicCreds

The result is the script runner is prompted with a login dialog for the U/P then, Invoke-WebRequest is able to access the site with those credentials. This works because $Creds.Password is already an encrypted string.

I hope this helps someone looking for a similar solution to the above question but without saving the username or PW in the script

Ernest Correale
  • 451
  • 3
  • 5