/PSAuthClient

PowerShell OAuth2.0/OpenID Connect (OIDC) Client.

Primary LanguagePowerShellMIT LicenseMIT

PSAuthClient

PSAuthClient is a flexible PowerShell OAuth2.0/OpenID Connect (OIDC) Client.


Auth client in use

Install

The module is available from PSGallery, alternatively download and place the module in '$home\Documents\WindowsPowerShell\Modules or '$env:ProgramFiles\PowerShell\Modules' manually.

Install-Module PSAuthClient -Scope:CurrentUser

Usage

See links for function documentation, usage and examples.

Function Description
Invoke-OAuth2AuthorizationEndpoint Uses WebView2 (embedded browser based on Microsoft Edge) to request authorization, this ensures support for modern web pages and capabilities like SSO, Windows Hello, FIDO key login, etc.
Invoke-OAuth2DeviceAuthorizationEndpoint Get device verification code and end-user code from the device authorization endpoint, which then can be used to request tokens from the token endpoint.
Invoke-OAuth2TokenEndpoint Build and send token exchange requests to the OAuth2.0 Token Endpoint.
Get-OidcConfigurationMetadata Retreive OpenID Connect Discovery endpoint metadata.
ConvertFrom-JsonWebToken Convert (decode) a JSON Web Token (JWT) to a PowerShell object.
Test-JsonWebTokenSignature Attempt to validate the signature of a JSON Web Token (JWT) by using the issuer discovery metadata to get the signing certificate. (If no signing certificate or secret was provided.)
New-PkceChallenge Generate code_verifier and code_challenge for PKCE (authorization code flow).
New-Oauth2JwtAssertion Create and sign JWT Assertions using either a client_certificate (x509certificate2 or RSA Private key) or client_secret (for HMAC-based signature).
Clear-WebView2Cache Removes PSAuthClient WebView2 user data folder (UDF) which is used to store browser data such as cookies, permissions and cached resources.

Examples of OpenID Connect (OIDC) and OAuth2.0 Grants

OpenID Connect is an extension of OAuth2 that adds an identity layer to the authorization framework. This allows a client to verify the identity of the user and obtain basic profile information. OIDC grants contains 'openid' scope and the identity provider will return a 'id_token' with user information (claims).

Parameters that are used (and modified) troughout the examples below.
$authorization_endpoint = "https://login.microsoftonline.com/example.org/oauth2/v2.0/authorize"
$token_endpoint = "https://login.microsoftonline.com/example.org/oauth2/v2.0/token"

$splat = @{
    client_id = "5eda97cf-2963-41e9-bea0-b6ba2bbf8f99"
    scope = "user.read openid offline_access"
    redirect_uri = "https://login.microsoftonline.com/common/oauth2/nativeclient"
    customParameters = @{ 
        prompt = "none"
    }
}


Authorization Code Grant with Proof Key for Code Exchange (PKCE)

Example

$code = Invoke-OAuth2AuthorizationEndpoint -uri $authorization_endpoint @splat

client_id                      5eda97cf-2963-41e9-bea0-b6ba2bbf8f99
code_verifier                  ig0Sly4Kdjc_e77Zsp5..PKi.TbqzSNz_CEKsamyPRI5~uRr4_
nonce                          o180HoFS2k5y0gj.spbYos.IPUS8-SqSf4cx0Z7x
redirect_uri                   https://login.microsoftonline.com/common/oauth2/nativeclient
code                           0.AUcAjvFfm8BTokWLwpwMj2CyxiGBP5hz2ZpErJuc3chlhOUNAVw.AgABAAIAAAA...

$token = Invoke-OAuth2TokenEndpoint -uri $token_endpoint @code

token_type      : Bearer
scope           : User.Read profile openid email
expires_in      : 5340
ext_expires_in  : 5340
access_token    : eyJ0eXAiOiJKV1QiLCJub25jZSI6IlhFMjJvBXRyVDBkQ1Z1cG7zbEFJQk1kU1RxLS5xQUppS3Fpbr...
refresh_token   : 0.AUcAjvFfm8BTokWLwpwMj2CyxiGBP5hz2ZpErJuc3chlhOUNAVw.AgABAAEAAAAmoFfGtYxvRrNr...
id_token        : eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImtXYmthYTZxczh3c1RuQndpaU5ZT2hIYm...
expiry_datetime : 31.01.2024 14:11:08

