getting authentication tokens from MSAL via PowerShell

I have a little PowerShell script that I can use to get tokens from MSAL, for an API project I maintain, and I could have sworn that I’d blogged about it at some point. But I can’t find a post mentioning it. So I guess it’s one of those things I meant to blog about, but never got around to it.

I just rewrote it for a new API project, so I thought I’d blog about that. And since I never actually blogged about the first version, I might as well include that too.

So the first API is an older .NET Framework project. In the Visual Studio solution, I have both the API and a console program that can be used to run some simple tests against it. The console program, of course, uses MSAL.NET to authenticate. (I blogged about that in 2021.) I also like to do little ad-hoc tests of the API with Fiddler, using the Composer tab. But I need to get a bearer token to do that. There are a bunch of ways to do that, but I wanted a simple PowerShell script that I could run at the command line and that would automatically save the token to the clipboard, so I could paste it into Fiddler. I also wanted the PowerShell script to read the client ID and secret (and other parameters) from the same config file that was used for the console program. The script shown below does that, reading parameters from the console program’s app.config file, and pulling the actual client ID and secret from environment variables. (All of this is, of course, to avoid storing secrets in any text files that might get accidentally checked in to source control…)

# get-auth-hdr-0.ps1
# https://gist.github.com/andyhuey/68bade6eceaff64454eaeabae2351552
# Get the auth hdr and send it to the clipboard.
# ajh 2022-08-29: rewrite to use MSAL.PS.
# ajh 2022-11-23: read secret from env vars.

#Requires -Version 5.1
#Requires -Modules @{ ModuleName="MSAL.PS"; ModuleVersion="4.0" }

# force TLS 1.2
$TLS12Protocol = [System.Net.SecurityProtocolType] 'Tls12'
[System.Net.ServicePointManager]::SecurityProtocol = $TLS12Protocol

echo $null | clip	# clear the clipboard.

# read the settings file.
$configFilePath = ".\App.config"
[xml]$configXML = Get-Content $configFilePath
$configXML.configuration.appSettings.add | foreach {
	$add = $_
	switch($add.key) {
		"ida:Authority" 		{$authority = $add.value; break}
		"xyz:ServiceResourceId"	{$svcResourceId = $add.value; break}
		"env:ClientId"			{$client_id_var = $add.value; break}
		"env:ClientSecret" 		{$client_secret_var = $add.value; break}
	}
}
if (!$client_id_var -or !$client_secret_var -or !$authority -or !$svcResourceId) {
	Write-Error "One or more settings are missing from $configFilePath."
	return
}

# and the env vars.
$client_id = [Environment]::GetEnvironmentVariable($client_id_var, 'Machine')
$client_secret = [Environment]::GetEnvironmentVariable($client_secret_var, 'Machine')
if (!$client_id -or !$client_secret) {
	Write-Error "One or more env vars are missing."
	return
}

$scope = $svcResourceId + "/.default"
$secSecret = ConvertTo-SecureString $client_secret -AsPlainText -Force

$msalToken = Get-MsalToken -ClientId $client_id -ClientSecret $secSecret -Scope $scope -Authority $authority
$authHdr = $msalToken.CreateAuthorizationHeader()
$fullAuthHdr = "Authorization: $($authHdr)"
$fullAuthHdr | clip
"auth header has been copied to the clipboard."

For my new project, I needed to create a new version of this script, since the new project is in .NET Core, using an appsettings.json file rather than the old XML format app.config file. I’m also now using the Secret Manager to store the client ID and secret.

# get-auth-hdr-1.ps1
# https://gist.github.com/andyhuey/de85972ec0f6268034e5ce46b0278a07
# Get the auth hdr and send it to the clipboard.
# ajh 2023-04-06: new. 

#Requires -Version 7
#Requires -Modules @{ ModuleName="MSAL.PS"; ModuleVersion="4.0" }

# force TLS 1.2
$TLS12Protocol = [System.Net.SecurityProtocolType] 'Tls12'
[System.Net.ServicePointManager]::SecurityProtocol = $TLS12Protocol

echo $null | clip	# clear the clipboard.

$secrets = dotnet user-secrets list --json | ConvertFrom-Json
$clientId = $secrets.'AuthConfig:ClientId'
$clientSecret = $secrets.'AuthConfig:ClientSecret'
$secSecret = ConvertTo-SecureString $clientSecret -AsPlainText -Force

$appSettings = Get-Content appsettings.json | ConvertFrom-Json
$scope = $appSettings.AuthConfig.ResourceId
$authority = $appSettings.AuthConfig.Instance -f $appSettings.AuthConfig.TenantId

$msalToken = Get-MsalToken -ClientId $clientId -ClientSecret $secSecret -Scope $scope -Authority $authority
$authHdr = $msalToken.CreateAuthorizationHeader()
$fullAuthHdr = "Authorization: $($authHdr)"
$fullAuthHdr | clip
"auth header has been copied to the clipboard."

So this one is calling “dotnet user-secrets list” to get the secrets. And it’s using “ConvertFrom-Json” for both that and the appsecrets.json file.

Both scripts are using MSAL.PS for the MSAL call.

One thing that might not be obvious in the second script is that the “Instance” value is formatted like this: “”https://login.microsoftonline.com/{0}” so we’re using the “-f” string format function to pop the tenant ID into that {0} placeholder. (I took that functionality from an online sample I found somewhere, but I may change that around, since I think it just confuses things.) Also, in the first example, I added “/.default” to the $scope variable in the script, while the new version already has that in the config file.

I’m not sure if any of this will ever be useful to anyone but me, but it seems like something that might help someone else out there on the internet somewhere, at some point.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.