Friday, December 27, 2019

Several problems when use Set-AzureADApplication cmdlet with AzureAD app with allowPublicClient = true

In order to be able to use username/password credentials authentication flow with AzureAD app it should have allowPublicClient property set to true. As described in documentation this property won’t affect those OAuth flows which use reply urls:

Specifies the fallback application type. Azure AD infers the application type from the replyUrlsWithType by default. There are certain scenarios where Azure AD cannot determine the client app type (e.g. ROPC flow where HTTP request happens without a URL redirection). In those cases Azure AD will interpret the application type based on the value of this property. If this value is set to true the fallback application type is set as public client, such as an installed app running on a mobile device. The default value is false which means the fallback application type is confidential client such as web app.

However setting it true has several side effects. One of them is that you can’t set this property via Set-AzureADApplication cmdlet as there is no such property (Update 2021-04-22: it is possible to change it using another cmdlets - see Change "Allow public client flows" property of Azure AD apps via PowerShell). There is another property “publicClient” and based on this discussion on github when you set it to true/false it also set to true/false “allowPublicClient”. But if you will try to run Set-AzureADApplication and try to specify API permissions via RequiredResourceAccess

Set-AzureADApplication -ObjectId … –RequiredResourceAccess $permissions -PublicClient $true

you will get error:

Property requiredResourceAccess.resourceAccess is invalid.

It is possible to set allowPublicClient to true from UI in Azure portal > Azure active directory > App registrations > app > Authentication > Default client type > Treat application as a public client = Yes:

But after that it won’t be possible to change API permissions via Set-AzureADApplication cmdlet. The following code:

$tokensApp = Get-AzureADApplication | Where-Object { $_.AppId -eq "..." }
$requiredResourceAccess = $tokensApp.RequiredResourceAccess
$graphPermissions = $requiredResourceAccess | Where-Object { $_.ResourceAppId -eq "00000003-0000-0000-c000-000000000000" }
# add Sites.Read.All delegated permission
$perm = New-Object -TypeName "Microsoft.Open.AzureAD.Model.ResourceAccess"-ArgumentList "205e70e5-aba6-4c52-a976-6d2d46c48043","Scope"
$graphPermissions.ResourceAccess.Add($perm)

Set-AzureADApplication -ObjectId $tokensApp.ObjectId -RequiredResourceAccess $requiredResourceAccess

will thrown the same exception:

Set-AzureADApplication : Error occurred while executing SetApplication
Code: Request_BadRequest
Message: Property requiredResourceAccess.resourceAccess is invalid.
Details: PropertyName - requiredResourceAccess.resourceAccess, PropertyErrorCode - GenericError
HttpStatusCode: BadRequest
HttpStatusDescription: Bad Request
HttpResponseStatus: Completed

Note that the same code works properly if allowPublicClient property is set to false. For now I posted this problem in github here. If you know any workarounds for this problem please share it in comments.

Friday, December 20, 2019

Configurable schedule of timer triggered Azure functions

As you probably know it is possible to create Azure functions which will be triggered automatically by schedule specified by CRON expression. In order to do that create timer-triggered Azure function and specify CRON expression like this:

public static class MyFunc
{
 [FunctionName("MyFunc")]
 public static void Run([TimerTrigger("0 */15 * * * *")] TimerInfo myTimer, TraceWriter log)
 {
  ...
 }
} 

Here we defined function which will run every 15 minutes. But with this approach if you will want to change schedule on production env you will need to recompile the package and re-upload it to Azure. It is more convenient to create schedule configurable. In order to do that use the following syntax:

public static class MyFunc
{
 [FunctionName("MyFunc")]
 public static void Run([TimerTrigger("%MyFuncSchedule%")] TimerInfo myTimer, TraceWriter log)
 {
  ...
 }
}

At the same time in AF app settings you need to have MyFuncSchedule app setting:

{
  "IsEncrypted": false,
  "Values": {
    "MyFuncSchedule": "0 */15 * * * *",
  },
  "Host": {
    "CORS": "*"
  }
}

In this case it will be possible to change timer schedule for your timer-triggered Azure function without re-compiling and re-uploading the package.

Note 1. App setting with schedule should be added before to upload package with compiled Azure functions. Otherwise function won’t run.

Note 2. When you change Azure function app settings – currently running instances will be terminated. After that new instances will use updated app settings.

Monday, December 9, 2019

Configure permissions for Azure Function app to access secrets from Azure Key vault