Authorization Code Grant

Example

$code = Invoke-OAuth2AuthorizationEndpoint -uri $authorization_endpoint @splat -usePkce:$false

nonce                          UYhqAG~GLvZqGj4hnlTkYFJY9LVcS9TrWiq.8n8Vu
redirect_uri                   https://login.microsoftonline.com/common/oauth2/nativeclient
client_id                      5eda97cf-2963-41e9-bea0-b6ba2bbf8f99
code                           0.AUcAjvFfm8BTokWLwpwMj2CyxiGBP5hz2ZpErJuc3chlhOUNAVw.AgABAAmoFfG...

$token = Invoke-OAuth2TokenEndpoint -uri $token_endpoint @code

token_type      : Bearer
scope           : User.Read profile openid email
expires_in      : 3848
ext_expires_in  : 3848
access_token    : eyJ0eXAiOiJKV1QiLCJub62jZSI6ImhDRkwxMjVHdE85SmNqS0NWMFZQLWxTd2Z0Zm12LXFsV2VDR0...
refresh_token   : 0.AUcAjvFfm8BTokWLwpwMkJCyxiGBP5hz2ZpErJuc3chlhOUNAVw.AgABAAEAAAAmoFfGtYxjHyNf...
id_token        : eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImtXYmthYTZxczh3c1RuQndpaU547ZT2hI...
expiry_datetime : 31.01.2024 14:05:18
Authorization Code Grant with Client Authentication (secret)

Example

$splat.redirect_uri = "https://localhost/web"
$code = Invoke-OAuth2AuthorizationEndpoint -uri $authorization_endpoint @splat 

client_id                      5eda97cf-2963-41e9-bea0-b6ba2bbf8f99
code_verifier                  jWe-ecfnqZ.weAxbb-qHiZ3oe7LZ-tEyWq~7UB9RcNfZn65Xq2zPO7-8rv-5tp24p...
nonce                          HRBD6BuH9PQM2_Kmuqj6KTranVVcuL80fsEpll-9nppaZp0H3CQaYhaqQ2VqUV8
redirect_uri                   https://localhost/web
code                           0.AUcAjvFfm8BTokWLwpwMj2CyxiGBP5hz2ZpErJuc3chlhOUNAVw.AgABAAIAAAm...

$token = Invoke-OAuth2TokenEndpoint -uri $token_endpoint @code -client_secret $client_secret

token_type      : Bearer
scope           : User.Read profile openid email
expires_in      : 4069
ext_expires_in  : 4069
access_token    : eyJ0eXAiOiJKG1QqLCJub25jZSI5IllOTzdpTmdXZnMtSmSSY1hpZk45bTdoa2E0WnNpWFY5ckswen...
refresh_token   : 0.AUcAjvFfmC9TokWLwpwMj2CyxiGBP5hz2ZpRrJuc3chlhOUGAVw.AgABAAEAAAAmoFfGtYxvRrNf...
id_token        : eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImtXYmthYTZxczh3c1RuQndpaU5ZT2hIYm...
expiry_datetime : 31.01.2024 14:28:58
Refresh Token Grant

Example

$token = Invoke-OAuth2TokenEndpoint -uri $token_endpoint -refresh_token $token.refresh_token -client_id $splat.client_id -scope $splat.scope -nonce $code.nonce

token_type      : Bearer
scope           : User.Read profile openid email
expires_in      : 3951
ext_expires_in  : 3951
access_token    : eyJ0eXAiOiJKR1QiLCJsf52jZSI6IjdCbkI2VDc5OGJZVlh3ZHdIRWVOMGducUVKQVBEUnBPcTZhMm...
refresh_token   : 0.AUcAjvFfm1BTokWLkjrMj3CyxiGBP5hz4ZpErJuc3chlhOUNAVw.AgABAAEAAAAmoFfGtDxvRrNa...
id_token        : eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImtXYmthYTZxczh3c1RuQndsapaU5ZT2hI...
expiry_datetime : 31.01.2024 14:16:56
Client Credentials Grant (client_secret_basic)

Example

$splat.Remove("customParameters")
$splat.scope = ".default"
$token = Invoke-OAuth2TokenEndpoint -uri $token_endpoint @splat -client_secret (Invoke-Cache -keyName "PSC_Test-ClientSecret") -client_auth_method client_secret_basic

