Last active
March 1, 2024 18:20
-
-
Save kevball2/697f7a1912b26a0ffaae5770df565abb to your computer and use it in GitHub Desktop.
PowerShell B2C Application Creation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$B2CTenantName = '<B2cTenantName>' | |
if (Get-Module -ListAvailable -Name Microsoft.Graph) { | |
Write-Host "Module Microsoft.Graph exists." | |
} | |
else { | |
throw "Module Microsoft.Graph is not installed yet. Please install it first! Run 'Install-Module Microsoft.Graph'." | |
} | |
# On the initial connection to your tenant, you will need an admin account to approve the Admin Consent | |
# to access tenant resources. | |
Connect-MgGraph -TenantId "$($B2CTenantName).onmicrosoft.com" ` | |
-Scopes "User.ReadWrite.All", "Application.ReadWrite.All", "Directory.AccessAsUser.All", "Directory.ReadWrite.All", "TrustFrameworkKeySet.Read.All", "TrustFrameworkKeySet.ReadWrite.All" | |
function New-B2cApp { | |
param ( | |
[string] $B2cAppName, | |
#[string] $keyVaultName, | |
[string] $environment | |
) | |
$AppName = $($B2cAppName) | |
# Well-known ID for offline_access = 7427e0e9-2fba-42fe-b0c0-848c9e6a8182 | |
$offlineAccessScope = @{ Id = "7427e0e9-2fba-42fe-b0c0-848c9e6a8182"; Type = "Scope" } | |
# Well-known ID for openid = 37f7f235-527c-4136-accd-4a02d197296e | |
$openidScope = @{ Id = "37f7f235-527c-4136-accd-4a02d197296e"; Type = "Scope" } | |
# offline_access and openid scopes are tied to the same app | |
$graphRRA = New-Object -TypeName Microsoft.Graph.PowerShell.Models.MicrosoftGraphRequiredResourceAccess | |
$graphRRA.ResourceAccess = @($offlineAccessScope, $openidScope) | |
# Well-known ID, the same across all tenants | |
$graphRRA.ResourceAppId = "00000003-0000-0000-c000-000000000000" | |
$resourceAccessList = @( | |
$graphRRA | |
) | |
if(!$environment.Contains("prod")) | |
{ | |
$redirctUris = @( | |
"https://localhost/authentication/login-callback" | |
) | |
} | |
else { | |
$redirctUris = @( | |
"https://localhost/authentication/login-callback" | |
) | |
} | |
# Define the application settings | |
$b2cApp = New-Object -TypeName Microsoft.Graph.PowerShell.Models.MicrosoftGraphApplication | |
$b2cApp.DisplayName = $AppName | |
$b2cApp.SignInAudience = "AzureADMultipleOrgs" # for single tenant - "AzureADandPersonalMicrosoftAccount" | |
$b2cApp.Web.RedirectUris = $redirctUris | |
$b2cApp.Web.ImplicitGrantSettings.EnableAccessTokenIssuance = $true | |
$b2cApp.Web.ImplicitGrantSettings.EnableIdTokenIssuance = $true | |
$b2cApp.RequiredResourceAccess = $resourceAccessList | |
# Create the application | |
Write-Host "Creating $AppName application..." | |
$b2cApp = New-MgApplication -BodyParameter $b2cApp | |
Write-Host "Successfully created $AppName with applicationId $($b2cApp.AppId)" | |
#Up the application resource access | |
Update-MgApplication ` | |
-ApplicationId $b2cApp.Id ` | |
-RequiredResourceAccess $resourceAccessList ` | |
-IdentifierUris "https://$($B2CTenantName).onmicrosoft.com/$($b2cApp.AppId)" | |
# Some applications will require a service principal to do authentication. This can also be done programmatically. | |
# # Service principal for the application is not created automatically. | |
# # It's needed for admin consent etc. | |
# $mtSP = New-MgServicePrincipal -AppId $b2cApp.AppId | |
# $oauthPermissions = @("openid", "offline_access") | |
# $oauthScope = $oauthPermissions -join " " | |
# # Admin Consent - for the UI app this is OAuth2 Permission Grant | |
# Write-Host "Updating admin consent for $AppName..." | |
# New-MgOauth2PermissionGrant ` | |
# -ConsentType AllPrincipals ` | |
# -ClientId $mtSP.Id ` | |
# -Scope 'offline_access openid' ` | |
# -ResourceId $mtSP.Id ` | |
# -ExpiryTime $(Get-Date).AddYears(10) | |
# New-ServicePrincipalSecret ` | |
# -applicationId $b2cApp.Id ` | |
# -displayName $AppName ` | |
# -keyVaultName $keyVaultName ` | |
# -createPolicySecret $true | |
write-host "" | |
Write-Host "*** Azure AD B2C Application '$($b2cApp.DisplayName)' created." | |
Write-Host "*** Client ID: $($b2cApp.AppId)" | |
Write-Host "*** Object ID: $($b2cApp.Id)" | |
write-host "" | |
return @{ | |
B2cId = $b2cApp.AppId | |
} | |
} | |
# Sample JWT app to visual tokens. Requires a Key Vault to store secret value. | |
function New-JWTApp { | |
param ( | |
[string] $B2CTenantName, | |
[string] $keyVaultName # Only required the Key Vault Name, not the whole URL | |
) | |
$jwtAppName = "JWT Demo App-$B2CTenantName" | |
# Well-known ID for offline_access = 7427e0e9-2fba-42fe-b0c0-848c9e6a8182 | |
$offlineAccessScope = @{ Id = "7427e0e9-2fba-42fe-b0c0-848c9e6a8182"; Type = "Scope" } | |
# Well-known ID for openid = 37f7f235-527c-4136-accd-4a02d197296e | |
$openidScope = @{ Id = "37f7f235-527c-4136-accd-4a02d197296e"; Type = "Scope" } | |
# offline_access and openid scopes are tied to the same app | |
$graphRRA = New-Object -TypeName Microsoft.Graph.PowerShell.Models.MicrosoftGraphRequiredResourceAccess | |
$graphRRA.ResourceAccess = @($offlineAccessScope, $openidScope) | |
$graphRRA.ResourceAppId = "00000003-0000-0000-c000-000000000000" # Well-known ID, the same across all tenants | |
$resourceAccessList = @( | |
$graphRRA | |
) | |
$redirctUris = @( | |
"https://jwt.ms" | |
) | |
# Define the JWT application settings | |
$jwtApp = New-Object -TypeName Microsoft.Graph.PowerShell.Models.MicrosoftGraphApplication | |
$jwtApp.DisplayName = $jwtAppName | |
$jwtApp.SignInAudience = "AzureADandPersonalMicrosoftAccount" | |
$jwtApp.Web.RedirectUris = $redirctUris | |
$jwtApp.Web.ImplicitGrantSettings.EnableAccessTokenIssuance = $true | |
$jwtApp.Web.ImplicitGrantSettings.EnableIdTokenIssuance = $true | |
$jwtApp.RequiredResourceAccess = $resourceAccessList | |
# Create the IdentityExperienceFramework application | |
Write-Host "Creating $jwtAppName application..." | |
$jwtApp = New-MgApplication -BodyParameter $jwtApp | |
Write-Host "Successfully created $jwtAppName with applicationId $($jwtApp.AppId)" | |
#Up the application resource access | |
Update-MgApplication ` | |
-ApplicationId $jwtApp.Id ` | |
-RequiredResourceAccess $resourceAccessList ` | |
-IdentifierUris "https://$($B2CTenantName).onmicrosoft.com/$($jwtApp.AppId)" | |
# Service principal for the application is not created automatically. | |
# It's needed for admin consent etc. | |
$jwtSP = New-MgServicePrincipal -AppId $jwtApp.AppId | |
# Admin Consent - for the JWT this is OAuth2 Permission Grant | |
Write-Host "Updating admin consent for the AD MultiTenant app..." | |
New-MgOauth2PermissionGrant ` | |
-ConsentType AllPrincipals ` | |
-ClientId $jwtSP.Id ` | |
-Scope 'offline_access openid' ` | |
-ResourceId $jwtSP.Id #` | |
#-ExpiryTime $(Get-Date).AddYears(10) | |
New-ServicePrincipalSecret ` | |
-applicationId $jwtApp.Id ` | |
-displayName $jwtAppName ` | |
-keyVaultName $keyVaultName ` | |
-createPolicySecret $false ` | |
-validityInMonths 60 | |
write-host "" | |
Write-Host "*** Azure AD B2C Application '$($jwtApp.DisplayName)' created." | |
Write-Host "*** Client ID: $($jwtApp.AppId)" | |
Write-Host "*** Object ID: $($jwtApp.Id)" | |
write-host "" | |
return @{ | |
jwtId = $jwtApp.AppId | |
} | |
} | |
# Creates Secret for App Registration and stores in Key Vault. There is also an option to add the secret to a Policy Secret. | |
function New-ServicePrincipalSecret{ | |
param( | |
[Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$applicationId, | |
[Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$displayName, | |
[Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$keyVaultName, | |
[ValidateNotNullOrEmpty()][int]$validityInMonths = 24, | |
[ValidateNotNullOrEmpty()][int]$startValidityInMonths = 0, | |
[ValidateNotNullOrEmpty()][string]$createPolicySecret = $false | |
) | |
Import-Module Microsoft.Graph.Applications | |
if($displayName.Contains(" ")) | |
{ | |
$displayName = $displayName.Replace(" ", "") | |
Write-Host "Removing spaces from display name: $displayName" | |
} | |
$exp = [math]::Round((New-TimeSpan -Start (Get-Date "01/01/1970") -End (Get-Date).AddMonths($startValidityInMonths+$validityInMonths)).TotalSeconds) | |
$nbf = [math]::Round((New-TimeSpan -Start (Get-Date "01/01/1970") -End (Get-Date).AddMonths($startValidityInMonths)).TotalSeconds) | |
$params = @{ | |
PasswordCredential = @{ | |
DisplayName = $DisplayName | |
endDateTime = $exp | |
startDateTime = $nbf | |
} | |
} | |
$password = Add-MgApplicationPassword -ApplicationId $applicationId -BodyParameter $params | |
$keyId = $password.KeyId | |
$secretText = $password.SecretText | |
Write-host "Secret created for $applicationid" | |
Write-Host "KeyId: $keyId" | |
Write-verbose "Secret value has been stored in KeyVault: $keyVaultName" | |
$Expires = (Get-Date).AddMonths($startValidityInMonths+$validityInMonths) | |
$NotBefore = (Get-Date).AddMonths($startValidityInMonths) | |
$secretText = ConvertTo-SecureString $secretText -AsPlainText -Force | |
Get-AzKeyVault -VaultName $keyVaultName | |
Set-AzKeyVaultSecret -VaultName $keyVaultName -SecretValue $secretText -Name $displayName -Expires $Expires -NotBefore $NotBefore | |
If($createPolicySecret -eq $true) | |
{ | |
New-PolicyKeySetSecret -SecretText $password.SecretText -name $displayName -NotBefore $nbf -Expires $exp -use "sig" | |
} | |
} | |
function New-PolicyKeySetSecret{ | |
param( | |
[Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$secretText, | |
[Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$notBefore, | |
[Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$expires, | |
[Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$name, | |
[ValidateNotNullOrEmpty()][string]$purpose = "sig", | |
[ValidateNotNullOrEmpty()][string]$keyType = "rsa", | |
[Parameter(Mandatory)][ValidateNotNullOrEmpty()][ValidateSet("sig", "enc")][string]$use | |
) | |
$params = @{ | |
Use = $use | |
K = $secretText | |
Nbf = $notBefore | |
Exp = $expires | |
} | |
$name = $name -replace '-','' | |
Select-MgProfile -Name "beta" | |
$keyset = get-MgTrustFrameworkKeySet -TrustFrameworkKeySetId ("B2C_1A_{0}" -f $name) -ErrorAction SilentlyContinue | |
$keySetId = ("B2C_1A_{0}" -f $name) | |
Write-Debug ("New-PolicysKeySet: creating key container {0}" -f $name) | |
if($null -ne $keyset) { | |
Write-Host ("Adding key to an existing keyset {0}." -f $keysetId) | |
try { | |
Invoke-MgUploadTrustFrameworkKeySetSecret -TrustFrameworkKeySetId $keySetId -BodyParameter $params | |
Write-Host ("Successfully added new key to keyset {0}." -f $keyset.id) | |
} | |
catch { | |
{1:<#Do this if a terminating exception happens#>} | |
} | |
} | |
else { | |
$keySetId = ("B2C_1A_{0}" -f $name) | |
Write-Host ("Creating keyset {0}" -f $keySetId) | |
try { | |
New-MgTrustFrameworkKeySet -id $keySetId -ErrorAction Stop #"B2C_1A_Demo02" | |
Invoke-MgUploadTrustFrameworkKeySetSecret -TrustFrameworkKeySetId $keySetId -BodyParameter $params | |
Write-Host ("Successfully created new keyset {0}." -f $keyset.id) | |
} | |
catch { | |
#{1:<#Do this if a terminating exception happens#>} | |
Write-Host "Unable to create KeySet" | |
$error[0].Exception.Message | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment