A few months have passed since the last blog post about Microsoft Graph support in Bicep. To be precise, it has been eight months.
During this time, there have been a few developments in Microsoft Graph Bicep support. On 29 July this year, the module finally became generally available (GA).
To conclude this series, today I would like to show you how you can create an app registration with just a little code, assign API permissions at the application and delegated levels, and approve them directly with admin consent.
I am aware that there is much more that could be done, such as creating an app registration and granting Azure RBAC permissions or creating an app registration with a secret. The principle behind it is always the same. With my three-part blog post on the topic, it is very important to me to familiarise you with the subject matter and Microsoft Graph support in Bicep so that you will then be able to make the necessary enhancements yourself according to your needs.
Of course, if you get stuck somewhere, feel free to contact me via my social media channels.
App registration
For this example, we are not using an existing base, but starting from scratch. In order for the example to run, your app registration that executes it requires the following API permissions:
-
Application.ReadWrite.OwnedBy (Application permission) — allows the app to create and manage applications it owns.
-
DelegatedPermissionGrant.ReadWrite.All (Application permission) — required to create OAuth2 delegated permission grants when granting delegated scopes across the tenant.
-
AppRoleAssignment.ReadWrite.All (Application permission) — required to assign app roles (application permissions) to service principals.
-
Application.Read.All (Application permission) — used to read existing application and service principal details (for example, when looking up the Microsoft Graph service principal).
Create App Registration with API permissions and admin grants
The aim is to create an app registration with the appropriate API permission (application or delegated) and to grant the corresponding API permission with admin consent.
extension microsoftGraphV1 @description('Unique name for the app registration.') param uniqueName string @description('Display name for the app registration.') param displayName string @description('The permission identifier (ID) for the required permission.') param permissionIdentifier string @description('The type of resource access, either "Role" or "Scope".') @allowed([ 'Role' 'Scope' ]) param type string @description('The application ID of the Microsoft Graph API.') param graphAppId string @description('The delegated scope for OAuth2 permission grants, required if type is "Scope".') param delegatedScope string // Create the application (app registration) with the required resource access (API permissions) resource application 'Microsoft.Graph/[email protected]' = { uniqueName: uniqueName displayName: displayName requiredResourceAccess: [ { resourceAccess: [ { id: permissionIdentifier type: type } ] resourceAppId: graphAppId } ] } // Create the service principal for the application resource applicationServicePrincipal 'Microsoft.Graph/[email protected]' = { appId: application.appId } // Reference the Microsoft Graph service principal (existing) so we can use its object id resource graphServicePrincipal 'Microsoft.Graph/[email protected]' existing = { appId: graphAppId } // When the required resource access is a delegated permission (Scope), create an OAuth2 permission grant resource grants 'Microsoft.Graph/[email protected]' = if (type == 'Scope') { clientId: applicationServicePrincipal.id consentType: 'AllPrincipals' resourceId: graphServicePrincipal.id scope: delegatedScope } // When the required resource access is an application permission (Role), create an app role assignment grant resource appRoleAssignment 'Microsoft.Graph/[email protected]' = if (type == 'Role') { principalId: applicationServicePrincipal.id resourceId: graphServicePrincipal.id appRoleId: permissionIdentifier } // Outputs output applicationAppId string = application.appId output applicationObjectId string = application.id output applicationServicePrincipalId string = applicationServicePrincipal.id
// Extensions extension microsoftGraphV1 // Parameters @description('Unique name for the app registration.') param uniqueName string @description('Display name for the app registration.') param displayName string @description('The permission identifier (ID) for the required permission.') param permissionIdentifier string @description('The type of resource access, either "Role" or "Scope".') @allowed([ 'Role' 'Scope' ]) param type string @description('The application ID of the Microsoft Graph API.') param graphAppId string @description('The delegated scope for OAuth2 permission grants, required if type is "Scope".') param delegatedScope string // Resources module application 'modules/application.bicep' = { name: 'application' params: { uniqueName: uniqueName displayName: displayName permissionIdentifier: permissionIdentifier type: type graphAppId: graphAppId delegatedScope: delegatedScope } }
main.bicepparam
using'./main.bicep' // Parameters param uniqueName = 'MSGraphBlogExample' param displayName = 'MS Graph Blog Example' param permissionIdentifier = '5b567255-7703-4780-807c-7be8301ae99b' param type = 'Role' param graphAppId = '00000003-0000-0000-c000-000000000000'// Microsoft Graph param delegatedScope = ''
Result
After executing the Bicep code, an app registration named MS Graph Blog Example is created. In addition, the API permission Group.Read.Allis added as an application permission, which has been granted directly.
The complete code of this example can be found here: Github example: application-add-api-grants
Conclusion
It’s great to see that Microsoft Graph support for Bicep has been generally available since the end of July 2025. This definitely gives us more confidence for the future to try out the module and then use it productively.
However, I have personally noticed that the module documentation is relatively inadequate. Most parameters are described, but permissible values are not specified. This costs time, especially when reverse engineering a function.
Nevertheless, I am looking forward to many more features, and who knows, perhaps in the future it will be possible to replace the PowerShell modules used today with Microsoft Graph for Bicep and integrate them into your existing CI/CD pipeline.