token_type      : Bearer
expires_in      : 3599
ext_expires_in  : 3599
access_token    : eyJ0eXAiOiJKV1DiLCJub25jZSI3IjUtQjB0bXBSNHhzYWtJSW8wOFY5ejFGVGRTWDF5blZfalNVX2...
expiry_datetime : 31.01.2024 14:14:06
Client Credentials Grant (client_secret_post)
$token = Invoke-OAuth2TokenEndpoint -uri $token_endpoint @splat -client_secret (Invoke-Cache -keyName "PSC_Test-ClientSecret" -asSecureString)

token_type      : Bearer
expires_in      : 3599
ext_expires_in  : 3599
access_token    : eyJ0eXAiOiJKV1QiGCJub25jZSI3ImtIeW5MWTNyUjdja0lZd1RTQWVSRi1yRnVYYUx0Y6VaU11NEF...
expiry_datetime : 31.01.2024 14:16:10
Client Credentials Grant (client_secret_jwt)

Example

# Microsoft Graph DOES NOT support client_secret_jwt, but if they did, this is how you would do it.
$token = Invoke-OAuth2TokenEndpoint -uri $token_endpoint @splat -client_secret $client_secret -client_auth_method "client_secret_jwt"

error          error_description
-----          -----------------
invalid_client AADSTS5002723: Invalid JWT token. No certificate SHA-1 thumbprint, certificate SH...
Client Credentials Grant certificate (private_key_jwt)

Example

$token = Invoke-OAuth2TokenEndpoint -uri $token_endpoint @splat -client_certificate "Cert:\CurrentUser\My\8ade399dddc5973e04e34ac19fe8f8759ba059b8"

token_type      : Bearer
expires_in      : 3599
ext_expires_in  : 3599
access_token    : eyJ0eXAiOiJKV1QiLCJub21jZSI2InpBUjQ6UTBRc7dzYkcxOVJibQ032s2UUxrckZUcm9BYmwgdh0...
expiry_datetime : 31.01.2024 14:20:03
Implicit Grant (OAuth2.0)

Example

$splat.redirect_uri = "https://localhost/spa"
$splat.scope = "User.Read"
$token = Invoke-OAuth2AuthorizationEndpoint -uri $authorization_endpoint @splat -response_type "token" -usePkce:$false

expires_in                     4371
expiry_datetime                31.01.2024 14:39:19
scope                          User.Read profile openid email
session_state                  5c044a56-543e-4bcc-a94f-d411ddec5a87
access_token                   eyJ0eXAiOiJKV1QiLCJkj76jZSI6InlaZzBmU1NGV1M1UmllaFRHc01jMWJkSFNIZ...
token_type                     Bearer
Implicit Grant (OIDC)

Example

$token = Invoke-OAuth2AuthorizationEndpoint -uri $authorization_endpoint @splat -response_type "token id_token" -usePkce:$false

nonce                          NtKwrnSuV7xQQiya.jNXF940RQkS0OMlTcQDCOOgJay8a2qi0.MO4KKX8xc-XWUa
expires_in                     4949
id_token                       eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImtXYmthYTZxczh3c1RuQ...
expiry_datetime                31.01.2024 14:46:35
scope                          User.Read profile openid email
session_state                  5c044a56-543e-4bcc-a94f-d411ddec5a87
access_token                   eyJ0eXAiOiJKV1QiLCJub51jZSI6Ik2saWhWbkdCMzNYUnI0VTF5VUVYLXA0Zkp6K...
token_type                     Bearer
Hybrid Grant

Example

$splat.scope = "user.read openid offline_access"
$splat.redirect_uri = "http://localhost"
$splat.usePkce = $true
$token = Invoke-OAuth2AuthorizationEndpoint -uri $authorization_endpoint  @splat -response_type "code id_token"

nonce                          7B61P-.ST87WdKZ9TPF~1a5sMkPs.atxj8sBCmY2mHHfEKRotmK37dxDl
code_verifier                  w6Fvr5LTkex0k.aRJhL9rZeEDNSO5sdc8zeQYlstYJuZ2K9ck2azZ~Luxeaw2CCSd...
id_token                       eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImtXYmthYTZxczh3c1RuQ...
client_id                      5eda97cf-2963-41e9-bea0-b6ba2bbf8f99
session_state                  5c044a56-543e-4bcc-a94f-d411ddec5a87
redirect_uri                   http://localhost
code                           0.AUcAjvFfm8BTokWLwpwMj2CyxiGBP5hz2ZpErJuc3chlhOUNAVw.AgABAAAAAmo...

