Skip to content

Commit

Permalink
Merge branch 'main' into dkershaw10-users-sample
Browse files Browse the repository at this point in the history
  • Loading branch information
dkershaw10 authored Jan 29, 2025
2 parents 0295beb + 16f39aa commit f37c24f
Show file tree
Hide file tree
Showing 5 changed files with 219 additions and 0 deletions.
56 changes: 56 additions & 0 deletions quickstart-templates/apps-permissions-and-grants/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Configure an app with OAuth2.0 scopes to call Microsoft Graph

This template sample creates a client application and depending on the mode parameter, either:

1. Sets required resource access on the client application definition, or
2. Grants OAuth2.0 scopes to the client application.

In either case, the target resource used is Microsoft Graph, and the deployer can select which Microsoft Graph OAuth2.0 scopes are used.

## Details

This sample operates in two modes, depending on the `mode` parameter.

1. `set-required-scopes`:

- The sample creates a basic client application configuring the `requiredResourceAccess` property with Microsoft Graph Oauth2.0 scopes
- This option uses the appRequiredResourceAccess.bicep module.
- **NOTE:** The `requiredResourceAccess` configures which permissions the client application requires and this drives the **user consent experience** where those permissions can be granted. `requiredResourceAccess` itself does **not** grant any permissions to the client application.

2. `grant-scopes`:

- The sample creates a basic client application (and does not configure `requiredResourceAccess`), creates a service principal from the application, and finally grants the desired Microsoft Graph OAuth2.0 scopes to the service principal (using the `Microsoft.Graph/oauth2PermissionGrants` bicep type).
- This option uses the appGrantScopes.bicep module.
- **NOTE:** Setting `requiredResourceAccess` on a client application is **not** required to grant OAuth2.0 scopes to the client application.

The `appScopes` array parameter allows the deployer to select the Microsoft Graph Oauth2.0 scopes to set for or grant to the client application. The sample validates the set of provided scopes in the array parameter against [Microsoft Graph delegated permission scopes][graph-permissions]. Any invalid scopes provided are ignored. `appScores` should contain a list of scope names (for example *User.Read.All* and *Group.ReadWrite.All*).

### Prerequisites

