diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 09b9142..f50a58f 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -6,33 +6,58 @@
"image": "mcr.microsoft.com/devcontainers/dotnet:1-8.0-bookworm",
// Features to add to the dev container. More info: https://containers.dev/features.
- // "features": {},
-
- // Use 'forwardPorts' to make a list of ports inside the container available locally.
- // "forwardPorts": [5000, 5001],
- // "portsAttributes": {
- // "5001": {
- // "protocol": "https"
- // }
- // }
+ "features": {
+ //Az CLI install
+ "azure-cli": {
+ "version": "latest"
+ },
+ //Terraform + tflint install
+ "ghcr.io/devcontainers/features/terraform:latest": {
+ "version": "latest",
+ "tflint": "latest"
+ },
+ // Azure Function Core tools + Visual Studio Extension install
+ "ghcr.io/jlaundry/devcontainer-features/azure-functions-core-tools:1": {},
+ // Node JS :
+ "ghcr.io/devcontainers/features/node:1": {
+ "version": "20",
+ "nvmVersion": "latest",
+ "installYarn": true
+ }
+ },
+ "forwardPorts": [
+ 7071,
+ 10000,
+ 10001,
+ 10002
+ ],
// Use 'postCreateCommand' to run commands after the container is created.
- // "postCreateCommand": "dotnet restore",
+ "postCreateCommand": "curl -sSL https://ngrok-agent.s3.amazonaws.com/ngrok.asc | sudo tee /etc/apt/trusted.gpg.d/ngrok.asc >/dev/null && echo 'deb https://ngrok-agent.s3.amazonaws.com buster main' | sudo tee /etc/apt/sources.list.d/ngrok.list && sudo apt update && sudo apt install ngrok",
// Configure tool-specific properties.
"customizations": {
"vscode": {
"extensions": [
+ // Azurite : Storage Account Emulator for local Azure Function Debug
+ "azurite.azurite",
+ "humao.rest-client",
+ // Terraform
+ "hashicorp.terraform",
+
+ // Language Specifics
"ms-dotnettools.csharp",
"ms-dotnettools.vscodeintellicode-csharp",
"ms-dotnettools.csdevkit",
"ms-azuretools.ms-entra",
"ms-vscode.vscode-node-azure-pack",
- "ms-vscode.azure-account"
+ "ms-vscode.azure-account",
+ "devondcarew.bazel-code",
+ "ms-vscode.vscode-typescript-tslint-plugin"
]
}
},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
- // "remoteUser": "root"
+ "remoteUser": "root"
}
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
deleted file mode 100644
index 0d009b0..0000000
--- a/.vscode/launch.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "configurations": [
- {
- "name": ".NET Core Launch (console)",
- "type": "coreclr",
- "request": "launch",
- "preLaunchTask": "build",
- "program": "bin/Debug/net8.0/Cli.dll",
- "args": [],
- "cwd": "${workspaceFolder}/src/MSAL_Example",
- "stopAtEntry": false,
- "console": "internalConsole"
- }
- ]
-}
\ No newline at end of file
diff --git a/.vscode/projects.code-workspace b/.vscode/projects.code-workspace
new file mode 100644
index 0000000..88d125e
--- /dev/null
+++ b/.vscode/projects.code-workspace
@@ -0,0 +1,21 @@
+{
+ "folders": [
+ {
+ "path": ".."
+ },
+ {
+ "path": "../src/MSAL_Example"
+ },
+ {
+ "path": "../src/Backend"
+ },
+ {
+ "path": "../src/SPA_JS_Vanilla/App",
+ "name": "SPA_JS_Vanilla"
+ },
+ {
+ "path": "../src/WIP/AzFunc/BlobTrigger"
+ },
+ ],
+ "settings": {}
+}
\ No newline at end of file
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
deleted file mode 100644
index af88786..0000000
--- a/.vscode/tasks.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "version": "2.0.0",
- "tasks": [
- {
- "label": "build",
- "command": "dotnet",
- "type": "process",
- "args": [
- "build",
- "${workspaceFolder}/src/MSAL_Example/Cli.csproj"
- ],
- "problemMatcher": "$msCompile"
- }
- ]
-}
\ No newline at end of file
diff --git a/README.md b/README.md
index ccf5208..d1823dc 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,17 @@
# Serverless Event Driven Architecture Example
+# Configure Custom Attributes :
+
+
# Resources :
+
- [Azure Data Table SDK Reference](https://azuresdkdocs.blob.core.windows.net/$web/dotnet/Azure.Data.Tables/12.4.0/index.html#:~:text=The%20set%20of%20existing%20Azure%20tables%20can%20be,cref%3D%22Pageable%22%3E%20in%20order%20to%20access%20queried%20tables.%20Console.WriteLine%28table.Name%29%3B)
+- [Define Custom Attributes](https://learn.microsoft.com/en-us/entra/external-id/customers/how-to-define-custom-attributes#create-custom-user-attributes)
+- [Custom Attributes in Token](https://learn.microsoft.com/en-us/entra/external-id/customers/how-to-add-attributes-to-token)
+- [Configure Group Claims](https://learn.microsoft.com/en-us/entra/identity/hybrid/connect/how-to-connect-fed-group-claims)
+- [Graph Application Extension Attributes Test](https://mzansibytes.com/2022/01/08/working-with-extension-attributes-using-microsoft-graph/)
+- [Custom Claims Article](https://techcommunity.microsoft.com/blog/appsonazureblog/basic-understanding-on-microsoft-entra-custom-claims-provider/4125866)
+
+# Disable Sign-up :
+- [(Preview) MSGraph Disable Sign-Up process](https://github.com/MathiasMSFT/MyWiki/blob/main/Entra%20External%20ID/Disable-SignUp.md)
+- [Custom Authentication Extension Overview](https://learn.microsoft.com/en-us/entra/identity-platform/custom-extension-overview)
\ No newline at end of file
diff --git a/Resources/sssu_sample/.gitignore b/Resources/sssu_sample/.gitignore
new file mode 100644
index 0000000..615324a
--- /dev/null
+++ b/Resources/sssu_sample/.gitignore
@@ -0,0 +1,354 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+*.ds_store
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Local settings
+local.settings.json
\ No newline at end of file
diff --git a/Resources/sssu_sample/.vscode/extensions.json b/Resources/sssu_sample/.vscode/extensions.json
new file mode 100644
index 0000000..de991f4
--- /dev/null
+++ b/Resources/sssu_sample/.vscode/extensions.json
@@ -0,0 +1,6 @@
+{
+ "recommendations": [
+ "ms-azuretools.vscode-azurefunctions",
+ "ms-dotnettools.csharp"
+ ]
+}
diff --git a/Resources/sssu_sample/.vscode/launch.json b/Resources/sssu_sample/.vscode/launch.json
new file mode 100644
index 0000000..894cbe6
--- /dev/null
+++ b/Resources/sssu_sample/.vscode/launch.json
@@ -0,0 +1,11 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Attach to .NET Functions",
+ "type": "coreclr",
+ "request": "attach",
+ "processId": "${command:azureFunctions.pickProcess}"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Resources/sssu_sample/.vscode/settings.json b/Resources/sssu_sample/.vscode/settings.json
new file mode 100644
index 0000000..9977b0e
--- /dev/null
+++ b/Resources/sssu_sample/.vscode/settings.json
@@ -0,0 +1,7 @@
+{
+ "azureFunctions.deploySubpath": "bin/Release/netcoreapp3.1/publish",
+ "azureFunctions.projectLanguage": "C#",
+ "azureFunctions.projectRuntime": "~3",
+ "debug.internalConsoleOptions": "neverOpen",
+ "azureFunctions.preDeployTask": "publish"
+}
\ No newline at end of file
diff --git a/Resources/sssu_sample/.vscode/tasks.json b/Resources/sssu_sample/.vscode/tasks.json
new file mode 100644
index 0000000..5b873bd
--- /dev/null
+++ b/Resources/sssu_sample/.vscode/tasks.json
@@ -0,0 +1,69 @@
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "clean",
+ "command": "dotnet",
+ "args": [
+ "clean",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary"
+ ],
+ "type": "process",
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "build",
+ "command": "dotnet",
+ "args": [
+ "build",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary"
+ ],
+ "type": "process",
+ "dependsOn": "clean",
+ "group": {
+ "kind": "build",
+ "isDefault": true
+ },
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "clean release",
+ "command": "dotnet",
+ "args": [
+ "clean",
+ "--configuration",
+ "Release",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary"
+ ],
+ "type": "process",
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "publish",
+ "command": "dotnet",
+ "args": [
+ "publish",
+ "--configuration",
+ "Release",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary"
+ ],
+ "type": "process",
+ "dependsOn": "clean release",
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "type": "func",
+ "dependsOn": "build",
+ "options": {
+ "cwd": "${workspaceFolder}/bin/Debug/netcoreapp3.1"
+ },
+ "command": "host start",
+ "isBackground": true,
+ "problemMatcher": "$func-watch"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Resources/sssu_sample/CODE_OF_CONDUCT.md b/Resources/sssu_sample/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..f9ba8cf
--- /dev/null
+++ b/Resources/sssu_sample/CODE_OF_CONDUCT.md
@@ -0,0 +1,9 @@
+# Microsoft Open Source Code of Conduct
+
+This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
+
+Resources:
+
+- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
+- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
+- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns
diff --git a/Resources/sssu_sample/LICENSE b/Resources/sssu_sample/LICENSE
new file mode 100644
index 0000000..9e841e7
--- /dev/null
+++ b/Resources/sssu_sample/LICENSE
@@ -0,0 +1,21 @@
+ MIT License
+
+ Copyright (c) Microsoft Corporation.
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE
diff --git a/Resources/sssu_sample/README.md b/Resources/sssu_sample/README.md
new file mode 100644
index 0000000..c546992
--- /dev/null
+++ b/Resources/sssu_sample/README.md
@@ -0,0 +1,137 @@
+---
+page_type: sample
+languages:
+ - C#
+ - .Net core
+products:
+ - azure-active-directory
+description: "A sample to demonstrate how to validating a sign-up user flow using a C# Azure Function and API connectors"
+urlFragment: "active-directory-dotnet-external-identities-api-connector-azure-function-validate"
+---
+
+# User flow sign-up customization using C# .NET Core Azure Function and API connectors
+
+This sample demonstrates how to use API connectors to customize sign-up for Azure AD [guest user self-service sign-up](https://docs.microsoft.com/azure/active-directory/b2b/self-service-sign-up-overview) and [Azure AD B2C sign-up user flows](https://docs.microsoft.com/azure/active-directory-b2c/tutorial-create-user-flows).
+In particular, the sample demonstrates how to:
+
+1. Limit external user sign-ups to only a particular federated Azure Active Directory tenant. In this example, it's a fictitious `fabrikam.com` and `fabricam.com`.
+1. Validate a user-provided value `Display Name` against a validation rule.
+
+The API is implemented using an Azure Function HTTP trigger in C# .NET Core.
+
+## Contents
+
+| File/folder | Description |
+| --------------------------- | ------------------------------------------ |
+| [SignUpValidation.cs](SignUpValidation.cs) | Sample source code for HTTP trigger. |
+| [ResponseContent.cs](ResponseContent.cs) | HTTP response content. |
+| `.gitignore` | Define what to ignore at commit time. |
+| `CHANGELOG.md` | List of changes to the sample. |
+| `CONTRIBUTING.md` | Guidelines for contributing to the sample. |
+| `README.md` | This README file. |
+| `LICENSE` | The license for the sample. |
+
+## Key concepts
+
+API connectors provide you with a way to modify and extend sign-up flows by leveraging web APIs. API connectors are available in both [guest user self-service sign up](https://docs.microsoft.com/azure/active-directory/external-identities/api-connectors-overview) and [Azure AD B2C sign-up user flows](https://docs.microsoft.com/azure/active-directory-b2c/api-connectors-overview?pivots=b2c-user-flow).
+
+This examples uses an API connector to limit sign-ups to only specific email domains, fabrikam.com and fabricam.com. This is easily modifiable in `SignUpValidation.cs` and can be extended limit sign ups to any particular email domain or set of email domains. Further, the API connector in this sample is used to perform input validation on `Display Name` by ensuring a user provides a value of at least 4 characters.
+
+This sample uses an Azure Function as the web API endpoint but you can alternatively edit the `.cs` files in your preferred IDE and deploy that code in any web service. If so, environment variables used for authentication may work differently.
+
+## Prerequisites
+
+Before you get started, make sure you have the following requirements in place:
+
+- An Azure account with an active subscription. [Create an account for free](https://azure.microsoft.com/free/?ref=microsoft.com&utm_source=microsoft.com&utm_medium=docs&utm_campaign=visualstudio).
+- A [self-service sign-up user flow](https://docs.microsoft.com/azure/active-directory/b2b/self-service-sign-up-user-flow) in an Azure AD tenant or a [sign-up or sign-in user flow](https://docs.microsoft.com/azure/active-directory-b2c/tutorial-create-user-flows) in an Azure AD B2C tenant.
+- Install [.NET Core 3.1](https://dotnet.microsoft.com/download/dotnet-core/3.1). The ASP.NET Core Runtime enables you to run existing web/server applications.
+- Install [Visual Studio code](https://code.visualstudio.com). A free source-code editor made by Microsoft for Windows, Linux and macOS. Features include support for debugging, syntax highlighting, intelligent code completion, snippets, code refactoring, and embedded Git.
+- The [Azure Functions extension](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions) for Visual Studio Code.
+
+## Setting up your Azure Function
+
+### Steps to run locally
+
+1. Clone the repository
+
+```console
+git clone https://github.com/Azure-Samples/active-directory-dotnet-external-identities-api-connector-azure-function-validate
+```
+
+1. Navigate to the **Azure extension** in Visual Studio code on the left navigation bar. You should see a 'Local Project' folder representing your local Azure Function.
+1. Press **F5** (or use the **Debug > Start Debugging** menu command) to launch the debugger and attach to the Azure Functions host. (This command automatically uses the single debug configuration that Azure Functions created.)
+1. The Azure Function extension will automatically generate a few files for local development, install dependencies, and install the Function Core tools if not already present. These tools help with the debugging experience.
+1. Output from the Functions Core tools appears in the VS Code **Terminal** panel. Once the host has started, **Alt+click** the local URL shown in the output to open the browser and run the function. You can also see the url of the Local Function by right clicking on the function on the Azure Functions explorer.
+1. To redeploy the local instance during testing, just repeat these steps.
+
+### Add authentication
+
+Authentication is stored in environment variables, so they're not stored as part of the repository and should never be stored in checked in code. Read more about the [local.settings.json](https://docs.microsoft.com/azure/azure-functions/functions-run-local?tabs=macos%2Ccsharp%2Cbash#local-settings-file) file.
+
+1. Create a [local.settings.json](local.settings.json) file
+1. Add the `BASIC_AUTH_USERNAME` and the `BASIC_AUTH_PASSWORD` setting.
+1. You final local.settings.json should look like following one:
+
+```json
+{
+ "IsEncrypted": false,
+ "Values": {
+ "AzureWebJobsStorage": "",
+ "FUNCTIONS_WORKER_RUNTIME": "dotnet",
+ "BASIC_AUTH_USERNAME": "",
+ "BASIC_AUTH_PASSWORD": ""
+ }
+}
+```
+
+Specify a **Username** and **Password**. This will be what your Azure Function uses to authenticate incoming requests.
+
+### Deploy the application
+
+1. Follow steps of [this](https://docs.microsoft.com/azure/azure-functions/functions-develop-vs-code?tabs=csharp#publish-to-azure) guide to deploy your Azure Function to the cloud. Copy the endpoint web URL of your Azure Function.
+1. Once deployed, you'll see a **'Upload settings'** option. Select this. It will upload your environment variables onto the [Application settings](https://docs.microsoft.com/azure/azure-functions/functions-develop-vs-code?tabs=csharp#application-settings-in-azure) of the cloud.
+
+To learn more about Visual Studio Code development for Azure Functions, see [this](https://docs.microsoft.com/azure/azure-functions/functions-dotnet-class-library).
+
+## Configure and enable the API connector
+
+Follow the steps outlined in "Add an API connector" for [guest user self-service sign-up](https://docs.microsoft.com/azure/active-directory/external-identities/self-service-sign-up-add-api-connector) or for [Azure AD B2C](https://docs.microsoft.com/azure/active-directory-b2c/add-api-connector) to create an API connector and enable it your user flow. The end result is shown below.
+
+### API connector configuration
+
+Your API connector configuration should look like the following:
+
+
+
+- **Endpoint URL** is the Function URL you copied earlier.
+- **Username** and **Password** are the Username and Passwords you defined as environment variables earlier.
+
+### Enable the API connector
+
+In the **API connector** settings for your user flow, you can select the API connector to be invoked at either step:
+
+
+
+- **After signing in with an identity provider** - if enabled for this step, the API connector will only allow users with an email ending in `@fabrikam.com`. Note that for Azure AD B2C, this does not apply to local accounts.
+- **Before creating the user** - if enabled for this step, the API connector will only allow users with an email ending in `@fabrikam.com` _and_ check whether the `Display Name` attribute is of at least length 5. Note, the `Display Name` has to be selected in **User attributes** for the user flow.
+
+## Customizing the Azure Function
+
+This sample provides a quick way to get started using API connectors. By modifying the source code and leveraging all the capabilities of a web API you're used to, you'll be able to accomplish many more complex scenarios including integration with other web APIs and services.
+
+## Contributing
+
+This project welcomes contributions and suggestions. Most contributions require you to agree to a
+Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
+the rights to use your contribution. For details, visit
+
+When you submit a pull request, a CLA bot will automatically determine whether you need to provide
+a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions
+provided by the bot. You will only need to do this once across all repos using our CLA.
+
+This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
+For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
+contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
diff --git a/Resources/sssu_sample/ResponseContent.cs b/Resources/sssu_sample/ResponseContent.cs
new file mode 100644
index 0000000..8374de5
--- /dev/null
+++ b/Resources/sssu_sample/ResponseContent.cs
@@ -0,0 +1,44 @@
+using Newtonsoft.Json;
+
+namespace Sample.ExternalIdentities
+{
+ public class ResponseContent
+ {
+ public const string ApiVersion = "1.0.0";
+
+ public ResponseContent()
+ {
+ this.version = ResponseContent.ApiVersion;
+ this.action = "Continue";
+ }
+
+ public ResponseContent(string action, string userMessage)
+ {
+ this.version = ResponseContent.ApiVersion;
+ this.action = action;
+ this.userMessage = userMessage;
+ if (action == "ValidationError")
+ {
+ this.status = "400";
+ }
+ }
+
+ public string version { get; }
+ public string action { get; set; }
+
+
+ [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
+ public string userMessage { get; set; }
+
+
+ [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
+ public string status { get; set; }
+
+
+ [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
+ public string jobTitle { get; set; }
+
+ //[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
+ //public string extension_CustomClaim { get; set; }
+ }
+}
diff --git a/Resources/sssu_sample/SECURITY.md b/Resources/sssu_sample/SECURITY.md
new file mode 100644
index 0000000..9842a84
--- /dev/null
+++ b/Resources/sssu_sample/SECURITY.md
@@ -0,0 +1,41 @@
+
+
+## Security
+
+Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
+
+If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below.
+
+## Reporting Security Issues
+
+**Please do not report security vulnerabilities through public GitHub issues.**
+
+Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report).
+
+If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/msrc/pgp-key-msrc).
+
+You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc).
+
+Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
+
+ * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
+ * Full paths of source file(s) related to the manifestation of the issue
+ * The location of the affected source code (tag/branch/commit or direct URL)
+ * Any special configuration required to reproduce the issue
+ * Step-by-step instructions to reproduce the issue
+ * Proof-of-concept or exploit code (if possible)
+ * Impact of the issue, including how an attacker might exploit the issue
+
+This information will help us triage your report more quickly.
+
+If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs.
+
+## Preferred Languages
+
+We prefer all communications to be in English.
+
+## Policy
+
+Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/msrc/cvd).
+
+
\ No newline at end of file
diff --git a/Resources/sssu_sample/SignUpValidation.cs b/Resources/sssu_sample/SignUpValidation.cs
new file mode 100644
index 0000000..48e8efb
--- /dev/null
+++ b/Resources/sssu_sample/SignUpValidation.cs
@@ -0,0 +1,115 @@
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Azure.WebJobs;
+using Microsoft.Azure.WebJobs.Extensions.Http;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
+using System.Linq;
+
+namespace Sample.ExternalIdentities
+{
+ public static class SignUpValidation
+ {
+ [FunctionName("SignUpValidation")]
+ public static async Task Run(
+ [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
+ ILogger log)
+ {
+ // Allowed domains
+ string[] allowedDomain = { "fabrikam.com", "fabricam.com" };
+
+ // Check HTTP basic authorization
+ if (!Authorize(req, log))
+ {
+ log.LogWarning("HTTP basic authentication validation failed.");
+ return (ActionResult)new UnauthorizedResult();
+ }
+
+ // Get the request body
+ string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
+ dynamic data = JsonConvert.DeserializeObject(requestBody);
+
+ // If input data is null, show block page
+ if (data == null)
+ {
+ return (ActionResult)new OkObjectResult(new ResponseContent("ShowBlockPage", "There was a problem with your request."));
+ }
+
+ // Print out the request body
+ log.LogInformation("Request body: " + requestBody);
+
+ // Get the current user language
+ string language = (data.ui_locales == null || data.ui_locales.ToString() == "") ? "default" : data.ui_locales.ToString();
+ log.LogInformation($"Current language: {language}");
+
+ // If email claim not found, show block page. Email is required and sent by default.
+ if (data.email == null || data.email.ToString() == "" || data.email.ToString().Contains("@") == false)
+ {
+ return (ActionResult)new OkObjectResult(new ResponseContent("ShowBlockPage", "Email name is mandatory."));
+ }
+
+ // Get domain of email address
+ string domain = data.email.ToString().Split("@")[1];
+
+ // Check the domain in the allowed list
+ if (!allowedDomain.Contains(domain.ToLower()))
+ {
+ return (ActionResult)new OkObjectResult(new ResponseContent("ShowBlockPage", $"You must have an account from '{string.Join(", ", allowedDomain)}' to register as an external user for Contoso."));
+ }
+
+ // If displayName claim doesn't exist, or it is too short, show validation error message. So, user can fix the input data.
+ if (data.displayName == null || data.displayName.ToString().Length < 5)
+ {
+ return (ActionResult)new BadRequestObjectResult(new ResponseContent("ValidationError", "Please provide a Display Name with at least five characters."));
+ }
+
+ // Input validation passed successfully, return `Allow` response.
+ // TO DO: Configure the claims you want to return
+ return (ActionResult)new OkObjectResult(new ResponseContent() {
+ jobTitle = "This value return by the API Connector"//,
+ // You can also return custom claims using extension properties.
+ //extension_CustomClaim = "my custom claim response"
+ });
+ }
+
+ private static bool Authorize(HttpRequest req, ILogger log)
+ {
+ // Get the environment's credentials
+ string username = System.Environment.GetEnvironmentVariable("BASIC_AUTH_USERNAME", EnvironmentVariableTarget.Process);
+ string password = System.Environment.GetEnvironmentVariable("BASIC_AUTH_PASSWORD", EnvironmentVariableTarget.Process);
+
+ // Returns authorized if the username is empty or not exists.
+ if (string.IsNullOrEmpty(username))
+ {
+ log.LogInformation("HTTP basic authentication is not set.");
+ return true;
+ }
+
+ // Check if the HTTP Authorization header exist
+ if (!req.Headers.ContainsKey("Authorization"))
+ {
+ log.LogWarning("Missing HTTP basic authentication header.");
+ return false;
+ }
+
+ // Read the authorization header
+ var auth = req.Headers["Authorization"].ToString();
+
+ // Ensure the type of the authorization header id `Basic`
+ if (!auth.StartsWith("Basic "))
+ {
+ log.LogWarning("HTTP basic authentication header must start with 'Basic '.");
+ return false;
+ }
+
+ // Get the the HTTP basinc authorization credentials
+ var cred = System.Text.UTF8Encoding.UTF8.GetString(Convert.FromBase64String(auth.Substring(6))).Split(':');
+
+ // Evaluate the credentials and return the result
+ return (cred[0] == username && cred[1] == password);
+ }
+ }
+}
diff --git a/Resources/sssu_sample/csharp.csproj b/Resources/sssu_sample/csharp.csproj
new file mode 100644
index 0000000..2189ee7
--- /dev/null
+++ b/Resources/sssu_sample/csharp.csproj
@@ -0,0 +1,18 @@
+
+
+ netcoreapp3.1
+ v3
+
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+ Never
+
+
+
\ No newline at end of file
diff --git a/Resources/sssu_sample/host.json b/Resources/sssu_sample/host.json
new file mode 100644
index 0000000..2685784
--- /dev/null
+++ b/Resources/sssu_sample/host.json
@@ -0,0 +1,11 @@
+{
+ "version": "2.0",
+ "logging": {
+ "applicationInsights": {
+ "samplingExcludedTypes": "Request",
+ "samplingSettings": {
+ "isEnabled": true
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Resources/sssu_sample/media/api-connector-configuration.png b/Resources/sssu_sample/media/api-connector-configuration.png
new file mode 100644
index 0000000..4324e6a
Binary files /dev/null and b/Resources/sssu_sample/media/api-connector-configuration.png differ
diff --git a/Resources/sssu_sample/media/api-connector-selected.png b/Resources/sssu_sample/media/api-connector-selected.png
new file mode 100644
index 0000000..189041e
Binary files /dev/null and b/Resources/sssu_sample/media/api-connector-selected.png differ
diff --git a/Resources/sssu_sample/media/create-function-app-project.png b/Resources/sssu_sample/media/create-function-app-project.png
new file mode 100644
index 0000000..3f1d6f6
Binary files /dev/null and b/Resources/sssu_sample/media/create-function-app-project.png differ
diff --git a/assets/JWT_with_custom_Attributes.png b/assets/JWT_with_custom_Attributes.png
new file mode 100644
index 0000000..697b216
Binary files /dev/null and b/assets/JWT_with_custom_Attributes.png differ
diff --git a/ms-identity-docs-code-dotnet.sln b/ms-identity-docs-code-dotnet.sln
index e90343b..3eefe00 100644
--- a/ms-identity-docs-code-dotnet.sln
+++ b/ms-identity-docs-code-dotnet.sln
@@ -1,25 +1,30 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
-VisualStudioVersion = 17.5.002.0
+VisualStudioVersion = 17.5.2.0
MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cli", "src\MSAL_Example\Cli.csproj", "{02150C0C-C207-4DF3-8733-E239A6F6C72B}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{34473EFE-8914-4FAB-A39D-EE0315235850}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cli", "src\MSGraphUsers_Example\Cli.csproj", "{970D6801-B4F9-432D-8BD4-EDAE0FBBD33E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {02150C0C-C207-4DF3-8733-E239A6F6C72B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {02150C0C-C207-4DF3-8733-E239A6F6C72B}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {02150C0C-C207-4DF3-8733-E239A6F6C72B}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {02150C0C-C207-4DF3-8733-E239A6F6C72B}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E447D051-17A5-4726-BC3C-98FC39077032}
EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {970D6801-B4F9-432D-8BD4-EDAE0FBBD33E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {970D6801-B4F9-432D-8BD4-EDAE0FBBD33E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {970D6801-B4F9-432D-8BD4-EDAE0FBBD33E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {970D6801-B4F9-432D-8BD4-EDAE0FBBD33E}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {970D6801-B4F9-432D-8BD4-EDAE0FBBD33E} = {34473EFE-8914-4FAB-A39D-EE0315235850}
+ EndGlobalSection
EndGlobal
diff --git a/src/EasyAuthSample/.devcontainer/devcontainer.json b/src/EasyAuthSample/.devcontainer/devcontainer.json
new file mode 100644
index 0000000..a46d874
--- /dev/null
+++ b/src/EasyAuthSample/.devcontainer/devcontainer.json
@@ -0,0 +1,22 @@
+// For format details, see https://aka.ms/devcontainer.json. For config options, see the
+// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node
+{
+ "name": "Node.js & TypeScript",
+ // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
+ "image": "mcr.microsoft.com/devcontainers/typescript-node:0-18"
+
+ // Features to add to the dev container. More info: https://containers.dev/features.
+ // "features": {},
+
+ // Use 'forwardPorts' to make a list of ports inside the container available locally.
+ // "forwardPorts": [],
+
+ // Use 'postCreateCommand' to run commands after the container is created.
+ // "postCreateCommand": "yarn install",
+
+ // Configure tool-specific properties.
+ // "customizations": {},
+
+ // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
+ // "remoteUser": "root"
+}
diff --git a/src/EasyAuthSample/.github/CODE_OF_CONDUCT.md b/src/EasyAuthSample/.github/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..f9ba8cf
--- /dev/null
+++ b/src/EasyAuthSample/.github/CODE_OF_CONDUCT.md
@@ -0,0 +1,9 @@
+# Microsoft Open Source Code of Conduct
+
+This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
+
+Resources:
+
+- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
+- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
+- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns
diff --git a/src/EasyAuthSample/.github/ISSUE_TEMPLATE.md b/src/EasyAuthSample/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 0000000..15c7f60
--- /dev/null
+++ b/src/EasyAuthSample/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,33 @@
+
+> Please provide us with the following information:
+> ---------------------------------------------------------------
+
+### This issue is for a: (mark with an `x`)
+```
+- [ ] bug report -> please search issues before submitting
+- [ ] feature request
+- [ ] documentation issue or request
+- [ ] regression (a behavior that used to work and stopped in a new release)
+```
+
+### Minimal steps to reproduce
+>
+
+### Any log messages given by the failure
+>
+
+### Expected/desired behavior
+>
+
+### OS and Version?
+> Windows 7, 8 or 10. Linux (which distribution). macOS (Yosemite? El Capitan? Sierra?)
+
+### Versions
+>
+
+### Mention any other details that might be useful
+
+> ---------------------------------------------------------------
+> Thanks! We'll be in touch soon.
diff --git a/src/EasyAuthSample/.github/PULL_REQUEST_TEMPLATE.md b/src/EasyAuthSample/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..ab05e29
--- /dev/null
+++ b/src/EasyAuthSample/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,45 @@
+## Purpose
+
+* ...
+
+## Does this introduce a breaking change?
+
+```
+[ ] Yes
+[ ] No
+```
+
+## Pull Request Type
+What kind of change does this Pull Request introduce?
+
+
+```
+[ ] Bugfix
+[ ] Feature
+[ ] Code style update (formatting, local variables)
+[ ] Refactoring (no functional changes, no api changes)
+[ ] Documentation content changes
+[ ] Other... Please describe:
+```
+
+## How to Test
+* Get the code
+
+```
+git clone [repo-address]
+cd [repo-name]
+git checkout [branch-name]
+npm install
+```
+
+* Test the code
+
+```
+```
+
+## What to Check
+Verify that the following are valid
+* ...
+
+## Other Information
+
\ No newline at end of file
diff --git a/src/EasyAuthSample/.gitignore b/src/EasyAuthSample/.gitignore
new file mode 100644
index 0000000..dfcfd56
--- /dev/null
+++ b/src/EasyAuthSample/.gitignore
@@ -0,0 +1,350 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
diff --git a/src/EasyAuthSample/CHANGELOG.md b/src/EasyAuthSample/CHANGELOG.md
new file mode 100644
index 0000000..9824752
--- /dev/null
+++ b/src/EasyAuthSample/CHANGELOG.md
@@ -0,0 +1,13 @@
+## [project-title] Changelog
+
+
+# x.y.z (yyyy-mm-dd)
+
+*Features*
+* ...
+
+*Bug Fixes*
+* ...
+
+*Breaking Changes*
+* ...
diff --git a/src/EasyAuthSample/CONTRIBUTING.md b/src/EasyAuthSample/CONTRIBUTING.md
new file mode 100644
index 0000000..a9115cf
--- /dev/null
+++ b/src/EasyAuthSample/CONTRIBUTING.md
@@ -0,0 +1,76 @@
+# Contributing to [project-title]
+
+This project welcomes contributions and suggestions. Most contributions require you to agree to a
+Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
+the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
+
+When you submit a pull request, a CLA bot will automatically determine whether you need to provide
+a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions
+provided by the bot. You will only need to do this once across all repos using our CLA.
+
+This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
+For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
+contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
+
+ - [Code of Conduct](#coc)
+ - [Issues and Bugs](#issue)
+ - [Feature Requests](#feature)
+ - [Submission Guidelines](#submit)
+
+## Code of Conduct
+Help us keep this project open and inclusive. Please read and follow our [Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
+
+## Found an Issue?
+If you find a bug in the source code or a mistake in the documentation, you can help us by
+[submitting an issue](#submit-issue) to the GitHub Repository. Even better, you can
+[submit a Pull Request](#submit-pr) with a fix.
+
+## Want a Feature?
+You can *request* a new feature by [submitting an issue](#submit-issue) to the GitHub
+Repository. If you would like to *implement* a new feature, please submit an issue with
+a proposal for your work first, to be sure that we can use it.
+
+* **Small Features** can be crafted and directly [submitted as a Pull Request](#submit-pr).
+
+## Submission Guidelines
+
+### Submitting an Issue
+Before you submit an issue, search the archive, maybe your question was already answered.
+
+If your issue appears to be a bug, and hasn't been reported, open a new issue.
+Help us to maximize the effort we can spend fixing issues and adding new
+features, by not reporting duplicate issues. Providing the following information will increase the
+chances of your issue being dealt with quickly:
+
+* **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps
+* **Version** - what version is affected (e.g. 0.1.2)
+* **Motivation for or Use Case** - explain what are you trying to do and why the current behavior is a bug for you
+* **Browsers and Operating System** - is this a problem with all browsers?
+* **Reproduce the Error** - provide a live example or a unambiguous set of steps
+* **Related Issues** - has a similar issue been reported before?
+* **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be
+ causing the problem (line of code or commit)
+
+You can file new issues by providing the above information at the corresponding repository's issues link: https://github.com/[organization-name]/[repository-name]/issues/new].
+
+### Submitting a Pull Request (PR)
+Before you submit your Pull Request (PR) consider the following guidelines:
+
+* Search the repository (https://github.com/[organization-name]/[repository-name]/pulls) for an open or closed PR
+ that relates to your submission. You don't want to duplicate effort.
+
+* Make your changes in a new git fork:
+
+* Commit your changes using a descriptive commit message
+* Push your fork to GitHub:
+* In GitHub, create a pull request
+* If we suggest changes then:
+ * Make the required updates.
+ * Rebase your fork and force push to your GitHub repository (this will update your Pull Request):
+
+ ```shell
+ git rebase master -i
+ git push -f
+ ```
+
+That's it! Thank you for your contribution!
diff --git a/src/EasyAuthSample/LICENSE.md b/src/EasyAuthSample/LICENSE.md
new file mode 100644
index 0000000..7965606
--- /dev/null
+++ b/src/EasyAuthSample/LICENSE.md
@@ -0,0 +1,21 @@
+ MIT License
+
+ Copyright (c) Microsoft Corporation.
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE
\ No newline at end of file
diff --git a/src/EasyAuthSample/README.md b/src/EasyAuthSample/README.md
new file mode 100644
index 0000000..d504bb3
--- /dev/null
+++ b/src/EasyAuthSample/README.md
@@ -0,0 +1,30 @@
+---
+page_type: sample
+name: Authenticate and authorize users end-to-end in Azure App Service with JavaScript
+description: This tutorial shows how to secure two App Services (frontend and backend), passing user auth from the frontend app to the backend.
+languages:
+- javascript
+products:
+- azure-app-service
+---
+
+# Authenticate and authorize users end-to-end in Azure App Service
+
+Azure App Service provides a highly scalable, self-patching web hosting service. In addition, App Service has built-in support for [user authentication and authorization](https://learn.microsoft.com/azure/app-service/overview-authentication-authorization). This tutorial shows how to secure your apps with App Service authentication and authorization. It uses an Express.js with views frontend as an example. App Service authentication and authorization support all language runtimes, and you can learn how to apply it to your preferred language by following the tutorial.
+
+## Features
+
+In the tutorial, you learn:
+
+> [!div class="checklist"]
+> * Enable built-in authentication and authorization
+> * Secure apps against unauthenticated requests
+> * Use Azure Active Directory as the identity provider
+> * Access a remote app on behalf of the signed-in user
+> * Secure service-to-service calls with token authentication
+> * Use access tokens from server code
+> * Use access tokens from client (browser) code
+
+## Read the tutorial
+
+[Read the tutorial](https://learn.microsoft.com/azure/app-service/tutorial-auth-aad) to understand how to deploy this scenario to App Service.
\ No newline at end of file
diff --git a/src/EasyAuthSample/backend/.deployment b/src/EasyAuthSample/backend/.deployment
new file mode 100644
index 0000000..6278331
--- /dev/null
+++ b/src/EasyAuthSample/backend/.deployment
@@ -0,0 +1,2 @@
+[config]
+SCM_DO_BUILD_DURING_DEPLOYMENT=true
\ No newline at end of file
diff --git a/src/EasyAuthSample/backend/.gitignore b/src/EasyAuthSample/backend/.gitignore
new file mode 100644
index 0000000..dfcfd56
--- /dev/null
+++ b/src/EasyAuthSample/backend/.gitignore
@@ -0,0 +1,350 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
diff --git a/src/EasyAuthSample/backend/.vscode/settings.json b/src/EasyAuthSample/backend/.vscode/settings.json
new file mode 100644
index 0000000..32a2492
--- /dev/null
+++ b/src/EasyAuthSample/backend/.vscode/settings.json
@@ -0,0 +1,7 @@
+{
+ "appService.zipIgnorePattern": [
+ "node_modules{,/**}",
+ ".env",
+ ".vscode{,/**}"
+ ]
+}
\ No newline at end of file
diff --git a/src/EasyAuthSample/backend/package.json b/src/EasyAuthSample/backend/package.json
new file mode 100644
index 0000000..c1c3137
--- /dev/null
+++ b/src/EasyAuthSample/backend/package.json
@@ -0,0 +1,35 @@
+{
+ "name": "backend",
+ "version": "1.0.0",
+ "description": "JavaScript server written with Express.js and Microsoft Graph",
+ "main": "src/index.js",
+ "type": "module",
+ "scripts": {
+ "start": "node src/index.js --unhandled-rejections=warn"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/azure-samples/js-e2e-web-app-easy-auth-app-to-app.git"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "bugs": {
+ "url": "https://github.com/azure-samples/js-e2e-web-app-easy-auth-app-to-app/issues"
+ },
+ "homepage": "https://github.com/azure-samples/js-e2e-web-app-easy-auth-app-to-app#readme",
+ "devDependencies": {},
+ "dependencies": {
+ "@azure/msal-node": "^1.12.1",
+ "@microsoft/microsoft-graph-client": "^3.0.2",
+ "express": "^4.17.1",
+ "isomorphic-fetch": "^3.0.0",
+ "jwt-decode": "^3.1.2"
+ },
+ "prettier": {
+ "printWidth": 80,
+ "semi": true,
+ "singleQuote": true,
+ "trailingComma": "es5"
+ }
+}
diff --git a/src/EasyAuthSample/backend/run.cjs b/src/EasyAuthSample/backend/run.cjs
new file mode 100644
index 0000000..7437e84
--- /dev/null
+++ b/src/EasyAuthSample/backend/run.cjs
@@ -0,0 +1,2 @@
+// For more information on this file, see https://techcommunity.microsoft.com/t5/apps-on-azure-blog/supporting-es6-import-on-windows-app-service-node-js-iisnode/ba-p/3639037
+import("./src/index.js");
\ No newline at end of file
diff --git a/src/EasyAuthSample/backend/src/index.js b/src/EasyAuthSample/backend/src/index.js
new file mode 100644
index 0000000..a7f8ec9
--- /dev/null
+++ b/src/EasyAuthSample/backend/src/index.js
@@ -0,0 +1,11 @@
+import { create } from './server.js';
+
+// Listening port for App Service should be set to WEB_PORT for Linux container and PORT for Windows native
+const port = process.env.WEB_PORT || process.env.PORT || 8080;
+
+create()
+.then(app => {
+ app.listen(port, () => {
+ console.log(`backend index.js Server has started on port ${port}!`);
+ });
+}).catch(err => console.log(`backend index.js ${JSON.stringify(err)}`));
\ No newline at end of file
diff --git a/src/EasyAuthSample/backend/src/server.js b/src/EasyAuthSample/backend/src/server.js
new file mode 100644
index 0000000..885a387
--- /dev/null
+++ b/src/EasyAuthSample/backend/src/server.js
@@ -0,0 +1,112 @@
+//
+// Express.js app server
+import express from 'express';
+import 'isomorphic-fetch';
+import { sortJson, prettyJson } from './sortJson.js';
+
+
+// Uncomment for the app->app->graph tutorial
+import { getGraphProfile } from './with-graph/graph.js';
+
+//
+
+//
+export const create = async () => {
+ // Create express app
+ const app = express();
+
+ // Get root
+ app.get('/debug', async (req, res) => {
+
+ res.send(
+ prettyJson(
+ sortJson({
+ route: 'debug',
+ headers: sortJson(req.headers),
+ env: sortJson(process.env),
+ })
+ )
+ );
+ });
+
+ // Get Profile and return to client
+ app.get('/get-profile', async (req, res) => {
+
+ console.log('/get-profile requested');
+
+ try {
+
+ const profile = {
+ "displayName": "John Doe",
+
+ // return true if we have an access token
+ "withAuthentication": false
+ }
+ let profileFromGraph=false;
+ //let graphProfile={};
+
+ const bearerToken = req.headers['Authorization'] || req.headers['authorization'];
+ console.log(`backend server.js bearerToken ${!!bearerToken ? 'found' : 'not found'}`);
+
+ if (bearerToken) {
+ const accessToken = bearerToken.split(' ')[1];
+
+
+ if (!accessToken || accessToken === 'undefined' || accessToken === 'null' || accessToken.length === 0){
+ console.log(`backend server.js accessToken: 'not found'}`);
+ return res.status(401).json({ error: 'No access token found' });
+ } else {
+ console.log(`backend server.js accessToken: 'found' ${accessToken}}`);
+ profile.withAuthentication = true;
+ }
+
+ // TODO: get profile from Graph API
+ // Uncomment for the app->app->graph tutorial
+
+ // where did the profile come from
+ //profileFromGraph=true;
+
+ // get the profile from Microsoft Graph
+ //graphProfile = await getGraphProfile(accessToken);
+
+ // log the profile for debugging
+ // console.log(`profile: ${JSON.stringify(graphProfile)}`);
+ }
+
+ const dataToReturn = {
+ route: '/profile success',
+ profile: (profileFromGraph) ? { authentication: true, ...graphProfile }: {...profile},
+ headers: req.headers,
+ bearerToken,
+ env: process.env,
+ error: null,
+ }
+ console.log(`backend server.js profile: ${JSON.stringify(profile)}`)
+
+ return res.status(200).json(dataToReturn);
+
+ } catch (err) {
+ const dataToReturn = {
+ error: {
+ route: '/profile error',
+ profile: 'error',
+ server_response: err,
+ message: err.message,
+ },
+ }
+ console.log(`backend server.js err message: ${err.message}`)
+
+ // Return 200 so error displays in browser for debugging
+ // Don't do this in production
+ return res.status(200).json(dataToReturn);
+ }
+ });
+
+ // instead of 404 - just return home page
+ app.get('*', (_, res) => {
+ res.json({ status: 'unknown url request' });
+ });
+
+ return app;
+};
+//
diff --git a/src/EasyAuthSample/backend/src/sortJson.js b/src/EasyAuthSample/backend/src/sortJson.js
new file mode 100644
index 0000000..28f69c2
--- /dev/null
+++ b/src/EasyAuthSample/backend/src/sortJson.js
@@ -0,0 +1,11 @@
+export const sortJson = (json) => {
+ if (!json) return {};
+
+ if (typeof json !== 'object') return {};
+
+ return Object.entries(json).sort(Intl.Collator().compare).reduce((o, [k, v]) => (o[k] = v, o), {});
+}
+export const prettyJson = (json) => {
+ return JSON.stringify(json, null, 6) .replace(/\n( *)/g, function (match, p1) {
+ return ' ' + ' ' .repeat(p1.length); })
+}
\ No newline at end of file
diff --git a/src/EasyAuthSample/backend/src/with-graph/auth.js b/src/EasyAuthSample/backend/src/with-graph/auth.js
new file mode 100644
index 0000000..c9a1b44
--- /dev/null
+++ b/src/EasyAuthSample/backend/src/with-graph/auth.js
@@ -0,0 +1,64 @@
+// ./backend/src/auth.js
+import MSAL from '@azure/msal-node';
+import 'isomorphic-fetch';
+
+// Programmatically get tenant id from env var
+// Env var was set by Easy Auth in App Service
+// env value should look something like: https://sts.windows.net/YOUR-TENANT-ID-AS-GUID/v2.0
+export function getTenantId() {
+
+ const openIdIssuer = process.env.WEBSITE_AUTH_OPENID_ISSUER;
+ const backendAppTenantId = openIdIssuer.replace(/https:\/\/sts\.windows\.net\/(.{1,36})\/v2\.0/gm, '$1');
+
+ return backendAppTenantId;
+}
+// ./backend/src/auth.js
+// Exchange current bearerToken for Graph API token
+// Env vars were set by Easy Auth in App Service
+export async function getGraphToken(backEndAccessToken) {
+
+ const config = {
+ // MSAL configuration
+ auth: {
+ // the backend's authentication CLIENT ID
+ clientId: process.env.WEBSITE_AUTH_CLIENT_ID,
+ // the backend's authentication CLIENT SECRET
+ clientSecret: process.env.MICROSOFT_PROVIDER_AUTHENTICATION_SECRET,
+ // OAuth 2.0 authorization endpoint (v2)
+ // should be: https://login.microsoftonline.com/BACKEND-TENANT-ID
+ authority: `https://login.microsoftonline.com/${getTenantId()}`
+ },
+ // used for debugging
+ system: {
+ loggerOptions: {
+ loggerCallback(loglevel, message, containsPii) {
+ console.log(message);
+ },
+ piiLoggingEnabled: true,
+ logLevel: MSAL.LogLevel.Verbose,
+ }
+ }
+ };
+
+ const clientCredentialAuthority = new MSAL.ConfidentialClientApplication(config);
+
+ const oboRequest = {
+ oboAssertion: backEndAccessToken,
+ // this scope must already exist on the backend authentication app registration
+ // and visible in resources.azure.com backend app auth config
+ scopes: ["https://graph.microsoft.com/.default"]
+ }
+
+ // This example has Easy auth validate token in App service runtime
+ // from headers that can't be set externally
+
+ // If you aren't using App service/Easy Auth,
+ // you must validate your access token yourself
+ // before calling this code
+ try {
+ const { accessToken } = await clientCredentialAuthority.acquireTokenOnBehalfOf(oboRequest);
+ return accessToken;
+ } catch (error) {
+ console.log(`getGraphToken:error.type = ${error.type} ${error.message}`);
+ }
+}
diff --git a/src/EasyAuthSample/backend/src/with-graph/graph.js b/src/EasyAuthSample/backend/src/with-graph/graph.js
new file mode 100644
index 0000000..7dfab0f
--- /dev/null
+++ b/src/EasyAuthSample/backend/src/with-graph/graph.js
@@ -0,0 +1,29 @@
+// ./backend/src/graph.js
+import graph from "@microsoft/microsoft-graph-client";
+import { getGraphToken } from "./auth.js";
+
+// Create client from token with Graph API scope
+export function getAuthenticatedClient(accessToken) {
+ const client = graph.Client.init({
+ authProvider: (done) => {
+ done(null, accessToken);
+ }
+ });
+
+ return client;
+}
+export async function getGraphProfile(accessToken) {
+ // exchange current backend token for token with
+ // graph api scope
+ const graphToken = await getGraphToken(accessToken);
+
+ // use graph token to get Graph client
+ const graphClient = getAuthenticatedClient(graphToken);
+
+ // get profile of user
+ const profile = await graphClient
+ .api('/me')
+ .get();
+
+ return profile;
+}
\ No newline at end of file
diff --git a/src/EasyAuthSample/backend/web.config b/src/EasyAuthSample/backend/web.config
new file mode 100644
index 0000000..c50a8f4
--- /dev/null
+++ b/src/EasyAuthSample/backend/web.config
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/EasyAuthSample/frontend/.azure/config b/src/EasyAuthSample/frontend/.azure/config
new file mode 100644
index 0000000..6042bba
--- /dev/null
+++ b/src/EasyAuthSample/frontend/.azure/config
@@ -0,0 +1,7 @@
+[defaults]
+group = rg-forvia-appauth
+sku = P0V3
+appserviceplan = appplan-forvia-authsample
+location = francecentral
+web = app-front-forvia-appauth
+
diff --git a/src/EasyAuthSample/frontend/.deployment b/src/EasyAuthSample/frontend/.deployment
new file mode 100644
index 0000000..6278331
--- /dev/null
+++ b/src/EasyAuthSample/frontend/.deployment
@@ -0,0 +1,2 @@
+[config]
+SCM_DO_BUILD_DURING_DEPLOYMENT=true
\ No newline at end of file
diff --git a/src/EasyAuthSample/frontend/.env.sample b/src/EasyAuthSample/frontend/.env.sample
new file mode 100644
index 0000000..ec5d600
--- /dev/null
+++ b/src/EasyAuthSample/frontend/.env.sample
@@ -0,0 +1,3 @@
+APPSETTING_WEBSITE_AUTH_ENABLED=false
+BACKEND_URL="http://localhost:8080"
+WEB_PORT=3000
\ No newline at end of file
diff --git a/src/EasyAuthSample/frontend/.gitignore b/src/EasyAuthSample/frontend/.gitignore
new file mode 100644
index 0000000..ff517bb
--- /dev/null
+++ b/src/EasyAuthSample/frontend/.gitignore
@@ -0,0 +1,353 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Environment files
+.env
\ No newline at end of file
diff --git a/src/EasyAuthSample/frontend/.vscode/launch.json b/src/EasyAuthSample/frontend/.vscode/launch.json
new file mode 100644
index 0000000..d5598fe
--- /dev/null
+++ b/src/EasyAuthSample/frontend/.vscode/launch.json
@@ -0,0 +1,17 @@
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "type": "pwa-node",
+ "request": "launch",
+ "name": "Launch Program",
+ "skipFiles": [
+ "/**"
+ ],
+ "program": "${workspaceFolder}\\src\\index.js"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/EasyAuthSample/frontend/.vscode/settings.json b/src/EasyAuthSample/frontend/.vscode/settings.json
new file mode 100644
index 0000000..32a2492
--- /dev/null
+++ b/src/EasyAuthSample/frontend/.vscode/settings.json
@@ -0,0 +1,7 @@
+{
+ "appService.zipIgnorePattern": [
+ "node_modules{,/**}",
+ ".env",
+ ".vscode{,/**}"
+ ]
+}
\ No newline at end of file
diff --git a/src/EasyAuthSample/frontend/package.json b/src/EasyAuthSample/frontend/package.json
new file mode 100644
index 0000000..03c2a57
--- /dev/null
+++ b/src/EasyAuthSample/frontend/package.json
@@ -0,0 +1,35 @@
+{
+ "name": "frontend",
+ "version": "1.0.0",
+ "description": "JavaScript server written with Express.js and Microsoft Graph",
+ "main": "src/index.js",
+ "type": "module",
+ "scripts": {
+ "start": "node src/index.js --unhandled-rejections=warn"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/azure-samples/js-e2e-web-app-easy-auth-app-to-app.git"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "bugs": {
+ "url": "https://github.com/azure-samples/azure-samples/js-e2e-web-app-easy-auth-app-to-app/issues"
+ },
+ "homepage": "https://github.com/azure-samples/js-e2e-web-app-easy-auth-app-to-app#readme",
+ "devDependencies": {},
+ "dependencies": {
+ "dotenv": "^16.0.3",
+ "ejs": "^3.1.8",
+ "express": "^4.17.1",
+ "isomorphic-fetch": "^3.0.0",
+ "jwt-decode": "^3.1.2"
+ },
+ "prettier": {
+ "printWidth": 80,
+ "semi": true,
+ "singleQuote": true,
+ "trailingComma": "es5"
+ }
+}
diff --git a/src/EasyAuthSample/frontend/run.cjs b/src/EasyAuthSample/frontend/run.cjs
new file mode 100644
index 0000000..7437e84
--- /dev/null
+++ b/src/EasyAuthSample/frontend/run.cjs
@@ -0,0 +1,2 @@
+// For more information on this file, see https://techcommunity.microsoft.com/t5/apps-on-azure-blog/supporting-es6-import-on-windows-app-service-node-js-iisnode/ba-p/3639037
+import("./src/index.js");
\ No newline at end of file
diff --git a/src/EasyAuthSample/frontend/src/error.js b/src/EasyAuthSample/frontend/src/error.js
new file mode 100644
index 0000000..82d67ef
--- /dev/null
+++ b/src/EasyAuthSample/frontend/src/error.js
@@ -0,0 +1,6 @@
+export class HTTPResponseError extends Error {
+ constructor(response, ...args) {
+ super(`HTTP Error Response: ${response.status} ${response.statusText}`, ...args);
+ this.response = response;
+ }
+}
diff --git a/src/EasyAuthSample/frontend/src/index.js b/src/EasyAuthSample/frontend/src/index.js
new file mode 100644
index 0000000..d9140e9
--- /dev/null
+++ b/src/EasyAuthSample/frontend/src/index.js
@@ -0,0 +1,15 @@
+import * as dotenv from 'dotenv' // see https://github.com/motdotla/dotenv#how-do-i-use-dotenv-with-import
+dotenv.config()
+
+
+import { create } from './server.js';
+
+// Listening port for App Service should be set to WEB_PORT for Linux container and PORT for Windows native
+const port = process.env.WEB_PORT || process.env.PORT || 8080;
+
+create()
+ .then(app => {
+ app.listen(port, () => {
+ console.log(`frontend index.js Server has started on port ${port}!`);
+ });
+ }).catch(err => console.log(`frontend index.js: ${JSON.stringify(err)}`));
\ No newline at end of file
diff --git a/src/EasyAuthSample/frontend/src/refreshToken.js b/src/EasyAuthSample/frontend/src/refreshToken.js
new file mode 100644
index 0000000..527405f
--- /dev/null
+++ b/src/EasyAuthSample/frontend/src/refreshToken.js
@@ -0,0 +1,78 @@
+import "isomorphic-fetch";
+import { HTTPResponseError } from './error.js';
+
+export const refreshTokenInMiddleware = async (url, accessToken) => {
+
+ try {
+ // Get refreshed token
+ if (!url) {
+ return {
+ refreshed: false,
+ status: `!url`
+ }
+ };
+
+ // Get remote profile
+ const response = await fetch(url, {
+ cache: "no-store", // no caching -- for demo purposes only
+ method: 'GET',
+ headers: {
+ 'Authorization': `Bearer ${accessToken}`
+ }
+ });
+
+ // // Check response status
+ if (response.ok) {
+ return {
+ refreshed: true,
+ status: response.status
+ }
+ } else {
+ return {
+ refreshed: false,
+ status: response.status,
+ error: new HTTPResponseError(response)
+ }
+ }
+ } catch (err) {
+ console.log(`frontend refreshToken.js: ${JSON.stringify(err)}`);
+ }
+
+}
+export const isTokenExpired = (expiration) => {
+
+ // Get token expired time
+ const expiredTime = expiration;
+ const expirationDate = new Date(expiredTime * 1000);
+
+ const currentTime = (+ new Date()); // current time in seconds
+ const currentTimeDate = new Date(currentTime);
+
+ if (currentTime > (expiredTime * 1000)) {
+
+ return {
+ expired: true,
+ expiredTime,
+ expirationDate,
+ currentTime,
+ currentTimeDate,
+ minutes_remaining: 0,
+ refreshed: false,
+ cause: "token expired"
+ };
+
+ } else {
+ const minutes_remaining = Math.round((((expirationDate - currentTimeDate) % 86400000) % 3600000) / 60000);
+
+ return {
+ expired: false,
+ expiredTime,
+ expirationDate,
+ currentTime,
+ currentTimeDate,
+ minutes_remaining,
+ refreshed: false,
+ cause: "token NOT expired"
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/EasyAuthSample/frontend/src/remoteProfile.js b/src/EasyAuthSample/frontend/src/remoteProfile.js
new file mode 100644
index 0000000..99e1e5f
--- /dev/null
+++ b/src/EasyAuthSample/frontend/src/remoteProfile.js
@@ -0,0 +1,75 @@
+import { HTTPResponseError } from './error.js';
+import { sortJson } from './sortJson.js';
+import "isomorphic-fetch";
+
+export const getRemoteProfile = async (remoteUrl, accessToken, authEnabled) => {
+
+ try {
+
+ if (!remoteUrl ) {
+ return {
+ error: 'Client: No remote URL'
+ };
+ }
+
+
+ if (authEnabled && !accessToken) {
+ return {
+ error: 'Client: No access token found'
+ };
+ }
+
+ const config = {
+ cache: "no-store", // no caching -- for demo purposes only
+ method: 'GET',
+ headers: {}
+ }
+
+ // Add access token as bearer token only if present
+ if(!!accessToken){
+ config.headers.Authorization= `Bearer ${accessToken}`
+ }
+
+ // Get remote profile
+ const response = await fetch(remoteUrl, config);
+
+ // Check response status
+ if (response.ok) {
+
+ // Get api response including profile
+ const apiResponse = await response.json();
+ console.log(`frontend remoteProfile.js getRemoteProfile response json: ${JSON.stringify(apiResponse?.profile)}`);
+
+ // Data for rendered view
+ return {
+ error: {},
+ profile: sortJson(apiResponse?.profile),
+ headers: sortJson(apiResponse?.headers),
+ env: sortJson(apiResponse?.env),
+ bearerToken: apiResponse?.bearerToken,
+ };
+ } else {
+
+ const textError = await response.text();
+ console.log(`frontend remoteProfile.js getRemoteProfile api Fetch error text: ${response?.statusCode} ${textError}`);
+
+ return {
+ error: {
+ error: new HTTPResponseError(response),
+ message: `api response not ok ${response?.statusCode} ${textError}`,
+ type: "getRemoteProfile - api response",
+
+ }
+ }
+ }
+ } catch (error) {
+ console.log(`frontend remoteProfile.js getRemoteProfile caught error = ${error?.message}`);
+ return {
+ error: {
+ error: new HTTPResponseError(error),
+ message: error.message,
+ type: "getRemoteProfile - catch",
+ }
+ }
+ }
+}
diff --git a/src/EasyAuthSample/frontend/src/server.js b/src/EasyAuthSample/frontend/src/server.js
new file mode 100644
index 0000000..83fb98a
--- /dev/null
+++ b/src/EasyAuthSample/frontend/src/server.js
@@ -0,0 +1,145 @@
+//
+// Express.js app server
+import express from 'express';
+import { refreshTokenInMiddleware, isTokenExpired } from './refreshToken.js';
+import "isomorphic-fetch";
+import jwt_decode from 'jwt-decode';
+import { dirname } from 'path';
+import { fileURLToPath } from 'url';
+import { sortJson } from './sortJson.js';
+import { getRemoteProfile } from './remoteProfile.js';
+const __dirname = dirname(fileURLToPath(import.meta.url));
+//
+
+//
+// Optional middleware to refresh the token if it is about to expire
+// There are otherways to refresh, this was the easiest for this sample project
+const refreshToken = async function (req, _, next) {
+
+ // Set default middleware values
+ req.tokenMiddleware = {
+ "token": "",
+ "decoded": ""
+ };
+
+ // Get token from injected headers
+ req.tokenMiddleware.token = req.headers['x-ms-token-aad-access-token'];
+
+ if (!req.tokenMiddleware.token) {
+ return next();
+ }
+
+ // Decode token
+ req.tokenMiddleware.decoded = jwt_decode(req.tokenMiddleware.token);
+
+ // Check if token is expired
+ req.tokenMiddleware.isExpired = isTokenExpired(req.tokenMiddleware.decoded.exp);
+
+ // If token is expired, refresh it
+ if (req.tokenMiddleware.isExpired.expired) {
+
+ const refreshUrl = `https://${req.headers.host}/.auth/refresh`;
+ req.tokenMiddleware.refreshedTokenResult = await refreshTokenInMiddleware(refreshUrl, req.tokenMiddleware.token);
+ }
+ return next();
+}
+//
+
+//
+export const create = async () => {
+
+ // Create express app
+ const app = express();
+
+ // Refresh token middleware
+ app.use(refreshToken)
+
+ // set the view engine to ejs
+ app.set('view engine', 'ejs');
+
+ // Home page
+ app.get('/', async (_, res) => {
+ res.render(`${__dirname}/views/home`);
+ });
+
+ // Access token from injected header
+ app.get('/debug', async (req, res) => {
+
+ try {
+
+ // Data for rendered view
+ const dataForView = {
+ error: undefined,
+ accessToken: req.headers['x-ms-token-aad-access-token'],
+ scope: req.tokenMiddleware?.decoded?.scp,
+ user: req.tokenMiddleware?.decoded?.name,
+ tokenMiddleware: sortJson(req.tokenMiddleware),
+ headers: sortJson(req.headers),
+ env: sortJson(process.env)
+ };
+
+ // Success - View
+ res.render(`${__dirname}/views/debug`, dataForView)
+
+ } catch (error) {
+
+ // Failure - View
+ res.render(`${__dirname}/views/debug`, { error })
+ }
+ });
+
+ // Get remote profile
+ // use access token as bearer token to API server
+ app.get('/get-profile', async (req, res) => {
+
+ try {
+
+ // Get remote URL from environment variable
+ // Should be in format of https://server/profile
+ let remoteUrl = process.env.BACKEND_URL + "/get-profile";
+ if (!remoteUrl) {
+ return res.render(`${__dirname}/views/profile`, { error: 'Client: No remote URL found' });
+ }
+
+ // Get access token from injected header
+ const accessToken = req.headers['x-ms-token-aad-access-token'];
+ console.log(`accessToken: ${accessToken}`)
+
+ const authEnabled = process.env.APPSETTING_WEBSITE_AUTH_ENABLED==='true' ? true : false;
+ if (authEnabled && !accessToken) {
+ return res.render(`${__dirname}/views/profile`, { error: 'Client: No access token found' });
+ }
+
+ // Get remote profile
+ const response = await getRemoteProfile(remoteUrl, accessToken, authEnabled);
+ const error = (response.error && Object.keys(response.error).length > 0 ) ? JSON.stringify(response.error) : "";
+
+ // Data for rendered view
+ const dataForView = {
+ error,
+ remoteUrl,
+ profile: sortJson(response.profile),
+ headers: sortJson(response.headers),
+ env: sortJson(response.env),
+ authEnabled,
+ bearerToken: response.bearerToken,
+ raw: response
+ };
+
+ // Success - render view
+ res.render(`${__dirname}/views/profile`, dataForView);
+
+ } catch (error) {
+ // Route-level Failure - render view
+ res.render(`${__dirname}/views/profile`, error.message );
+ }
+ });
+
+ // instead of 404 - just return home page
+ app.get('*', (_, res) => {
+ res.redirect('/');
+ });
+
+ return app;
+};
+//
diff --git a/src/EasyAuthSample/frontend/src/sortJson.js b/src/EasyAuthSample/frontend/src/sortJson.js
new file mode 100644
index 0000000..5223e5e
--- /dev/null
+++ b/src/EasyAuthSample/frontend/src/sortJson.js
@@ -0,0 +1,7 @@
+export const sortJson = (json) => {
+ if (!json) return {};
+
+ if (typeof json !== 'object') return {};
+
+ return Object.entries(json).sort(Intl.Collator().compare).reduce((o, [k, v]) => (o[k] = v, o), {});
+}
\ No newline at end of file
diff --git a/src/EasyAuthSample/frontend/src/views/debug.ejs b/src/EasyAuthSample/frontend/src/views/debug.ejs
new file mode 100644
index 0000000..73cd419
--- /dev/null
+++ b/src/EasyAuthSample/frontend/src/views/debug.ejs
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+ Easy auth - Get Profile from API server
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/EasyAuthSample/frontend/src/views/profile.ejs b/src/EasyAuthSample/frontend/src/views/profile.ejs
new file mode 100644
index 0000000..291e98a
--- /dev/null
+++ b/src/EasyAuthSample/frontend/src/views/profile.ejs
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+ Easy auth - Get Profile from API server
+
+
+
+
+
+
+
diff --git a/src/SPA_JS_Vanilla/App/public/styles.css b/src/SPA_JS_Vanilla/App/public/styles.css
new file mode 100644
index 0000000..b895048
--- /dev/null
+++ b/src/SPA_JS_Vanilla/App/public/styles.css
@@ -0,0 +1,8 @@
+.navbarStyle {
+ padding: .5rem 1rem !important;
+}
+
+td {
+ word-break: break-word;
+ max-width: 20rem;
+}
\ No newline at end of file
diff --git a/src/SPA_JS_Vanilla/App/public/ui.js b/src/SPA_JS_Vanilla/App/public/ui.js
new file mode 100644
index 0000000..d327694
--- /dev/null
+++ b/src/SPA_JS_Vanilla/App/public/ui.js
@@ -0,0 +1,32 @@
+// Select DOM elements to work with
+const signInButton = document.getElementById('signIn');
+const signOutButton = document.getElementById('signOut');
+const titleDiv = document.getElementById('title-div');
+const welcomeDiv = document.getElementById('welcome-div');
+const tableDiv = document.getElementById('table-div');
+const tableBody = document.getElementById('table-body-div');
+
+function welcomeUser(username) {
+ console.log('welcome called');
+ signInButton.classList.add('d-none');
+ signOutButton.classList.remove('d-none');
+ titleDiv.classList.add('d-none');
+ welcomeDiv.classList.remove('d-none');
+ welcomeDiv.innerHTML = `Welcome ${username}!`;
+};
+
+function updateTable(account) {
+ tableDiv.classList.remove('d-none');
+
+ const tokenClaims = createClaimsTable(account.idTokenClaims);
+
+ Object.keys(tokenClaims).forEach((key) => {
+ let row = tableBody.insertRow(0);
+ let cell1 = row.insertCell(0);
+ let cell2 = row.insertCell(1);
+ let cell3 = row.insertCell(2);
+ cell1.innerHTML = tokenClaims[key][0];
+ cell2.innerHTML = tokenClaims[key][1];
+ cell3.innerHTML = tokenClaims[key][2];
+ });
+};
\ No newline at end of file
diff --git a/src/SPA_JS_Vanilla/App/sample.test.js b/src/SPA_JS_Vanilla/App/sample.test.js
new file mode 100644
index 0000000..78fda76
--- /dev/null
+++ b/src/SPA_JS_Vanilla/App/sample.test.js
@@ -0,0 +1,58 @@
+/**
+ * @jest-environment jsdom
+ */
+
+const request = require('supertest');
+const path = require('path');
+const fs = require('fs');
+
+const app = require('./server.js');
+
+jest.dontMock('fs');
+
+const html = fs.readFileSync(path.resolve(__dirname, './public/index.html'), 'utf8');
+
+describe('Sanitize configuration object', () => {
+ beforeAll(() => {
+ global.msalConfig = require('./public/authConfig.js').msalConfig;
+ });
+
+ it('should define the config object', () => {
+ expect(msalConfig).toBeDefined();
+ });
+
+ it('should not contain credentials', () => {
+ const regexGuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
+ expect(regexGuid.test(msalConfig.auth.clientId)).toBe(false);
+ });
+
+ it('should contain authority URI', () => {
+ const regexUri = /[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)?/gi;
+ expect(regexUri.test(msalConfig.auth.authority)).toBe(true);
+ });
+});
+
+describe('Ensure pages served', () => {
+
+ beforeAll(() => {
+ process.env.NODE_ENV = 'test';
+ });
+
+ it('should serve index page', async () => {
+ const res = await request(app)
+ .get('/');
+
+ const data = await fs.promises.readFile(path.join(__dirname, './public/index.html'), 'utf8');
+ expect(res.statusCode).toEqual(200);
+ expect(res.text).toEqual(data);
+ });
+
+ it('should serve signout page', async () => {
+ const res = await request(app)
+ .get('/signout');
+
+ const data = await fs.promises.readFile(path.join(__dirname, './public/signout.html'), 'utf8');
+ expect(res.statusCode).toEqual(200);
+ expect(res.text).toEqual(data);
+ });
+});
\ No newline at end of file
diff --git a/src/SPA_JS_Vanilla/App/server.js b/src/SPA_JS_Vanilla/App/server.js
new file mode 100644
index 0000000..33ead3b
--- /dev/null
+++ b/src/SPA_JS_Vanilla/App/server.js
@@ -0,0 +1,38 @@
+const express = require('express');
+const morgan = require('morgan');
+const path = require('path');
+
+const DEFAULT_PORT = process.env.PORT || 3000;
+
+// initialize express.
+const app = express();
+
+// Configure morgan module to log all requests.
+app.use(morgan('dev'));
+
+// serve public assets.
+app.use(express.static('public'));
+
+// serve msal-browser module
+app.use(express.static(path.join(__dirname, "node_modules/@azure/msal-browser/lib")));
+
+// set up a route for signout.html
+app.get('/signout', (req, res) => {
+ res.sendFile(path.join(__dirname + '/public/signout.html'));
+});
+
+// set up a route for redirect.html
+app.get('/redirect', (req, res) => {
+ res.sendFile(path.join(__dirname + '/public/redirect.html'));
+});
+
+// set up a route for index.html
+app.get('/', (req, res) => {
+ res.sendFile(path.join(__dirname + '/index.html'));
+});
+
+app.listen(DEFAULT_PORT, () => {
+ console.log(`Sample app listening on port ${DEFAULT_PORT}!`);
+});
+
+module.exports = app;
diff --git a/src/SPA_JS_Vanilla/README.md b/src/SPA_JS_Vanilla/README.md
new file mode 100644
index 0000000..4845a9a
--- /dev/null
+++ b/src/SPA_JS_Vanilla/README.md
@@ -0,0 +1,327 @@
+---
+page_type: sample
+name: Vanilla JavaScript single-page application using MSAL.js to authenticate users against Microsoft Entra External ID
+description: This sample demonstrates a Vanilla JavaScript single-page application using MSAL.js to authenticate users against Microsoft Entra External ID
+languages:
+ - javascript
+products:
+ - entra-external-id
+ - msal-js
+urlFragment: ms-identity-ciam-javascript-tutorial-0-sign-in-vanillajs
+extensions:
+ services:
+ - ms-identity
+ platform:
+ - JavaScript
+ endpoint:
+ - AAD v2.0
+ level:
+ - 100
+ client:
+ - Vanilla JavaScript SPA
+---
+
+# Vanilla JavaScript single-page application using MSAL.js to authenticate users against Microsoft Entra External ID
+
+* [Overview](#overview)
+* [Scenario](#scenario)
+* [Contents](#contents)
+* [Prerequisites](#prerequisites)
+* [Setup the sample](#setup-the-sample)
+* [Explore the sample](#explore-the-sample)
+* [Troubleshooting](#troubleshooting)
+* [About the code](#about-the-code)
+* [Contributing](#contributing)
+* [Learn More](#learn-more)
+
+## Overview
+
+This sample demonstrates a vanilla JavaScript single-page application (SPA) that lets users authenticate to [Microsoft Entra External ID](https://learn.microsoft.com/entra/external-id/customers/how-to-create-external-tenant-portal) using the [Microsoft Authentication Library for JavaScript](https://github.com/AzureAD/microsoft-authentication-library-for-js) (MSAL.js).
+Here you'll learn about [ID Tokens](https://docs.microsoft.com/azure/active-directory/develop/id-tokens), [OIDC scopes](https://docs.microsoft.com/azure/active-directory/develop/v2-permissions-and-consent#openid-connect-scopes), [single-sign on](https://docs.microsoft.com/azure/active-directory/develop/msal-js-sso), **silent requests** and more.
+
+## Scenario
+
+1. The client JavaScript SPA uses the to sign-in a user and obtain a JWT [ID Token](https://aka.ms/id-tokens) from **Microsoft Entra External ID**.
+1. The **ID Token** proves that the user has successfully authenticated against **Microsoft Entra External ID**.
+
+
+
+## Contents
+
+| File/folder | Description |
+|-----------------------|---------------------------------------------------------------------------|
+| `AppCreationScripts/` | Contains Powershell scripts to automate app registration. |
+| `public/authPopup.js` | Main authentication logic resides here (using Popup flow). |
+| `public/authRedirect.js` | Use this instead of `authPopup.js` for authentication with redirect flow. |
+| `public/authConfig.js` | Contains configuration parameters for the sample. |
+| `public/ui.js` | Contains UI logic. |
+| `server.js` | Simple Express server for `index.html`. |
+
+## Prerequisites
+
+* [Node.js](https://nodejs.org/en/download/) must be installed to run this sample.
+* [Visual Studio Code](https://code.visualstudio.com/download) is recommended for running and editing this sample.
+* [VS Code Azure Tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.vscode-node-azure-pack) extension is recommended for interacting with Azure through VS Code Interface.
+* A modern web browser.
+* An external tenant. To create one, choose from the following methods:
+ * (Recommended) Use the [Microsoft Entra External ID extension](https://aka.ms/ciamvscode/readme/marketplace) to set up an external tenant directly in Visual Studio Code.
+ * [Create a new external tenant](https://learn.microsoft.com/entra/external-id/customers/how-to-create-external-tenant-portal) in the Microsoft Entra admin center.
+* A user account with permissions to an external tenant.
+
+## Setup the sample
+
+### Step 1: Clone or download this repository
+
+From your shell or command line:
+
+```console
+git clone https://github.com/Azure-Samples/ms-identity-ciam-javascript-tutorial.git
+```
+
+or download and extract the repository *.zip* file.
+
+> :warning: To avoid path length limitations on Windows, we recommend cloning into a directory near the root of your drive.
+
+### Step 2: Install project dependencies
+
+```console
+ cd 1-Authorization\0-sign-in-vanillajs\App
+ npm install
+```
+
+### Step 3: Register the sample application(s) in your tenant
+
+There is one project in this sample. To register it, you can:
+
+* follow the steps below for manually register your apps
+* or use PowerShell scripts that:
+ * **automatically** creates the Microsoft Entra applications and related objects (passwords, permissions, dependencies) for you.
+ * modify the projects' configuration files.
+
+
+ Expand this section if you want to use this automation:
+
+> :warning: If you have never used **Microsoft Graph PowerShell** before, we recommend you go through the [App Creation Scripts Guide](./AppCreationScripts/AppCreationScripts.md) once to ensure that your environment is prepared correctly for this step.
+
+1. Ensure that you have PowerShell 7 or later installed.
+
+1. Run the script to create your Azure AD application and configure the code of the sample application accordingly.
+1. For interactive process -in PowerShell, run:
+
+ ```PowerShell
+ cd .\AppCreationScripts\
+ .\Configure.ps1 -TenantId "[Optional] - your tenant id" -AzureEnvironmentName "[Optional] - Azure environment, defaults to 'Global'"
+ ```
+
+> Other ways of running the scripts are described in [App Creation Scripts guide](./AppCreationScripts/AppCreationScripts.md). The scripts also provide a guide to automated application registration, configuration and removal which can help in your CI/CD scenarios.
+
+
+
+#### Choose the Microsoft Entra External ID tenant where you want to create your applications
+
+To manually register the apps, as a first step you'll need to:
+
+1. Sign in to the [Microsoft Entra admin center](https://entra.microsoft.com/).
+1. If your account is present in more than one Microsoft Entra External ID tenant, select your profile at the top right corner in the menu on top of the page, and then **switch directory** to change your portal session to the desired Microsoft Entra External ID tenant.
+
+#### Create User Flows
+
+Please refer to: [Tutorial: Create a sign-up and sign-in user flow](https://learn.microsoft.com/entra/external-id/customers/how-to-user-flow-sign-up-sign-in-customers)
+
+> :information_source: To enable password reset in an external tenant, please refer to: [Tutorial: Enable users to unlock their account or reset passwords using Microsoft Entra self-service password reset](https://learn.microsoft.com/entra/identity/authentication/tutorial-enable-sspr)
+
+#### Add External Identity Providers
+
+Please refer to:
+
+* [Tutorial: Add Google as an identity provider](https://learn.microsoft.com/entra/external-id/customers/how-to-google-federation-customers)
+* [Tutorial: Add Facebook as an identity provider](https://learn.microsoft.com/entra/external-id/customers/how-to-facebook-federation-customers)
+
+#### Register the client app (ciam-msal-javascript-spa)
+
+1. Navigate to the [Microsoft Entra admin center](https://entra.microsoft.com/) and select the **Microsoft Entra External ID** service.
+1. Select the **App Registrations** blade on the left, then select **New registration**.
+1. In the **Register an application page** that appears, enter your application's registration information:
+ 1. In the **Name** section, enter a meaningful application name that will be displayed to users of the app, for example `ciam-msal-javascript-spa`.
+ 1. Under **Supported account types**, select **Accounts in this organizational directory only**
+ 1. Select **Register** to create the application.
+1. In the **Overview** blade, find and note the **Application (client) ID**. You use this value in your app's configuration file(s) later in your code.
+1. In the app's registration screen, select the **Authentication** blade to the left.
+1. If you don't have a platform added, select **Add a platform** and select the **Single-page application** option.
+ 1. In the **Redirect URI** section enter the following redirect URIs:
+ 1. `http://localhost:3000`
+ 1. `http://localhost:3000/redirect`
+ 1. Click **Save** to save your changes.
+1. Since this app signs-in users, we will now proceed to select **delegated permissions**, which is is required by apps signing-in users.
+ 1. In the app's registration screen, select the **API permissions** blade in the left to open the page where we add access to the APIs that your application needs:
+ 1. Select the **Add a permission** button and then:
+ 1. Ensure that the **Microsoft APIs** tab is selected.
+ 1. In the *Commonly used Microsoft APIs* section, select **Microsoft Graph**
+ 1. In the **Delegated permissions** section, select **openid**, **offline_access** in the list. Use the search box if necessary.
+ 1. Select the **Add permissions** button at the bottom.
+1. At this stage, the permissions are assigned correctly, but since it's an external tenant, the users themselves cannot consent to these permissions. To get around this problem, we'd let the [tenant administrator consent on behalf of all users in the tenant](https://docs.microsoft.com/azure/active-directory/develop/v2-admin-consent). Select the **Grant admin consent for {tenant}** button, and then select **Yes** when you are asked if you want to grant consent for the requested permissions for all accounts in the tenant. You need to be a tenant admin to be able to carry out this operation.
+
+##### Configure the client app (ciam-msal-javascript-spa) to use your app registration
+
+Open the project in your IDE (like Visual Studio or Visual Studio Code) to configure the code.
+
+> In the steps below, "ClientID" is the same as "Application ID" or "AppId".
+
+1. Open the `App\public\authConfig.js` file.
+1. Find the key `Enter_the_Application_Id_Here` and replace the existing value with the application ID (clientId) of `ciam-msal-javascript-spa` app copied from the Microsoft Entra admin center.
+1. Find the placeholder `Enter_the_Tenant_Subdomain_Here` and replace it with the Directory (tenant) subdomain. For instance, if your tenant primary domain is `contoso.onmicrosoft.com`, use `contoso`. If you don't have your tenant domain name, learn how to [read your tenant details](https://review.learn.microsoft.com/azure/active-directory/external-identities/customers/how-to-create-customer-tenant-portal#get-the-customer-tenant-details).
+
+### Step 4: Running the sample
+
+```console
+ cd 1-Authorization\0-sign-in-vanillajs\App
+ npm start
+```
+
+## Explore the sample
+
+1. Open your browser and navigate to `http://localhost:3000`.
+1. Click the **sign-in** button on the top right corner.
+
+> :information_source: Did the sample not work for you as expected? Then please reach out to us using the [GitHub Issues](../../../../issues) page.
+
+## We'd love your feedback!
+
+Were we successful in addressing your learning objective? Consider taking a moment to [share your experience with us](https://forms.office.com/Pages/ResponsePage.aspx?id=v4j5cvGGr0GRqy180BHbR_ivMYEeUKlEq8CxnMPgdNZUNDlUTTk2NVNYQkZSSjdaTk5KT1o4V1VVNS4u).
+
+## Troubleshooting
+
+
+ Expand for troubleshooting info
+
+> * Use [Stack Overflow](http://stackoverflow.com/questions/tagged/msal) to get support from the community. Ask your questions on Stack Overflow first and browse existing issues to see if someone has asked your question before.
+Ask your questions on Stack Overflow first and browse existing issues to see if someone has asked your question before.
+Make sure that your questions or comments are tagged with [`azure-active-directory-b2c` `node` `ms-identity` `adal` `msal-js` `msal`].
+
+To provide feedback on or suggest features for Microsoft Entra, visit [User Voice page](https://feedback.azure.com/d365community/forum/79b1327d-d925-ec11-b6e6-000d3a4f06a4).
+
+
+## About the code
+
+## Sign-in
+
+MSAL.js provides 3 login APIs: `loginPopup()`, `loginRedirect()` and `ssoSilent()`:
+
+```javascript
+ myMSALObj.loginPopup(loginRequest)
+ .then((response) => {
+ // your logic
+ })
+ .catch(error => {
+ console.error(error);
+ });
+```
+
+To use the redirect flow, you must register a handler for redirect promise. **MSAL.js** provides`handleRedirectPromise()` API:
+
+```javascript
+ myMSALObj.handleRedirectPromise()
+ .then((response) => {
+ // your logic
+ })
+ .catch(err => {
+ console.error(err);
+ });
+
+ myMSALObj.loginRedirect(loginRequest);
+```
+
+The recommended pattern is that you fallback to an **interactive method** should the silent SSO fails.
+
+```javascript
+
+ const silentRequest = {
+ scopes: ["openid", "profile"],
+ loginHint: "example@domain.net"
+ };
+
+ myMSALObj.ssoSilent(silentRequest)
+ .then((response) => {
+ // your logic
+ }).catch(error => {
+ console.error("Silent Error: " + error);
+ if (error instanceof msal.InteractionRequiredAuthError) {
+ myMSALObj.loginRedirect(loginRequest);
+ }
+ });
+```
+
+You can get all the active accounts of a user with the get `getAllAccounts()` API. If you know the **username** or **home ID** of an account, you can select it by:
+
+```javascript
+ myMSALObj.getAccountByUsername(username);
+ // or
+ myMSALObj.getAccountByHomeId(homeId);
+```
+
+### Sign-out
+
+The Application redirects the user to the **Microsoft identity platform** logout endpoint to sign out. This endpoint clears the user's session from the browser. If your app did not go to the logout endpoint, the user may re-authenticate to your app without entering their credentials again, because they would have a valid single sign-in session with the **Microsoft identity platform** endpoint. For more, see: [Send a sign-out request](https://docs.microsoft.com/azure/active-directory/develop/v2-protocols-oidc#send-a-sign-out-request)
+
+### ID Token Validation
+
+When you receive an [ID token](https://docs.microsoft.com/azure/active-directory/develop/id-tokens) directly from the IdP on a secure channel (e.g. HTTPS), such is the case with SPAs, there’s no need to validate it. If you were to do it, you would validate it by asking the same server that gave you the ID token to give you the keys needed to validate it, which renders it pointless, as if one is compromised so is the other.
+
+### Deploying SPA to Azure Storage
+
+There is one single-page application in this sample. To deploy it to **Azure Storage**, you'll need to:
+
+- create an Azure Storage blob and obtain website coordinates
+- build your project and upload it to Azure Storage blob
+- update config files with website coordinates
+
+> :information_source: If you would like to use **VS Code Azure Tools** extension for deployment, [watch the tutorial](https://docs.microsoft.com/azure/developer/javascript/tutorial-vscode-static-website-node-01) offered by Microsoft Docs.
+
+#### Build and upload (ciam-msal-javascript-spa) to an Azure Storage blob
+
+> :information_source: If you don't have an account already, see: [How to create a storage account](https://docs.microsoft.com/azure/storage/common/storage-account-create).
+
+1. Sign in to the [Microsoft Entra admin center](https://entra.microsoft.com/).
+1. Locate your storage account and display the account overview.
+1. Select **Static website** to display the configuration page for static websites.
+1. Select **Enabled** to enable static website hosting for the storage account.
+1. In the **Index document name** field, specify a default index page (For example: `index.html`).
+1. The default **index page** is displayed when a user navigates to the root of your static website.
+1. Select **Save**. The Microsoft Entra admin center now displays your static website endpoint. Make a note of the **Primary endpoint field**.
+1. In the `ciam-msal-javascript-spa` project source code, update your configuration file with the **Primary endpoint field** as your new **Redirect URI** (you will register this URI later).
+1. Next, select **Storage Explorer**.
+1. Expand the **BLOB CONTAINERS** node, and then select the `$web` container.
+1. Choose the **Upload** button to upload files.
+1. If you intend for the browser to display the contents of file, make sure that the content type of that file is set to `text/html`.
+1. In the pane that appears beside the **account overview page** of your storage account, select **Static Website**. The URL of your site appears in the **Primary endpoint field**. In the next section, you will register this URI.
+
+#### Update the CIAM app registration for ciam-msal-javascript-spa
+
+1. Navigate back to to the [Microsoft Entra admin center](https://entra.microsoft.com/).
+1. In the left-hand navigation pane, select the **Microsoft Entra** service, and then select **App registrations**.
+1. In the resulting screen, select `ciam-msal-javascript-spa`.
+1. In the app's registration screen, select **Authentication** in the menu.
+ 1. In the **Redirect URIs** section, update the reply URLs to match the site URL of your Azure deployment. For example:
+ 1. `https://ciam-msal-javascript-spa.azurewebsites.net/`
+ 1. `https://ciam-msal-javascript-spa.azurewebsites.net/redirect`
+
+
+## Contributing
+
+If you'd like to contribute to this sample, see [CONTRIBUTING.MD](/CONTRIBUTING.md).
+
+This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information, see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
+
+## Learn More
+
+* [Configure your company branding](https://learn.microsoft.com/entra/fundamentals/how-to-customize-branding)
+* [OAuth 2.0 authorization with Microsoft Entra ID](https://learn.microsoft.com/entra/architecture/auth-oauth2)
+* [Language customization in Microsoft Entra External ID](https://learn.microsoft.com/entra/external-id/user-flow-customize-language)
+* [Building Zero Trust ready apps](https://aka.ms/ztdevsession)
+* [Initialize client applications using MSAL.js](https://learn.microsoft.com/entra/identity-platform/msal-js-initializing-client-applications)
+* [Single sign-on with MSAL.js](https://learn.microsoft.com/entra/identity-platform/msal-js-sso)
+* [Handle MSAL.js exceptions and errors](https://docs.microsoft.com/azure/active-directory/develop/msal-handling-exceptions?tabs=javascript)
+* [Logging in MSAL.js applications](https://learn.microsoft.com/entra/identity-platform/msal-logging-js)
+* [Pass custom state in authentication requests using MSAL.js](https://learn.microsoft.com/entra/identity-platform/msal-js-pass-custom-state-authentication-request)
+* [Prompt behavior in MSAL.js interactive requests](https://learn.microsoft.com/entra/identity-platform/msal-js-prompt-behavior)
+* [Use the Microsoft Authentication Library for JavaScript to work with Azure AD B2C](https://learn.microsoft.com/entra/identity-platform/msal-b2c-overview)
diff --git a/src/WIP/AzFunc/BlobTrigger/.gitignore b/src/WIP/AzFunc/BlobTrigger/.gitignore
new file mode 100644
index 0000000..3c3f4e6
--- /dev/null
+++ b/src/WIP/AzFunc/BlobTrigger/.gitignore
@@ -0,0 +1,264 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# Azure Functions localsettings file
+local.settings.json
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# DNX
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: Comment the next line if you want to checkin your web deploy settings
+# but database connection strings (with potential passwords) will be unencrypted
+#*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+# NuGet v3's project.json files produces more ignoreable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+node_modules/
+orleans.codegen.cs
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# CodeRush
+.cr/
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
\ No newline at end of file
diff --git a/src/WIP/AzFunc/BlobTrigger/.vscode/extensions.json b/src/WIP/AzFunc/BlobTrigger/.vscode/extensions.json
new file mode 100644
index 0000000..dde673d
--- /dev/null
+++ b/src/WIP/AzFunc/BlobTrigger/.vscode/extensions.json
@@ -0,0 +1,5 @@
+{
+ "recommendations": [
+ "ms-azuretools.vscode-azurefunctions"
+ ]
+}
\ No newline at end of file
diff --git a/src/WIP/AzFunc/BlobTrigger/BlobTrigger.csproj b/src/WIP/AzFunc/BlobTrigger/BlobTrigger.csproj
new file mode 100644
index 0000000..e99472f
--- /dev/null
+++ b/src/WIP/AzFunc/BlobTrigger/BlobTrigger.csproj
@@ -0,0 +1,31 @@
+
+
+ net8.0
+ v4
+ Exe
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+ Never
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/WIP/AzFunc/BlobTrigger/EvgblobTrigger.cs b/src/WIP/AzFunc/BlobTrigger/EvgblobTrigger.cs
new file mode 100644
index 0000000..9519a45
--- /dev/null
+++ b/src/WIP/AzFunc/BlobTrigger/EvgblobTrigger.cs
@@ -0,0 +1,25 @@
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.Azure.Functions.Worker;
+using Microsoft.Extensions.Logging;
+
+namespace BlobTrigger
+{
+ public class EvgblobTrigger
+ {
+ private readonly ILogger _logger;
+
+ public EvgblobTrigger(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ [Function(nameof(EvgblobTrigger))]
+ public async Task Run([BlobTrigger("samples-workitems/{name}", Source = BlobTriggerSource.EventGrid, Connection = "")] Stream stream, string name)
+ {
+ using var blobStreamReader = new StreamReader(stream);
+ var content = await blobStreamReader.ReadToEndAsync();
+ _logger.LogInformation($"C# Blob Trigger (using Event Grid) processed blob\n Name: {name} \n Data: {content}");
+ }
+ }
+}
diff --git a/src/WIP/AzFunc/BlobTrigger/Program.cs b/src/WIP/AzFunc/BlobTrigger/Program.cs
new file mode 100644
index 0000000..80221bb
--- /dev/null
+++ b/src/WIP/AzFunc/BlobTrigger/Program.cs
@@ -0,0 +1,13 @@
+using Microsoft.Azure.Functions.Worker.Builder;
+using Microsoft.Extensions.Hosting;
+
+var builder = FunctionsApplication.CreateBuilder(args);
+
+builder.ConfigureFunctionsWebApplication();
+
+// Application Insights isn't enabled by default. See https://aka.ms/AAt8mw4.
+// builder.Services
+// .AddApplicationInsightsTelemetryWorkerService()
+// .ConfigureFunctionsApplicationInsights();
+
+builder.Build().Run();
diff --git a/src/WIP/AzFunc/BlobTrigger/Properties/launchSettings.json b/src/WIP/AzFunc/BlobTrigger/Properties/launchSettings.json
new file mode 100644
index 0000000..cb3713f
--- /dev/null
+++ b/src/WIP/AzFunc/BlobTrigger/Properties/launchSettings.json
@@ -0,0 +1,9 @@
+{
+ "profiles": {
+ "BlobTrigger": {
+ "commandName": "Project",
+ "commandLineArgs": "--port 7168",
+ "launchBrowser": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/WIP/AzFunc/BlobTrigger/host.json b/src/WIP/AzFunc/BlobTrigger/host.json
new file mode 100644
index 0000000..d4f2d41
--- /dev/null
+++ b/src/WIP/AzFunc/BlobTrigger/host.json
@@ -0,0 +1,12 @@
+{
+ "version": "2.0",
+ "logging": {
+ "applicationInsights": {
+ "samplingSettings": {
+ "isEnabled": true,
+ "excludedTypes": "Request"
+ },
+ "enableLiveMetricsFilters": true
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/WIP/Custom_Auth_Extensions/.gitignore b/src/WIP/Custom_Auth_Extensions/.gitignore
new file mode 100644
index 0000000..3c3f4e6
--- /dev/null
+++ b/src/WIP/Custom_Auth_Extensions/.gitignore
@@ -0,0 +1,264 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# Azure Functions localsettings file
+local.settings.json
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# DNX
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: Comment the next line if you want to checkin your web deploy settings
+# but database connection strings (with potential passwords) will be unencrypted
+#*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+# NuGet v3's project.json files produces more ignoreable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+node_modules/
+orleans.codegen.cs
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# CodeRush
+.cr/
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
\ No newline at end of file
diff --git a/src/WIP/Custom_Auth_Extensions/.vscode/extensions.json b/src/WIP/Custom_Auth_Extensions/.vscode/extensions.json
new file mode 100644
index 0000000..dde673d
--- /dev/null
+++ b/src/WIP/Custom_Auth_Extensions/.vscode/extensions.json
@@ -0,0 +1,5 @@
+{
+ "recommendations": [
+ "ms-azuretools.vscode-azurefunctions"
+ ]
+}
\ No newline at end of file
diff --git a/src/WIP/Custom_Auth_Extensions/Custom_Auth_Extensions.csproj b/src/WIP/Custom_Auth_Extensions/Custom_Auth_Extensions.csproj
new file mode 100644
index 0000000..1b214b2
--- /dev/null
+++ b/src/WIP/Custom_Auth_Extensions/Custom_Auth_Extensions.csproj
@@ -0,0 +1,31 @@
+
+
+ net8.0
+ v4
+ Exe
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+ Never
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/WIP/Custom_Auth_Extensions/OnAttributeCollectionStart.cs b/src/WIP/Custom_Auth_Extensions/OnAttributeCollectionStart.cs
new file mode 100644
index 0000000..42ee966
--- /dev/null
+++ b/src/WIP/Custom_Auth_Extensions/OnAttributeCollectionStart.cs
@@ -0,0 +1,24 @@
+using Microsoft.Azure.Functions.Worker;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Custom_Auth_Extensions
+{
+ public class OnAttributeCollectionStart
+ {
+ private readonly ILogger _logger;
+
+ public OnAttributeCollectionStart(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ [Function("OnAttributeCollectionStart")]
+ public IActionResult Run([HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequest req)
+ {
+ _logger.LogInformation("C# HTTP trigger function processed a request.");
+ return new OkObjectResult("Welcome to Azure Functions!");
+ }
+ }
+}
diff --git a/src/WIP/Custom_Auth_Extensions/Program.cs b/src/WIP/Custom_Auth_Extensions/Program.cs
new file mode 100644
index 0000000..80221bb
--- /dev/null
+++ b/src/WIP/Custom_Auth_Extensions/Program.cs
@@ -0,0 +1,13 @@
+using Microsoft.Azure.Functions.Worker.Builder;
+using Microsoft.Extensions.Hosting;
+
+var builder = FunctionsApplication.CreateBuilder(args);
+
+builder.ConfigureFunctionsWebApplication();
+
+// Application Insights isn't enabled by default. See https://aka.ms/AAt8mw4.
+// builder.Services
+// .AddApplicationInsightsTelemetryWorkerService()
+// .ConfigureFunctionsApplicationInsights();
+
+builder.Build().Run();
diff --git a/src/WIP/Custom_Auth_Extensions/Properties/launchSettings.json b/src/WIP/Custom_Auth_Extensions/Properties/launchSettings.json
new file mode 100644
index 0000000..3d6101f
--- /dev/null
+++ b/src/WIP/Custom_Auth_Extensions/Properties/launchSettings.json
@@ -0,0 +1,9 @@
+{
+ "profiles": {
+ "Custom_Auth_Extensions": {
+ "commandName": "Project",
+ "commandLineArgs": "--port 7202",
+ "launchBrowser": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/WIP/Custom_Auth_Extensions/README.md b/src/WIP/Custom_Auth_Extensions/README.md
new file mode 100644
index 0000000..e69de29
diff --git a/src/WIP/Custom_Auth_Extensions/host.json b/src/WIP/Custom_Auth_Extensions/host.json
new file mode 100644
index 0000000..d4f2d41
--- /dev/null
+++ b/src/WIP/Custom_Auth_Extensions/host.json
@@ -0,0 +1,12 @@
+{
+ "version": "2.0",
+ "logging": {
+ "applicationInsights": {
+ "samplingSettings": {
+ "isEnabled": true,
+ "excludedTypes": "Request"
+ },
+ "enableLiveMetricsFilters": true
+ }
+ }
+}
\ No newline at end of file