$token.Remove("id_token"); $token.Remove("session_state")
$tokens = Invoke-OAuth2TokenEndpoint -uri $token_endpoint @token

nonce                          da1EE3-RRVJO.fFeCEw2TvG7hK46AWFWHJCOBeRfnJ6o
code_verifier                  ~4fYq2QcXlSIZN_vZ7pnKsO5VZ0Pq39hsdQOAziqDqsGNL-JGP~
client_id                      5eda97cf-2963-41e9-bea0-b6ba2bbf8f99
redirect_uri                   http://localhost
code                           0.AUcAjvFfm8BTokWLwpwMj2CyxiGBP5hz2ZpErJuc3chlhOUNAVw.AgABAAIAAAA...
Implicit Flow (by Form_Post)

Example

$splat.redirect_uri = "http://localhost:5001/"
$customParameters = @{ 
    prompt = "none" # login, none, consent, select_account
}
$token = Invoke-OAuth2AuthorizationEndpoint -uri $authorization_endpoint  @splat -response_type "code id_token" -response_mode "form_post"

nonce                          iOJ6n7jBlYAL_TrYlFjfKwOsPklX1-4iR
code_verifier                  j1v4ZEjF4AE.lMfsQ36UzF6OoBp.zwuJ7Qkez9XQX~4lGo9pnxxtN.P4ulFhkwBaZ...
id_token                       eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImtXYmthYTZxczh3c1RuQ...
client_id                      5eda97cf-2963-41e9-bea0-b6ba2bbf8f99
session_state                  5c044a56-543e-4bcc-a94f-d411ddec5a87
redirect_uri                   http://localhost:5001/
code                           0.AUcAjvFfm8BTokWLwpwMj2CyxiGBP5hz2ZpErJuc3chlhOUNAVw.AgABAAIAmoF...


$token.Remove("id_token"); $token.Remove("session_state")
$tokens = Invoke-OAuth2TokenEndpoint -uri $token_endpoint @token

token_type      : Bearer
scope           : User.Read profile openid email
expires_in      : 4840
ext_expires_in  : 4840
access_token    : eyJ0eXAiOiJKV1QiLCJub55jZSI6IlRsTFVNS5MyaEpscDNfNzKH75GXMXI0WndKMnlKJSJzFdzJEb...
refresh_token   : 0.AUcAjvFfm8BTokSLwpwMj2CyxiGBP5kH76pErJuc3chlhOUNAVw.AgABAAEAPKIZ-AgDs_wSA9P9...
id_token        : eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImtXYmthYTZxczh3c1RuQndpaU5ZT2hIYm...
expiry_datetime : 31.01.2024 14:54:54
Device Code Grant

Example

$deviceCode = Invoke-OAuth2DeviceAuthorizationEndpoint -uri "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/devicecode" -client_id $splat.client_id -scope $splat.scope

user_code        : L8EFTXRY3
device_code      : LAQABAAEAAAAmoFfGtYxvRrNriQdPKIZ-2b64dTFbGcmRF3rSBagHQGtBcyz0K_XV8ltq-nXz8Ks6...
verification_uri : https://microsoft.com/devicelogin
expires_in       : 900
interval         : 5
message          : To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the co...

# Pop interactive browser
Invoke-WebView2 -uri "https://microsoft.com/devicelogin" -UrlCloseConditionRegex "//appverify$" -title "Device Code Flow" | Out-Null

# After user-interaction has been completed.
$token = Invoke-OAuth2TokenEndpoint -uri $token_endpoint -device_code $deviceCode.device_code -client_id $splat.client_id

token_type      : Bearer
scope           : User.Read profile openid email
expires_in      : 5320
ext_expires_in  : 5320
access_token    : eyJ0eXAiOiJKV1QiKH6Gb25jZSI5IjlzanppVWtNSlkR4WxfWjBRWFJRZUl4TEdyaDBad05TQ01sQ1...
refresh_token   : 0.AUcAjvFfm8BlORWLwpwMj2CyxiGBP5hz2ZpErkU62chlhOUNAVw.AgABAAEAAAAmoFfGtYxvRrlK...
id_token        : eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImtXYmthYTZxczh3c1RuQndpaU5ZT2hIYm...
expiry_datetime : 31.01.2024 15:07:19

Resource Owner Password Flow (ROPC) no thanks, tom hanks.

Tools

OIDC Discovery

Retreive OpenID Connect Discovery metadata.

Get-OidcDiscoveryMetadata "https://login.microsoftonline.com/common"

token_endpoint                        : https://login.microsoftonline.com/common/oauth2/token
token_endpoint_auth_methods_supported : {client_secret_post, private_key_jwt, client_secret_basic}
jwks_uri                              : https://login.microsoftonline.com/common/discovery/keys
response_modes_supported              : {query, fragment, form_post}
subject_types_supported               : {pairwise}
id_token_signing_alg_values_supported : {RS256}
response_types_supported              : {code, id_token, code id_token, token id_token}
scopes_supported                      : {openid}
issuer                                : https://sts.windows.net/{tenantid}/
microsoft_multi_refresh_token         : True
authorization_endpoint                : https://login.microsoftonline.com/common/oauth2/authorize
device_authorization_endpoint         : https://login.microsoftonline.com/common/oauth2/devicecode
http_logout_supported                 : True
frontchannel_logout_supported         : True
end_session_endpoint                  : https://login.microsoftonline.com/common/oauth2/logout
claims_supported                      : {sub, iss, cloud_instance_name, cloud_instance_host_name}
check_session_iframe                  : https://login.microsoftonline.com/common/oauth2/checksession
userinfo_endpoint                     : https://login.microsoftonline.com/common/openid/userinfo
kerberos_endpoint                     : https://login.microsoftonline.com/common/kerberos
tenant_region_scope                   : 
cloud_instance_name                   : microsoftonline.com
cloud_graph_host_name                 : graph.windows.net
msgraph_host                          : graph.microsoft.com
rbac_url                              : https://pas.windows.net

Decode JWT

Convert (decode) a JSON Web Token (JWT) to a PowerShell object.

PS> ConvertFrom-JsonWebToken "ew0KICAidHlwIjogIkpXVCIsDQogICJhbGciOiAiUlMyNTYiDQp9.ew0KICAi..."

header    : @{typ=JWT; alg=RS256}
exp       : 1706784929
echo      : Hello World!
nbf       : 1706784629
sub       : PSAuthClient
iss       : https://example.org
jti       : 27913c80-40d1-46a3-89d5-d3fb9f0d1e4e
iat       : 1706784629
aud       : PSAuthClient
signature : OHIxRGxuaXVLTjh4eXhRZ0VWYmZ3SHNlQ29iOUFBUVRMK1dqWUpWMEVXMD0


Validate JWT Signature

Attempt to validate the signature of a JSON Web Token (JWT) by using the issuer discovery metadata to get the signing certificate. (If no signing certificate or secret was provided.)

PS> Test-JsonWebTokenSignature -jwtInput $jwt
True
Build JWT Assertions

Create and sign JWT Assertions using either a client_certificate (x509certificate2 or RSA Private key) or client_secret (for HMAC-based signature).

PS> New-Oauth2JwtAssertion -issuer "test" -subject "test1" -audience "test2" -jwtId "123" -customClaims @{ claim1 = "test" } -client_secret "secret"

client_assertion_jwt           ew0KICAiYWxnIjogIlJTMjU2IiwNCiAgInR5cCI6ICJKV1QiDQp9.ew0KICAianRp...
client_assertion_type          urn:ietf:params:oauth:client-assertion-type:jwt-bearer
header                         @{alg=RS256; typ=JWT}
payload                        @{jti=123; claim1=test; aud=test2; exp=1706793151; nbf=170679285...}
Generate a PKCE Challenge

Generate code_verifier and code_challenge for PKCE (authorization code flow).

PS> New-PkceChallenge

code_verifier                  Vpq2YXOsD~1DRM-jBPR6bt8R-3dWQAHNLVLUIDxh7SkWpOT3A0grpenqKne5rAHcVKsTi-ya8-lGBxJ0NS7zavdcFbfdN0yFQ5kYOFbWBh3
code_challenge                 TW-3r-6mxRWjhkkxmYOabLlwIQ0JkQ0ndxzOSLJvCoU
code_challenge_method          S256