In order to be able to use Azure Key vault from Azure functions at first you need to grant permissions to Azure Function app to read data (in our example we will use Secrets i.e. passwords, app secrets, etc. But you also may use the same technique to access keys and certificates which also may be stored there) from Azure Key vault. At first you need to create System assigned identify for your Azure function from Platform features > Identify:

On this page under System assigned tab set status to On:

After that go to Azure Key vault (if you don’t have it yet than create it first) and select Access policies > Add Access Policy. In opened page select Secret permissions > Get:

(if you store keys or certificates in Key vault you have to select appropriate Key or Certificate permissions).

In Select principal choose name of your Azure Function app. Principal will be available in this field only after creation of Function app principal which we made above.

After that your Azure functions will be able to read values from Azure Key vault. Note that you have to keep it in the following format in app settings:

@Microsoft.KeyVault(SecretUri=https://{key-vault-name}.vault.azure.net/secrets/{secret-name}/{id})

Then you may just read this param from app setting and it will be automatically expanded to the actual secret value stored in Key vault.

Friday, December 6, 2019

Provision language specific embedded resources with Sharepoint project type in Visual Studio

In my previous posts I showed how to provision embedded resources automatically with wsp provisioning:

Provision and automatic update of embedded resources to Resources folder in Sharepoint hive

Provision and automatic update of embedded resources to App_LocalResources folder under Sharepoint Template/Layouts or Template/ControlTemplates sub folders

In this post I will describe how to include language specific embedded resources to wsp using standard Sharepoint project type in Visual Studio.

Let’s create new empty Sharerpoint project, add Resources mapped folder, add Test.resx file there and set it as Embedded resource:

After that let’s use mentioned solution from this post in order to include Test.resx to wsp package (by default Visual Studio adds only those resx files which are set as Content). If we will publish wsp package now it will look like this:

Now let’s add language specific resource Test.ru-RU.resx:

and also add it to wsp using the same technique as for default resource file Test.resx. Now our wsp will contain both resx files which will be provisioned to {hive}/Resources folder:

But this is not enough. When we add language specific embedded resource to solution Visual Studio produces additional assembly xx-XX\{AssemblyName}.resources.dll in output folder (in our example ru-RU\SharePointProject5.resources.dll). This additional assembly should be installed to the GAC together with basic assembly – otherwise code will use only default resources from Test.resx. In order to do it we need to add this assembly to Additional assembles list in Package > Advanced > Additional assembles > Add existing assembly:

(Don’t forget to add language identifier in Location field before assembly name – i.e. ru-RU\SharePointProject5.resources.dll, but not just SharePointProject5.resources.dll. If you will have several additional languages there will be several *.resources.dll assemblies for each of them. By adding language identifier we instruct Visual Studio to put them to appropriate subfolders inside wsp package).

After that both language specific resx file and additional resources.dll assembly will be added to wsp and will be installed automatically during wsp provisioning:

Thursday, December 5, 2019

One reason for Graph API call failure when it is done under delegated permissions

As you probably know you may call Graph API user app-only permissions and user delegated permissions. Here is example of authentication provider which can be used for calling Graph API under delegated permissions (using username and password):

public class AzureAuthenticationProviderDelegatedPermissions : IAuthenticationProvider
{
 public async Task AuthenticateRequestAsync(HttpRequestMessage request)
 {
  var delegatedAccessToken = await GetGraphAccessTokenForDelegatedPermissionsAsync();
  request.Headers.Add("Authorization", "Bearer " + delegatedAccessToken);
 }

 public async Task<string> GetGraphAccessTokenForDelegatedPermissionsAsync()
 {
  string clientId = ...;
  string userName = ...;
  string password = ...;
  string tenant = ...;
  
  var creds = new UserPasswordCredential(userName, password);
  var authContext = new AuthenticationContext(string.Format("https://login.microsoftonline.com/{0}", tenant));
  var authResult = await authContext.AcquireTokenAsync("https://graph.microsoft.com", clientId, creds);
  return authResult.AccessToken;
 }
}

However when you call Graph API with delegated permissions you may get the following error:

AADSTS7000218: The request body must contain the following parameter: 'client_assertion' or 'client_secret'.

The reason may be that app which app id is used for authentication is Default client type is set to private, i.e. “Treat application as a public client” set to No:

In order to fix it set Default client type to Public (set “Treat application as a public client” to Yes).