- A valid **Azure subscription**: If you don't own an Azure subscription, [create a free account](https://azure.microsoft.com/free/) before you begin.
- An **Azure resource group** that you own under a valid Azure subscription, or [deploy without an Azure subscription][no-azure-sub].
- [Bicep tools for authoring and deployment](https://learn.microsoft.com/graph/templates/quickstart-install-bicep-tools). The minimum required Bicep version is v0.30.3.
- Have the requisite **Microsoft Entra roles** to deploy this template:

- Permissions to create applications. [Users have this permission by default](https://learn.microsoft.com/entra/fundamentals/users-default-permissions#compare-member-and-guest-default-permissions). However, [admins can turn off this default](https://learn.microsoft.com/entra/fundamentals/users-default-permissions#restrict-member-users-default-permissions) in which case you need to be assigned at least the [Application Developer](https://learn.microsoft.com/entra/identity/role-based-access-control/permissions-reference#application-developer) role.
- **Additionally**, if using the `grant-scopes` mode, you'll also need the privileges to grant Microsoft Graph app roles to the application. This requires the [Privileged Role Administrator][priv-role-admin]

### Deploy the Bicep template

By default, the Bicep template (main.bicep) will operate in the `set-required-scopes` mode.

#### Az CLI

```sh
az deployment group create --resource-group <resource-group> --template-file main.bicep --parameter date='2025-01-24' appScopes="['User.Read','Application.Read.All']"
```

#### Az Powershell

```powershell
New-AzResourceGroupDeployment -ResourceGroupName <resource-group> -TemplateFile .\main.bicep -date "2025-01-24" -appScopes @('User.Read','Application.Read.All')
```

[priv-role-admin]:https://learn.microsoft.com/entra/identity/role-based-access-control/permissions-reference#privileged-role-administrator
[graph-permissions]:https://learn.microsoft.com/graph/permissions-reference
[no-azure-sub]:https://learn.microsoft.com/graph/templates/how-to-deploy-without-azure-sub?view=graph-bicep-1.0&tabs=CLI
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
extension microsoftGraphV1

// TEMPLATE DESCRIPTION
/* Grant OAuth2.0 scopes to a client application definition,
where the target resource used is Microsoft Graph, and the deployer can select which
Microsoft Graph OAuth2.0 scopes are granted on the client app.
NOTE: Setting requiredResourceAccess on a client application is NOT required
to grant OAuth2.0 permissions to the client application.
*/

param date string
param displayName string?
param filteredScopes array
param graphSpId string

var app = 'myApp'

// convert scopes array into space separate scopes string
var scopeArray = [for (scopeItem,i) in filteredScopes: filteredScopes[i].value]
var scopeString = join(scopeArray, ' ')

// create basic app
resource myApp 'Microsoft.Graph/[email protected]' = {
displayName: displayName == null ? '${app}-${date}' :'${displayName}-${app}-${date}'
uniqueName: uniqueString(app, date)
}

// Create service principal for the basic app
resource mySP 'Microsoft.Graph/[email protected]' = {
appId: myApp.appId
}

// Grant the OAuth2.0 scopes (requested in parameters) to the basic app,
// for all users in the tenant
resource graphScopesAssignment 'Microsoft.Graph/[email protected]' = {
clientId: mySP.id
resourceId: graphSpId
consentType: 'AllPrincipals'
scope: scopeString
}

// output information
output appName string = myApp.displayName
output appObjectID string = myApp.id
output appID string = myApp.appId
output scopes array = scopeArray
output grantedScopes string = graphScopesAssignment.scope
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
extension microsoftGraphV1

// TEMPLATE DESCRIPTION
/* Set the required resource access on a client application definition.
The target resource used is Microsoft Graph, and the deployer can select which
Microsoft Graph OAuth2.0 scopes are configured on the client app.
NOTE: requiredResourceAccess configures which permissions the client application
requires and this drives the user consent experience where permissions are granted.
requiredResourceAccess itself does NOT grant any permissions to the client application.
*/

param date string
param displayName string?
param filteredScopes array

var app = 'myApp'
var graphAppId = '00000003-0000-0000-c000-000000000000'

// create an application with the requiredResourceAccess property
// creates a resourceAccess scope for each Microsoft Graph scope in filteredScopes
resource myApp 'Microsoft.Graph/[email protected]' = {
displayName: displayName == null ? '${app}-${date}' :'${displayName}-${app}-${date}'
uniqueName: uniqueString(app, date)
requiredResourceAccess: [
{
resourceAppId: graphAppId
resourceAccess: [ for (scope, i) in filteredScopes: {
id: filteredScopes[i].id
type: 'Scope'
}
]
}
]
}

// output information
output appName string = myApp.displayName
output appObjectID string = myApp.id
output appID string = myApp.appId
output scopes array = [for (scopeItem,i) in filteredScopes: filteredScopes[i].value]
output clientAppResourceAccessList array = myApp.requiredResourceAccess[0].resourceAccess
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"experimentalFeaturesEnabled": {
"extensibility": true
},
// specify an alias for the version of the v1.0 dynamic types package you want to use
"extensions": {
"microsoftGraphV1": "br:mcr.microsoft.com/bicep/extensions/microsoftgraph/v1.0:0.1.9-preview"
}
}
64 changes: 64 additions & 0 deletions quickstart-templates/apps-permissions-and-grants/main.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
extension microsoftGraphV1

// TEMPLATE DESCRIPTION
/* Create a client application and depending on the mode parameter, either:
1. Sets required resource access on the client application definition OR
2. Grants OAuth2.0 scopes to the client application
In either case, the target resource used is Microsoft Graph, and the deployer
can select which Microsoft Graph OAuth2.0 scopes are used.
This bicep file utilizes two modules (one for each mode).
*/

@description('Supply today\'s date to deploy the template')
param date string

@description('Provide a friendly display name for the app')
param displayName string?

@description('Provide an array of Microsoft Graph scopes like "User.Read"')
param appScopes array = ['profile','User.Read']

@description('Configure is setting required resource access or granting scopes')
@allowed(['set-required-scopes','grant-scopes'])
param mode string = 'set-required-scopes'

var graphAppId = '00000003-0000-0000-c000-000000000000'

// Get the Microsoft Graph service principal so that the scope names
// can be looked up and mapped to a permission ID
resource msGraphSP 'Microsoft.Graph/[email protected]' existing = {
appId: graphAppId
}

var graphScopes = msGraphSP.oauth2PermissionScopes
var filteredScopes = filter(graphScopes, scope => contains(appScopes, scope.value))


module appCreateRraModule './appRequiredResourceAccess.bicep' = if(mode == 'set-required-scopes'){
name: 'appRraDeploy'
params: {
filteredScopes: filteredScopes
date: date
displayName: displayName
}
}

module appCreateGrantScopesModule './appGrantScopes.bicep' = if (mode == 'grant-scopes') {
name: 'appScopeGrantDeploy'
params: {
filteredScopes: filteredScopes
date: date
displayName: displayName
graphSpId: msGraphSP.id
}
}

// outputs
output appName string = ((mode == 'set-required-scopes') ? appCreateRraModule.outputs.appName : appCreateGrantScopesModule.outputs.appName)
output appObjectID string = ((mode == 'set-required-scopes') ? appCreateRraModule.outputs.appObjectID : appCreateGrantScopesModule.outputs.appObjectID)
output appID string = ((mode == 'set-required-scopes') ? appCreateRraModule.outputs.appID : appCreateGrantScopesModule.outputs.appID)
output foundInputScopes array = ((mode == 'set-required-scopes') ? appCreateRraModule.outputs.scopes: appCreateGrantScopesModule.outputs.scopes)
output clientAppResourceAccessList array = ((mode == 'set-required-scopes') ? appCreateRraModule.outputs.clientAppResourceAccessList : ['Not set'])
output grantedScopes string = ((mode == 'grant-scopes') ? appCreateGrantScopesModule.outputs.grantedScopes : 'Not set')

0 comments on commit f37c24f

Please sign in to comment.