Skip to content

Instantly share code, notes, and snippets.

@kevball2
Last active March 1, 2024 18:20
Show Gist options
  • Save kevball2/697f7a1912b26a0ffaae5770df565abb to your computer and use it in GitHub Desktop.
Save kevball2/697f7a1912b26a0ffaae5770df565abb to your computer and use it in GitHub Desktop.
PowerShell B2C Application Creation
$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