Category Archives: Azure AD

Blog Series – Power’ing up your Home Office Lights: Part 3 – Using Logic Apps to Authorize and Get Access Token using Oauth and Hue Remote API

This blog post is part of the Blog Series: Power’ing up your Home Office Lights with Power Platform. See introduction post for links to the other articles in the series:
https://gotoguy.blog/2020/12/02/blog-series—powering-up-your-home-office-lights-using-power-platform—introduction/

Now that we have registered the application for Hue Remote API, and stored the client Id and Secret in Azure Key Vault, we can start build the Logic App that will authorize and get access token via Oauth2.

Here is a short video where I introduce the concept:

Create the Logic App and HTTP Trigger

The first thing you need to do, is to create a new Logic App in your Azure subscription. Select the Resource Group you have contributor access to, and give the Logic App a suitable name, as per your naming guidelines:

Next, select HTTP request to be the trigger for the Logic App:

After that you will see the following in the Logic App designer:

Make sure you hit Save on the Logic App before the next step. You will now be shown the URL, but first go down to the “Add new parameter” and select Method and GET for method. This way your Logic App will trigger on HTTP GET requests, which is required for the Authorization Code flow with Hue:

Now copy this URL, and make sure that no one but you can access this URL, as this is a SAS (Shared Access Signature) URL that anyone in the world can send requests to if they know the URL. Save the Logic App.

Go back to your App Registration in the Hue Developers Portal, and change your temporary http://localhost/logicapp Callback URL to your Logic App URL:

This means that from now on, the Logic App will handle the authorization code for the Hue App. But first we will need to let the Logic App access the Key Vault.

Adding Logic App Identity and Key Vault Access

For the Logic App, under Settings and Identity, set system assigned managed identity to On:

After this setting is saved, you can later see the status and the object id of the service principal.

Next, under your Key Vault, click on Access Policies and Add Access Policy, from there selec the Get, Set and List Secret Management operations, and for principal search for and add the Logic App. This should look like this after adding:

With permissions in place, we are now ready to add actions to the Logic App.

Add Actions for getting Access Token

The first ting we need to do is to get the authorization code after the Hue App registration redirects back to the callback URL. This is returned as a querystring appended to the URL. Add a “Compose” action and use the following expression for getting the request queries:

(I’ve added the custom expression to comments for better visibility).

Then we need to get the Client Id and Secret from the Kay Vault. Add a HTTP action next, where we will use the Azure Rest API for getting the secret, and authenticate with the Managed Service Identity:

The URI above points to my Azure Key Vault URI, and the specified secret. The documentation for getting secrets can be seen here: Get Secret – Get Secret (Azure Key Vault) | Microsoft Docs.

The same applies to getting the Client Secret, add another HTTP action:

The get an access token from Hue Remote API we must either use Basic Authentication or Digest Authentication. Since I’m running this as a Logic App in a controlled environment and trusting the SSL encryption I will use Basic Authentication. In addition, I will secure the outputs from getting Client Id and Secret from Key Vault, so that other users cannot see those values from the run history:

This setting has been enabled for both the actions getting KV Secret Client Id and Client Secret:

For obtaining an Access Token with Basic Authentication the following header is required: Authorization: Basic <base64(clientid:clientsecret)>

This means that we need to base64 encode the clientid + “:” + clientsecret. This can be done using this lengthy expression, here in a “initialize variable” action:

This above action is just for reference though, as the HTTP action supports base64 encoding out of the box. So when posting to the token endpoint, the best way is to use the following:

(PS! Another way to do Basic Authentication in a HTTP action would be to add the Authorization header manually in the Action above with: “Basic <your calculated base64>” as value, and leaving the Authentication type to None.)

From the above settings, in the URI add the authorization code we got from the request queries earlier, using the expression:

outputs('Compose_Authorization_Code')?['code']

When selecting Authentication type to Basic, the username (client id) and password (secret) will automatically be base64 encoded. The values for username and password are the valies from the Get KV Secret Client actions earlier, in the following format:

body('Get_KV_Secret_Client_Id')?['value']

And this action will return the Bearer Token from Hue Remote API if everything is correctly inputted.

I also make sure that the output of this action is secured from viewing:

With the Bearer Token now retrieved, the next actions is to calculate the expiry time and write the Token back to the Key Vault secret.

Add Actions for getting expiry time and write Token to Key Vault

The Bearer Token returned by Hue Remote API will be in the format of the following masked response:

The _expires_in values are in seconds, so that means that the Access Token is valid for 7 days, and the Refresh Token about 112 days. It would then make sense to only refresh the token when needed.

Lets start by calculating when the access_token expires, with the following expression in a Compose action:

addSeconds(utcNow(), int(body('Post_Auth_Code_with_Basic_Auth_to_Get_Access_Token')?['access_token_expires_in']))

The above expression takes the current time and add the number of seconds for when the access token expires.

This will return a new datetime 7 days ahead. This value will be used to set the expiry time on the Key Vault secret for Bearer Token. By setting an expiry time I can later calculate if I need to refresh the Access Token or not.

But it’s not that easy.. The calculated time above will need to be converted to Epoch (32-bit “Unix”) integer format to be able to set the Key Vault secret “exp” attribute. This isn’t so clear when seeing the API docs, Set Secret – Set Secret (Azure Key Vault) | Microsoft Docs, so took me a little trial and error. And I found great help in this blog article: https://devkimchi.com/2018/11/04/converting-tick-or-epoch-to-timestamp-in-logic-app/.

Based on this I need to convert the timestamp to Ticks (64-bit). Ticks is a built in function in Logic Apps, but to be able to convert to Epoch I will need to calculate the difference in ticks between when the Access Token expire, and the first value of Epoch which is 1970-01-01T00:00:00Z. This is well explained in the above reference blog, but here are my resulting actions.

After calculating the Access Token expiry, I add a compose action which converts this to Ticks:

Using the following expression: ticks(outputs('AccessToken_Expires_Utc'))

Then I need to find the 1970-01-01T00:00:00Z value in Ticks:

Using this expression: ticks('1970-01-01T00:00:00Z')

Then we can convert this to Epoch by subtracting the two Ticks values calculated above, and divide by 1 million:

This is the expression used above: div(sub(outputs('GetTimestampInTicks'), outputs('Get1970TimestampInTicks')), 10000000)

We now have the correct format for the “exp” attribute to update the Key Vault secret. Add a new HTTP action and configure like below:

Remember to secure the Output for this action also:

Finally we can finish this Logic App by adding a Response action and do a quick test to verify that everything works as expected.

Adding Response action and verify Logic App

Add a Response action with status code 200 and a body like below:

Tips: It can be difficult to troubleshoot the Logic App when securing outputs, so you might hold back in that when testing. It will show your secrets in the run history though, so it might be best to do this in a test enviroment depending on your needs.

Now we can test. Construct the URL for authorizing the App again, like we did in Part 1:

https://api.meethue.com/oauth2/auth?clientid=&response_type=code&state=elvenanystring&appid=elven_demo_hue_app&deviceid=elven_demo&devicename=Elven Demo

Paste it in the Browser, and after granting access to the App in Hue Developer portal:

You should be redirected to the Logic App:

.. and with a respons success!

Looking at the Run history, we can verify the steps were successful:

You can also look into the inputs and outputs of the actions, except the actions where we secured the output:

We can also verify that the Key Vault secret storing the Bearer Token has been updated and have an Expiration Date one week forward:

Summary and next steps

That concludes this blog post. Thanks for reading this far, in the next part we will build the Logic App that will respond back the Access Token and renew using Refresh Token if needed.

Speaking at Cloud Identity Summit ’20

Tomorrow Thursday October 22nd, at 17:40 CET, I will be speaking an Online Session on the Identity Management Track at Cloud Identity Summit ’20.

My session will be about the Identity Governance solutions available i Azure Active Directory, we will cover the following topics in an hour long session:

  • Identity Access and Privileged Access Lifecycle
  • Entitlement Management
  • Access Reviews
  • Terms of Use
  • Privileged Identity Management (PIM)
  • .. and demos!

If you are new to Azure AD Identity Governance, I promise you will get a good overview over the value these solutions will provide and the challenges this will help solve. If you have been using some of these solutions from before, I will provide some tips and tricks, and real life experiences as well.

You can sign up to the FREE Cloud Identity Summit ’20 here: http://identitysummit.cloud/. There are some great other sessions also, in two tracks “Management” and “Security”. You can learn more about Azure AD Sync, Azure AD B2B, Identity Protection, Passwordless, Yubikeys and AAD Internals! Full agenda here: Agenda – Cloud Identity Summit 2020.

Hope to see you tomorrow!

Oauth Authentication to Microsoft Graph with PowerShell Core

In this short blog post I will show you a really simple way to get started authenticating with Oauth to Microsoft Graph using PowerShell Core.

As you might know, PowerShell Core is the open-source, cross-platform (Windows, macOS, and Linux) edition of PowerShell, with the latest version today being PowerShell 7.0 ( https://docs.microsoft.com/en-us/powershell/scripting/whats-new/what-s-new-in-powershell-70?view=powershell-7). The latest version of Windows PowerShell is 5.1, and runs side-by-side with PowerShell Core.

I’ve been using Windows PowerShell mostly previously, as I’m running Windows 10 and use a lot of PowerShell modules that variously depend on SDKs or other Windows Components, but I’m increasingly using PowerShell Core in scenarios for example with Azure Functions PowerShell ( https://docs.microsoft.com/en-us/azure/azure-functions/functions-reference-powershell). My computer now runs Visual Studio Code and Windows Terminal with different Profiles, so that I can easily change between environments and run side-by-side:

This blog post isn’t about the details of PowerShell though, so lets get back to the topic at hand. When running PowerShell against Microsoft Graph you would run requests using the command Invoke-RestMethod. The thing that caught my attention was the added parameters of specifying Authentication and Token with the request: https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/invoke-restmethod?view=powershell-7#parameters.

Let’s compare the “old” way and the “new” way, but first lets get an Access Token for Microsoft Graph.

Get Access Token using Graph Explorer

If you want to learn Microsoft Graph and start with some sample queries, Graph Explorer should be your main starting point: https://developer.microsoft.com/en-us/graph/graph-explorer/preview. The new preview version of the Graph Explorer lets you get a copy of your Access Token if you sign in with your account as shown below, you can copy it to your clipboard:

This Access Token will run queries as the logged on user (yourself) using the consented permissions in Graph Explorer, so you should only use this token for your own sample queries and not share it with anyone else.

Preferably you would get an Access Token by registering your own App Registration in Azure AD and authenticate using one of the following authentication flows:

  • Authorization Code Flow (Delegated User – Web Apps and APIs)
  • Device Code Flow (Delegated User – Native Clients, recommended for PowerShell)
  • Client Credentials Flow (Application Permissions – Scripts and Background Processes that don’t require user interactivity)

But for now I would use use the Access Token from Graph Explorer for the following samples.

The “old” way, using Authorization Header with Bearer Token

This method works with both Windows PowerShell and PowerShell Core. After aquiring an Access Token you would need to specify an Authorization Header with a Bearer Token for every request. In this example I get the Access Token from the Clipboard, as I copied that from the Graph Explorer.

$accessToken = Get-Clipboard
Invoke-RestMethod -Method Get -Uri "https://graph.microsoft.com/v1.0/me" -Headers @{"Authorization"="Bearer $accessToken"}

This should return a response like below:

The “new” way, using Authentication and Token Parameters

This method only works on PowerShell Core. The Access Token needs to be a secure string, so you need to convert it first like the following:

$accessToken = ConvertTo-SecureString (Get-Clipboard) -AsPlainText -Force

Another way would be to prompt for the Access Token and paste it with the following command:

$accessToken = Read-Host -AsSecureString -Prompt "Enter/Paste Oauth2 Token"

And then you would run the request specifying the Authentication parameter to be Oauth, and the Token parameter to be the secure string:

Invoke-RestMethod -Method Get -Uri "https://graph.microsoft.com/v1.0/me" -Authentication OAuth -Token $accessToken

This should return a response like below:

Summary

So in summary you can start using the Authentication and Token parameters in PowerShell Core, it would add / override the Authorization Header and Bearer Token for you. There can also be some added security gains as the Token is submitted as a securestring, which means you can try to keep it out of code as clear text.

Explore Microsoft Graph as a B2B Guest Account

The Microsoft Graph Explorer (https://aka.ms/ge) is always a great learning source and useful tool for querying the Microsoft Graph, especially as you can use your own Azure AD work account or Microsoft account to query for real data. Another advantage with the Graph Explorer is that you can use it without requiring an App Registration in your tenant, something most users are not able to do themselves as they don’t have the administrator rights for registering apps.

However, sometimes working with Microsoft Graph, I find myself in a scenario where I want to use my own work account, but where the Microsoft Graph resources I want to query is in another Organization’s tenant. These kind of scenarios is usually where my account is invited as a B2B guest user to the resource tenant, and currently there are no way to use the Graph Explorer tool to do that.

So I need another tool, and as an IT pro I could easily write myself some PowerShell code, or as a developer I could create a Web app or Console app querying Graph, and run the queries against the other tenant from there.

On the other hand, I want to do this more inline with the Graph Explorer experience, so the most logical choice for me is to use a tool where I can just run the REST API queries I like. And the most popular tool, both by me and many others are the “Postman” client. You can get it yourself for free at https://www.getpostman.com, both Windows, Linux and Mac downloads are supported!

So in this blog post I will show how I use Postman to query Microsoft Graph in a B2B Guest User scenario.

Requirements & Preparation

So, first you will need to get invited to another tenant where the resources you wan’t to query is. You might already have this Azure AD B2B invitation accepted previously. If you aren’t an administrator in that tenant, you will have to ask someone that has the rights to invite to do that for you.

Next, you will need to get assigned permissions to the resources you want to query in the resource tenant. This is all dependent on what kind of queries you want to run, whether it is for reading, writing or deleting resources for example.

Then, you will need an Global Administrator in that resource tenant to create an App Registration. The following instructions and screenshots can be used as a guide:

First, under the Azure AD experience in the Azure Portal, go to App Registrations and create a new Registration, type a name and a redirect URI like shown below:

image

You can type any name you like, for example in this scenario I want to use it for querying identities. I choose to support accounts in this organizational directory only, as this app registration is for members or guests from this tenant. And since I will be using the Postman i specify the redirect URI of “https://www.getpostman.com/oauth2/callback”. This is important as when I authenticate from Postman later the response will be returned to the Postman client.

When accessing Microsoft Graph you have to authenticate using one of the Oauth2 flows, and the most common is using authorization code flow, (https://developer.microsoft.com/en-us/graph/blogs/30daysmsgraph-day-12-authentication-and-authorization-scenarios/), which is exactly this scenario is all about as I will authenticate on behalf of my guest user account in the resource tenant. That means that I will have to add some delegated Graph permissions to the app registration, and in this example I add User.ReadBasic.All, this will make me able to query users via Graph:

image

After granting Admin consent for the permissions, I can verify that the permissions are added correctly:

image

Next I will need to create a client secret to be used in the request to get an access token, go to certificates & secrets and add a client secret of chosen time expiry:

image

Copy and make sure you save the client secret for later next:

image

Then copy the application id and tenant id and save for later:

image

Click on endpoints (see arrow above), and note the Oauth2 authorization and token endpoints (v2), this endpoints contains the tenantid:

image

With these steps the requirements are complete and we can move on to the postman client.

Authenticating and Querying Graph with Postman

There are a lot of features in the Postman client that you should look into when working with REST API’s. You can organize your queries in Collections, save variables in Environments, synchronize your requests across devices and so on. But for now we will start simple and easy.

In the main canvas at your workspace, create a new query, and for example start with https://graph.microsoft.com/v1.0/me:

image

Don’t push Send just yet, we need to authenticate first. Go to the Authorization section under the request, and select Oauth2, then click Get New Access Token:

image

In the following dialog fill in your details as shown below, where:

  • Token Name: You can type what you want here, this is named reference to the Access Token you will aquire.
  • Grant Type: As earlier mentioned, running graph queries as the logged in user (delegated) use the Authorization Code Oauth2 flow.
  • Callback URL: This is the URL that you specified earlier for Redirect URI under the Azure AD App Registration.
  • Auth URL and Access Token URL: These are the URL’s you saw earlier from the Endpoints setting for the Azure AD App Registration (contains the Tenant ID).
  • Client ID: This is the Application ID for the App Registration.
  • Client Secret: This is the secret key you generated under the App Registration.
  • Scope: Provide a default scope, use the default.
  • State: Scope is used for creating application logic that prevents cross use of Access Tokens. You can type anything you want here.
  • Client Authentication: Select to Send client credentials in body when working with Graph requests.
image

When you click request token, you will be taken to the resource tenant for authenticating with your delegated user. Type in your username and click next. This username can either be a normal user that belongs to this resource tenant, or in this case you can log in with your B2B Guest Account:

image

Now dependent on any Conditional Access policies and settings you might be required to approve sign in:

image

After successfully authenticating I should receive a valid Access Token:

image

If you get an error here, please verify your App Registration settings and that the account you logged in as is correct.

Scroll down and click Use Token:

image

You will see that the Access Token is now filled in the Access Token textbox. Next click Preview Request down to the left, this will add the Access Token to the request as a authorization header:

image

If you click on Headers, you can see the Authorization header has been added with the access token as a Bearer Token value:

image

This Access Token is now valid for 1 hour, and you can run as many requests you like as long as you are inside the delegated permissions (for the App Registration) and the logged in users actual permissions. After 1 hour you can request a new Access Token.

PS! Postman will save the authenticated user session in cookies, if you want to log in as a different user clear those cookies, se “cookies” link right below the Send button.

So, now lets run this query, click Send at verify the response, you should get details for your guest user. From the screenshot below you can clearly see that this user is a Guest account looking at the userPrincipalName attribute:

image

Lets try another query, in this query I list all users that has userPrincipalName that starts with “Jan”, and showing only the displayName and userPrincipalName attributes:

image

As you can see from the result above, I have several guest accounts in this tenant (Microsoft Account, Google, Azure AD) as well as a normal user account. You can also see that the Postman client is helpful in specifying my parameters.

Inspecting a B2B Guest Access Token

If you copy the Access Token you got earlier, and paste it into a site like jwt.ms or jwt.io, we can take a look at the access token contents and claims:

image

If I scroll down a little I see the displayname of the App Registration, but the most important info is the mail claim, which for Guest users will be the external e-mail address. Idp is the source authority for the Guest account, in this case another Azure AD tenant with the Tenant Id as shown below:

image

Working with Environments

Chances are that you might work with several environments in Postman, and that where it’s useful to create environment variables. For example create an environment like shown below:

image

That way you can select which environment you want to work with when running queries, and when referencing variables use “{{ .. }}”. For example under Get New Access Token, change to this:

image

Now, lets finalize this blog post by logging in with another guest account, I will choose my Gmail account, I’ve already set up Google Federation and invited this user to my tenant.

First I need to clear the cookies:

image

Next I will click to Get a New Access Token again, and then authenticate as my Google account, which directs me to the Google login page:

image

After successfully authenticating, and using the new Access Token in the Authorization Header, I can run the basic /me query again, this time showing me that I’m now authenticated to Graph with my Gmail user:

image

And looking inside the Access Token again, I can see that the e-mail address is my gmail and the idp is now google.com:

image

If I had logged on with a Microsoft Account the idp value would have been “live.com”.

Next steps

So, now you know how to authenticate to and query Microsoft Graph with an Azure AD B2B Guest User. I really hope this functionality will come to Graph Explorer eventually, but for now Postman is already an awesome free tool for organizing and running your Microsoft Graph queries that I use a lot myself.

The Microsoft Graph Team has also published this source for a lot of useful collections of Graph queries:

https://github.com/microsoftgraph/microsoftgraph-postman-collections

Exploring Azure MFA sign-in failures using Log Analytics

Most IT admins, pros and end users from organizations that use Office 365 and Azure AD will by now have heard about the big Azure MFA outage on Monday November 19. When something like this happens, it is important to get insights on which users that were affected, and in what type of scenarios they most experienced the problem. Microsoft MVP Tony Redmond wrote a useful blog post (https://office365itpros.com/2018/11/21/reporting-mfa-enabled-accounts/) on how to report on possible/potentially affected and MFA enabled users, and how to disable and re-enable those users. But many organizations are now using Conditional Access policies using Azure AD Premium, so this will be of limited help for those.

If I could wish one thing from Microsoft for Christmas this year, it would be to be able to manage MFA and Conditional Access policies with Azure AD PowerShell and Microsoft Graph! Admins could then run “break-the-glass” administrative users (or even “break-the-glass service principals”) to disable/re-enable policies when big MFA outages happens. A good CA policy design, trusting compliant devices and secure locations could also go a long way in mitigating such big outage problems.

Tony’s blog post made me think about the feature I recently activated, on integrating Azure AD Activity Logs to Azure Log Analytics, you can read more about this here: https://gotoguy.blog/2018/11/06/get-started-with-integration-of-azure-ad-activity-logs-to-azure-log-analytics/

By exploring the Sign-in logs in Log Analytics I could get some more insights into how my organization was affected by the MFA outage on November 19. Please see the above blog post on how to get started setting up this integration, the rest of this blog post will show some sample queries for the SigninLogs.

Querying the SigninLogs for failed and interrupted sign-ins

All the queries seen below are shown in screenshot images, but I have listed all them for you to copy at the end of the blog post.

First I can take a look at the SigninLogs for the specific day of 19th November, and the grouping on the result type and description of the sign-in events. For example I can see that there is a high number of event 50074: User did not pass the MFA challenge. Interestingly there is also a relatively high number of invalid username or password, that could be a separate issue but could also be that users that fails MFA sign-ins tries to log in again thinking they had wrong password first time.

image

Changing that query a little, I can exclude the successful sign-ins (ResultType 0), and sort on the most count of failures. Two of the events of most interest here is 50074 and 50076:

image

In this next query I focus on the “50074: User did not pass the MFA challenge” error. By increasing the time range to last 31 days, and adding a bin(TimeGenerated, 1d) to the summarize group, I’ll be able to see the count of this error on each day in the last month. This will give me a baseline, and we can see that on the 19th this number spikes. I have added a render to timechart for graphical display. There are also some other days where this number increases, I can look into more insights for that if I want as well, but for now I will focus on the 19th.

image

Back to the time range of the 19th of November, I can modify the summarize to group by each hour, by using bin(TimeGenerated, 1h). This will show me how the problems evolved during the day. Must errors occurred about 10 am in the morning:

image

Lets look at some queries for how this error affected my environment. First I can group on the Users and how many errors they experience. Some users were really persistent in trying to get through the MFA error. I have masked the real names. We also see some admin accounts but admins quickly recognized that something was wrong, and actively sought information on the outage. By midday most users were notified on the on-going outage and the number of errors slowly decrease during the day.

image

In this next query, I group on the Apps the users tried to reach:

image

And in this following query, what kind of Client App they used. It would be normal that Browser is quite high, as mobile apps and desktop clients are more likely to have valid refresh tokens.

image

In this query I can look into the device operating system the users tried to sign in from:

2018-11-21_22-05-27

In the following query I can look at which network the users tried to log in from, by identifying IP address:

image

And in this query we can get more location details from where users tried to sign in:

2018-11-21_22-07-17

Summary

Querying Log Analytics for Sign-in events as shown above can provide valuable insights into how such an outage can affect users. This can also give me some input on how to design Conditional Access policies. Querying this data over time can also provide a baseline for normal operations in your environment, and make it easier to set alert thresholds if you want to get alerts when number of failures inside a time interval gets higher than usual. Using Azure Monitor and action groups you can be pro-active and be notified if something similar should occur again.

Here are all the queries shown above:

// Look at SigninLogs for a custom date time interval and group by sign in results
SigninLogs
| where TimeGenerated between(datetime("2018-11-19 00:00:00") .. datetime("2018-11-19 23:59:59")) 
| summarize count() by ResultType, ResultDescription

// Exclude successful signins and format results with sorting
SigninLogs
| where TimeGenerated between(datetime("2018-11-19 00:00:00") .. datetime("2018-11-19 23:59:59")) 
| where ResultType != "0" 
| summarize FailedSigninCount = count() by ResultDescription, ResultType 
| sort by FailedSigninCount desc

// Look at User did not pass the MFA challenge error last month to see trend
// and present to line chart group by each 1 day
SigninLogs
| where TimeGenerated >= ago(31d)
| where ResultType == "50074"
| summarize FailedSigninCount = count() by ResultDescription, bin(TimeGenerated, 1d)
| render timechart

// Look at User did not pass the MFA challenge error on the MFA outage day
// and present to line chart group by each 1 hour to see impact during day
SigninLogs
| where TimeGenerated between(datetime("2018-11-19 00:00:00") .. datetime("2018-11-19 23:59:59")) 
| where ResultType == "50074" 
| summarize FailedSigninCount = count() by ResultDescription, bin(TimeGenerated, 1h)
| render timechart

// Look at User did not pass the MFA challenge error on the MFA outage day
// and group on users to see affected users
SigninLogs
| where TimeGenerated between(datetime("2018-11-19 00:00:00") .. datetime("2018-11-19 23:59:59")) 
| where ResultType == "50074"
| summarize FailedSigninCount = count() by UserDisplayName
| sort by FailedSigninCount desc

// Look at User did not pass the MFA challenge error on the MFA outage day
// and group on Apps to see affected applications the users tried to sign in to
SigninLogs
| where TimeGenerated  between(datetime("2018-11-19 00:00:00") .. datetime("2018-11-19 23:59:59")) 
| where ResultType == "50074"
| summarize FailedSigninCount = count() by AppDisplayName
| sort by FailedSigninCount desc

// Look at User did not pass the MFA challenge error on the MFA outage day
// and group on client apps to see affected apps the users tried to sign in from
SigninLogs
| where TimeGenerated between(datetime("2018-11-19 00:00:00") .. datetime("2018-11-19 23:59:59")) 
| where ResultType == "50074"
| summarize FailedSigninCount = count() by ClientAppUsed
| sort by FailedSigninCount desc

// Look at User did not pass the MFA challenge error on the MFA outage day
// and group on device operating system to see affected platforms
SigninLogs
| where TimeGenerated between(datetime("2018-11-19 00:00:00") .. datetime("2018-11-19 23:59:59")) 
| where ResultType == "50074"
| summarize FailedSigninCount = count() by tostring(DeviceDetail.operatingSystem)
| sort by FailedSigninCount desc

// Look at User did not pass the MFA challenge error on the MFA outage day
// and group on IP address to see from which network users tried to sign in from
SigninLogs
| where TimeGenerated between(datetime("2018-11-19 00:00:00") .. datetime("2018-11-19 23:59:59")) 
| where ResultType == "50074"
| summarize FailedSigninCount = count() by IPAddress
| sort by FailedSigninCount desc

// Look at User did not pass the MFA challenge error on the MFA outage day
// and group on users location details to see which country, state and city users tried to sign in from
SigninLogs
| where TimeGenerated between(datetime("2018-11-19 00:00:00") .. datetime("2018-11-19 23:59:59")) 
| where ResultType == "50074"
| summarize FailedSigninCount = count() by tostring(LocationDetails.countryOrRegion), tostring(LocationDetails.state), tostring(LocationDetails.city)
| sort by FailedSigninCount desc

Alert on On-premises Connectivity for Self Service Password Reset using Azure Monitor and Azure AD Activity Logs in Log Analytics

Recently I wrote a blog post on how to get started with integration of Azure AD Activity Logs to Azure Log Analytics. Setting up this is a requirement for the solution in this blog post, so make sure you have set this up first: https://gotoguy.blog/2018/11/06/get-started-with-integration-of-azure-ad-activity-logs-to-azure-log-analytics/.

In this blog post I wanted to show a practical example on how to create an alert for when Azure AD Self Service Password fails in password writeback because of connectivity error to the On-premises environment.

Build the query

If you know the schema, you can write the query directly, but more often than not you will work out these scenarios by exploring your actual log data. In my example we had a concrete example where password resets failed because of On-premises connectivity error. Looking into the Azure Log Analytics logs, I started with this simple query against AuditLogs:

image

After that I looked into the filters, and found that I could filter on Failures:

image

This resultated in some failure results. Exploring the results in the bottom right windows, I found that the failures had a ResultDescription of “OnPremisesConnectivityError”:

image

By clicking on the plus sign above I add that to the query:

image

I want to save my query next, so that I have it available for later:

image

Now that I have the results I want I can proceed to create an Alert Rule. Btw, here is the full query (I have since amended it to include OnPremisesConnectivityFailure in addition):

AuditLogs
| where Category == "Self-service Password Management"
| where ResultType == "Failure"
| where ResultDescription == "OnPremisesConnectivityError" or ResultDescription == "OnPremisesConnectivtyFailure"

Create an Alert Rule

Next, I can create a new Alert Rule for this query, something you can do directly from the query:

image

This next step would bring me over to the Azure Monitor and Rules Management section. The alert target (OMS/Log Analytics Workspace) and target hierarchy (Azure Subscription and Resource Group) should already be specifed:

image

Now I need to configure the alert criteria. Note that currently monthly cost for this alert is $1.50. Click on the criteria to configure the signal logic:

image

Here we see the query from before, as well as we need to set a threshold for number of results, and a period and frequency. Pricing details can be found at: https://azure.microsoft.com/en-us/pricing/details/monitor/. If you look at Alert signals and Log section, you’ll see that alerts with frequency of 5 minutes is $1.5, 10 minutes $1 and 15 minutes $0.5. This is per log monitored. I changed my period and frequency to 15 minutes:

image

After I click done I see that the alert criteria is correctly configured with a price of $0.50:

image

Next I need to specify the alert details, for example like below. You also have the option to supress multiple alerts inside a time window. I configured 60 minutes:

image

Next I need to either select an existing action group or create a new. An action group decides which action to take when an alert occures:

image

I’ll create a new action group for now. In this action group I will select to send an e-mail to a group in my company. As you see in the image below I have several options for action type, examples of use can be:

  • Trigger e-mail, SMS, push or other notifications.
  • Trigger Azure Functions for running some code logic.
  • Trigger Logic App for executing a business flow logic.
  • Trigger a webhook for posting a status, for example to a Microsoft Teams webhook.
  • Send the Alert via ITSM connector to create an incident in your connected ITSM system.
  • Trigger a Runbook in Azure Automation to run your own PowerShell runbooks, or you can use one of the built-in runbooks for restart, stop, remove or scale up/down VM.

image

When selecting Email I need to specify an e-mail address for the user/group I want to notify:

image

After that I’m ready to create the Alert rule:

SNAGHTML50ef585

After I created the rule, the group e-mail address I specified received this e-mail, confirming that it is now part of an action group:

image

If you want to locate and change this alert rule at a later stage, you will find it under Azure Monitor and Alert Rules:

image

Thats it, now we can just wait for future Self-Service Password Reset or Change connectivity errors, and we will get notified.

Testing the Alert rule Notification

For testing, I just wanted to force the error by logging into my Azure AD Connect Server and stop the service.

After that I tried to reset or change my password, resulting in this error message shown to the user:

image

Now in this situation, most users will either just wait and try again later, try one more time and then give up, and if you are lucky they will contact their IT admin and notify of the error. More often than not users just leave it there, and not notify anyone. This is where it is useful to get an alert as we have created here, because then you as an IT admin can proactively analyze and fix the error before it affects more users. This is the alert I received to my specified group:

image

We can directly click on the result to get into details for the error, for example which user was affected, from which IP address and more:

image

I don’t know about you, but I think this is just brilliant 🙂 With the integration of Azure AD Activity Logs in Log Analytics, I can really explore and analyze a lot of the operations going on in my tenant, and using Azure Monitor I can create alert rules that notifies or trigger other actions to handle those alerts.

Thanks for reading, more blog post will follow on this subject of Azure AD and Log Analytics, so stay tuned!

Get started with integration of Azure AD Activity Logs to Azure Log Analytics

Recently Microsoft announced the availability of forwarding the Azure AD Activity Logs to Azure Log Analyctis. You can read the announcement in full here: https://techcommunity.microsoft.com/t5/Azure-Active-Directory-Identity/Azure-Active-Directory-Activity-logs-in-Azure-Log-Analytics-now/ba-p/274843.

By bringing thousands (or even millions depending on your organization size and use of Azure AD), of sign-in and audit log events to Log Analytics you can finally use the power of Log Analytics for query, analyze, visualize and alert on your data.

In this blog post I will show how to get started and provide some useful tips. Most of this is already well documented in the following Microsoft Docs, but I will provide my own perspective and experience and as well let this blog post be an anchor for future detailed blog posts on the subject of analyzing Azure AD sign-in and audit logs in Log Analytics and Azure Monitor:

Set up Diagnostic Settings to Log Analytics

The first action we need to do is to Turn on diagnostics in the Azure AD Portal. You will need to be a Global Administrator or Security Administrator to do this:

image

PS! Another way to get to this setting to Turn on diagnostics is to either go to Sign-ins or Audit logs under Monotoring, and from there click on Export Data Settings:

SNAGHTML591fe33

Next select to Send to Log Analytics, and then select either or both of the AuditLogs or SigninLogs.

image

Note that to be able to export Sign-in data, your organization needs Azure AD Premium P1 or P2 (or EMS E3/E5). This requirement only applies to sign-in logs, not audit logs.

After selecting Log Analytics, and which logs to export, you need to configure which Log Analytics (still named as OMS) workspace to export the data to:

image

Note that this requires access to an Azure Subscription. You can either select an existing OMS workspace or create a new:

image

Important info! Usually you will need to be a Global Administrator or Security Administrator to be able to access the details of Sign-in logs or Audit logs in Azure AD, but by exporting this data to either an existing or a new Log Analytics workspace, potentially a lot more users can access that data. You need to think about if this is something you want to do, and at least control and govern which users can access that Log Analytics workspace.

For this reason alone it would probably be a better idea to create a dedicated Log Analytics workspace for the Azure AD activity logs:

image

Regarding pricing, using a Log Analytics workspace for Azure AD Activity Logs alone should not incur a notable cost in most normal environments. In an environment of less than 100 users I found the following consumption per day, which is way below the amount of free data you get included:

image

If you want to save and use that same query yourself, here it is:

Usage| where TimeGenerated > startofday(ago(31d))| where IsBillable == true
| where (DataType == "SigninLogs" or DataType == "AuditLogs") and Solution == "LogManagement"
| summarize TotalVolumeGB = sum(Quantity) / 1024 by bin(TimeGenerated, 1d), Solution| render barchart

Choosing a pricing tier depends on whether the Subscription was created before April 2, 2018 or not, or whether you have elected to move to a new pricing model. The older pricing model had a choice of free tier, which had a daily cap of 500 MB and a data retention of 7 days. As the diagram above showed, most organizations will be way below the 500 MB daily cap, but a retention of only 7 days will be considered short for most analyzing needs. So under the older pricing model you would consider a standalone per GB model, giving a retention of 1 month by default, but a cost of $2.30 per GB.

The new pricing model after April 2, 2018 has a simplified pricing model. Here the first 5 GB are free and you have a default retention of 31 days. Additional GBs for ingestion are $2.99 per month, and extra retention after the first 31 days is $0.13 per GB per month. Note that this pricing model is on subscription level and affects all your Log Analytics workspaces, so you need to carefully consider any changes to the new pricing model in your subscription.

After you have selected/created a Log Analytics workspace, and provided a name for the Diagnostic settings, you are ready to Save:

image

After about 15 minutes you can start explore the Logs in the Log Analytics workspace.

Start to Analyze Azure AD Activity logs with Log Analytics

To begin analyze the exported Azure AD Activity Logs with Log Analytics, you can either go to the Log Analytics section in your Azure Portal. You can also access the logs directly from Azure Active Directory from under the Monitoring section, which will take you directly to the configured Log Analytics workspace:

image

By default this will open a search query showing sample data from all your Log Analytics workspace.

I find that a good way to start learning about the sign-in and audit logs is to look at the schema. The SigninLogs and AuditLogs schemas should appear right under LogManagement as shown below:

SNAGHTML5fc1b7b

To look at the SigninLogs just add that to the query window and select a time range and click Run:

image

Depending on your sample data you can start filter on the left side, for example to look at only certain app sign ins, or client apps used, location and more..

Similarly for AuditLogs, in the following example I have set a time range of last 7 days:

image

See the links in the beginning of this blog post for some more sample queries and you can also import some sample views.

So now that we have a working diagnostic setting that exports my Azure AD sign-in and audit logs to Azure Log Analytics, I’m ready to explore some interesting scenarios for analyzing this data. This will be a topic for upcoming blog posts, so stay tuned for that!

Thanks for reading so far, I’m really excited for this feature! Smile

Exporting and Importing PowerApps and Flows Package that use a Custom Connector

Just recently I published a blog post on how to use PowerApps and Flow with a custom connector using Microsoft Graph API, to create an app for Azure AD PIM (Privileged Identity Management): https://gotoguy.blog/2018/09/15/create-your-own-azure-ad-pim-app-with-powerapps-and-flow-using-microsoft-graph/.

In this blog post I want to share some instructions and experiences on exporting the PowerApp and Flows to a package, and how you can export the Custom Connector definitions to a swagger file. After that I will show how you in a new environment can import these definitions, and import the PowerApp and Flow package.

Even better, based on the aforementioned blog post on the Azure AD PIM App, I will provide you with download links for the custom connector swagger defininiton for Microsoft Graph, as well as the PowerApp and Flows Package, so you can start from there without having to build all the stuff yourselves Smile!

Export the PowerApps Package

First, start in your Apps gallery of PowerApps, find the Export package (preview) button as shown below:

image

Specify a package name, environment and optionally a description as I have below:

image

Next, review the package content. For the Azure AD PIM App, I’ll change the Import Setup to “Create as new”, the same for the 3 Flows, as shown below:

image

For some of the resources you can select between Create as new or Update, and as I’m planning to import this as a new App with new Flows in the environment, I’ll change this from the default.

image

The other resources (like the connector and connections) I will select during import. This means these will have to be already existing in the environment I want to import the package to.

I can then download the package:

SNAGHTML9abd28

The package is downloaded as a zip file:

image

Inside the zip file there are some manifest json files and the PowerApps and Flows definitions:

image

Export the Custom Connector swagger file

The next thing we want to do is to export the custom connector and its operations. Go to Custom connectors in the menu:

image

Find the “PowerApps Microsoft Graph” connector, and click on the down arrow as shown below. This will download a swagger definition file in JSON format:

SNAGHTMLb16f76

You can open and inspect that JSON file in your favorite JSON editor, here is mine shown in Visual Studio Code:

image

Community Download

Courtesy of gotoguy.blog, I’ll provide you with a download for both the PowerApps/Flows package, as well as the Custom Connector Swagger JSON file. This is helpful if you want to skip right ahead to the next Import section.

These files are placed at my GitHub, in the following repositories:

Import the Custom Connector swagger file

In the new/target environment we will first have to import the swagger file for the Custom Connector. Here you have 2 options:

  1. You can create a new custom connector, and Import from an OpenAPI file/URL:

    image

  2. Or, if you already have a Custom Connector for Microsoft Graph, you can select to Update the existing connector from OpenAPI file/URL:

    image

For sake of education, lets try both variants. The first time you will have to create a new custom connector anyway, but later you will only need to update if there are any changes. I will use OpenAPI URL, as the swagger file is avaiable at my GitHub here: https://raw.githubusercontent.com/skillriver/PowerAppsFlowCustomConnector/master/MicrosoftGraphApi/PowerApps-Microsoft-Graph.swagger.json

PS! Prerequisite

Remember that to be able to use a Custom Connector and Microsoft Graph, you will have to create or use an App Registration in Azure AD in your target enviroment, like I have described in this blog article, under the section “App Registration”: https://gotoguy.blog/2017/12/17/access-microsoft-graph-api-using-custom-connector-in-powerapps-and-flows/.

Take a note of the application ID and secret key:

image

Remember also to give the App the right Microsoft Graph Permissions, and give Admin grant if needed:

image

Import from OpenAPI URL

To create a new custom connector, select to import from OpenAPI URL:

  1. Type a name for the Custom connector, and paste in the URL for the swagger json file:

    image

    Verify the URL and click Continue.

  2. Following that, verify that host is graph.microsoft.com and base URL is “/”, and optionally specify a connector icon, color and description:

    image

  3. On the security page you have to specify the client id which is the app id from the registered app in your target Azure AD environment, as well as client secret and resource URL:

    image
    In my target environment I have pasted in the client id, secret, and the resource URL is https://graph.microsoft.com. Note that the Redirect URL is not available before after the custom connector is saved:

    image

    Click to go to the next Definition page.

  4. At the Definition page, the actions are already in place because they were defined in the OpenAPI swagger file:

    image

    Click “Create connector”.

  5. After the Connector is created and saved, go back to Security, and copy the Redirect URL:

    image

  6. Make sure that the Redirect URL is on the list of the Reply URLs of the Azure AD App Registration:

    image

  7. Back in the Custom Connector, lets test the connector. Go to the Test page and create a connection:

    image

  8. After establishing a connection with your user account, you can go ahead and test one or more of the operations and verify that they run successfully:

    image

After testing this the custom connector is ready to use.

Update from OpenAPI URL

I you want to update an existing custom connector, select to Update from OpenAPI URL:

  1. Provide the URL for the swagger json file:

    image

  2. As with when creating a new custom connector, verify that host is graph.microsoft.com and base URL is “/”, and optionally specify a connector icon, color and description:

    image

  3. When updating an existing connector, you only have to specify the client secret again:

    image

    If you don’t have the original secret stored securely somewhere, you have to go to the App Registration in Azure AD and generate a new one.

  4. Verify that the Operations now has been updated from the imported OpenAPI swagger json file:

    image

    Click Update Connector to save the changes.

  5. After this, go to Test, and either use an existing connection or create a new, and the Test some of the operations to verify:

    image

Now we are ready import the PowerApp and the Flows that will use this custom connector.

Import the PowerApps and Flows Package

We can now import the package we exported earlier, or if you want to use the community download from my GitHub repository, make sure that you download the zip package before this next step.

Start by selecting Import package (preview) from the PowerApps menu:

image

Then browse to the zip packaged to start uploading:

image

When the upload is complete, we can review the package content. We have to select during import the connector and connections, marked as red under here:

image

After selecting the custom connector, and changing the connections to the target environment, we are ready to Import:

image

Note that you also can change the name of the PowerApp and Flows by clicking on the wrench symbol.

Click Import when you are ready, and verify that the import is successful:

image

You can now proceed to open the app for customizations and testing. If prompted, click to Allow the permission request:

image

After opening the Azure AD PIM App, now in the target environment, hold down the ALT key and click Refresh My Roles to test. And you should get the logged on users roles:

image

Obviously, now in the target environment, you would probably start to customize the logo, colors, label texts and language, if you don’t want to proceed with the “Elven” theme 😉

For example something like this from my company:

image

With that I can conclude this blog post, we have been able to export the custom connector definition and the PowerApps package including the Flows, and import these into a new environment. Now all that is left is to publish and share the PowerApp to be used in your organization.

Thanks for reading, hope it has been helpful!

Create your own Azure AD PIM App with PowerApps and Flow using Microsoft Graph

A while back I wrote a blog post on how you could access Microsoft Graph API using a custom connector in PowerApps and Flows: https://gotoguy.blog/2017/12/17/access-microsoft-graph-api-using-custom-connector-in-powerapps-and-flows/.

In this blog article I will build on that blog post to provide a practical example of using Microsoft Graph, and create an Azure AD PIM (Privileged Identity Management) App for activating any eligible admin roles for the logged on user.

First lets look into some of the documentation and what we need to prepare.

Microsoft Graph API for Azure AD PIM

Azure AD Privileged Identity Management provides you a way to enable on-demand time limited access for administrative roles. Microsoft Graph API for Azure AD PIM is currently available under the Beta endpoint, and documented here: https://developer.microsoft.com/en-us/graph/docs/api-reference/beta/resources/privilegedidentitymanagement_root.

If a user that has been assigned admin roles using Azure AD PIM, wants to activate any of the eligible role assignments, the user can navigate to the Azure AD PIM blade or just use this short url: https://aka.ms/myroles. In this blog post I will use my demo user account as an example, and this user has these roles assigned currently:

image

If I want to access my roles using Graph API I can use the privilegedRoleAssignment: my method (https://developer.microsoft.com/en-us/graph/docs/api-reference/beta/api/privilegedroleassignment_my).

Let’s try to do that using the Graph Explorer! (https://aka.ms/GE). Make sure you are signed in using your work account (normal user account), as I have in the screenshot below, and the run the GET command as shown below (https://graph.microsoft.com/beta/privilegedRoleAssignments/my):

SNAGHTML1778e65

In my case this returns the following (I have blurred out my userid for privacy):

image

Note that the response also shows if I have a current activation of any roles, and if so when that will expire. Roles that have isElevated set to “true”, and without an expirationDateTime are roles that are permanently assigned. If I want to query on that I can run the following GET command:

image

When my role assignments are returned I only get roleId’s though, so lets look at how I can get the displaynames of those roles.

For example, I see from the response above that one of the roleId’s returned is 29232cdf-9323-42fd-ade2-1d097af3e4de. In the Graph API for Azure AD PIM there is a method to list privilegedRoles (https://developer.microsoft.com/en-us/graph/docs/api-reference/beta/api/privilegedrole_list), so if I run the following in Graph Explorer: https://graph.microsoft.com/beta/privilegedroles/29232cdf-9323-42fd-ade2-1d097af3e4de, I should get more role information right?

No, I don’t have permission to do that:

image

Lets look at the documentation, and it clearly states that for the requestor (my normal user account) to be able to list privilegedRoles I need to be either a Global Administrator, PIM Administrator, Security Administrator or Security Reader:

image

So that won’t work for me, as I want to let normal user accounts to be able to use my Graph API commands.

However, one thing that normal users do have access to, is listing of directoryRoles (https://developer.microsoft.com/en-us/graph/docs/api-reference/beta/api/directoryrole_list). So if I run that, I will get all directory roles with their id and a roleTemplateId, and I have highlighted below the id I was looking for above, which turns out to be the Exchange Administrator role:

image

So, to get the displayName of the role I can run the following GET request: https://graph.microsoft.com/beta/directoryroles/?$filter=roleTemplateId eq ‘29232cdf-9323-42fd-ade2-1d097af3e4de’:

image

Ok, so now I have a way to query for my role assignments, and also a way to query for the display names of any roles. Now I need to see how I can activate (or deactivate) my role assignments.

I will use these methods: privilegedRole: selfActivate and privilegedRole: selfDeactivate, they are documented at https://developer.microsoft.com/en-us/graph/docs/api-reference/beta/api/privilegedrole_selfactivate / https://developer.microsoft.com/en-us/graph/docs/api-reference/beta/api/privilegedrole_selfdeactivate.

When I do a POST /privilegedRoles/{id}/selfActivate, I need to specify the role id in the request uri and and a request body:

{
  "reason": "reason-value",
  "duration": "duration-value",
  "ticketNumber": "ticketNumber-value",
  "ticketSystem": "ticketSystem-value"
}

For example I can try to activate the Exchange Administrator role by POST to: https://graph.microsoft.com/beta/privilegedRoles/5cfc2572-33b1-4839-8774-2bae31da1a29/selfActivate, and specify a request body like shown below. Note that all properties in the request body are optional, I can just leave them blank or provide default values:

image

Currently there is an error in the Graph Beta API for PIM that won’t let me activate roles that require MFA, so I’ll just accept this error and move on for now:

image

Before I deactivate a role I need to have it to be active, so for now I will go to https://aka.ms/myroles, and activate the Exchange Administrator role manually, promptly requiring MFA verification first:

image

And after that I can activate the role:

image

To deactivate the Exchange Administrator role via Graph API I’ll just do a POST to /privilegedRoles/{id}/selfDeactivate, specifying the role id like this: https://graph.microsoft.com/beta/privilegedRoles/29232cdf-9323-42fd-ade2-1d097af3e4de/selfDeactivate

No request body is needed, and this time I get a successful response:

image

I think these 4 methods will do for now, there are a lot of other methods for managing PIM roles and settings as well, but we are now ready to start working with our PowerApps and Flow Custom Connector.

Add Microsoft Graph Permissions to App Registration

As I mentioned in the beginning of this blog post, I previously wrote a blog post on how to set up an App Registration for a custom connector for PowerApps and Flows. I will now build on this, so if you want to follow the steps I do here, please set up the prerequisites as described in the blog post: https://gotoguy.blog/2017/12/17/access-microsoft-graph-api-using-custom-connector-in-powerapps-and-flows/.

Looking at the documentation I see that I need to add Delegated Permissions for Directory.AccessAsUser.All to be able to list my assignments:

image

Similarly, if I check the documentation for the other methods from above,  I will need also Directory.AccessAsUser.All:

image

image

image

So I will go ahead and add that permission to my App Registration from before. Logged in as a Global Admin find the App Registration, go to Settings and Permissions, and add the following delegated permission for Microsoft Graph:

image

Note that it requires an admin to consent, so remember to click on Grant permissions:

image

Now we are ready to add the PIM API methods to the Custom Connector.

Adding PIM API’s to Custom Connector Operations

Again building on my linked blog post, you should now be able to log on to PowerApps, and find your PowerApps Microsoft Graph connector:

image

If you don’t have it, just follow the steps in the linked post to create it.

Select to edit, and go to step 3. Definition and add a new action. Lets first create a new action for getting my role assignments:

image

Scroll down to Request, this is where we will provide the details for our query. The best way to do this is to select to Import from sample. I specify the Method to be GET, and then the query like this, which is the same query I ran in the Graph Explorer earlier:

image

I don’t need to specify any Header or Body for this query, so I just press Import. Now my action looks like this:

image

Scroll down to the Response section, and then click on the default response. Click on Import from sample, and this time you paste in the response body from the previous Graph Explorer query:

image

This response will help the custom connector operation so that we can get the right output values mapped in our PowerApp later. Select Import.

The response now looks like this:

image

We can also validate that the response looks ok:

image

Click on Update connector to save this operation, do not select to Test at this point. We have more to do..

Next I want to create another action for List directoryRoles. I’ll create a new Action:

image

Request and Import from sample:

image

Note that the Request now will have a $filter parameter:

image

Default Response and Import from sample:

image

Check validation and the Update Connector:

image

Next I want to create another action for

privilegedRole: selfActivate. I’ll create a new Action:

image

Request and Import from sample, this time note the POST verb, and specifying {id} in URL, as well as the request body as shown below:

image

Note now that the request will have an id parameter in the path as well as a body parameter:

image

Default Response and Import from sample (response body is copied from documentation):

image

The response looks like this now and we can check validation:

image

Click Update Connector to save our progress so far. Now we can add the last action for privilegedRole: selfDeactivate :

image

Request and Import from sample, specifying verb POST and again using {id} in URL:

image

The request will look like this now:

image

Default Response and Import from sample:

image

And we can validate the response:

image

Click on Update Connector to save. We should now have 4 actions successfully configured, in addition to the ones we had from before:

image

Now we can do some testing, close the connector for now. Under Data, find Connections. If you previously had any connections to the “PowerApps Microsoft Graph” connector, like I have here, delete the connection:

image

After clearing any existing connections, select New connection at the top and find the PowerApps Microsoft Graph connector:

image

Click create and the log in with your current user to create the connection. Now you can go back to the custom connector, click Edit and then go to Test section. Select the current connection, and select the action to test. Then click Test operation:

image

The test should complete successfully and return my role assignments:

image

Lets test the list directory roles, this time I need to specify the $filter:

image

Testing selfActivate will fail as it did with testing with Graph Explorer because of the MFA requirement: (we will explore that later)

image

image

Last test is for selfDeactivate, which willl have an empty response because the role is not active:

image

Starting with the PowerApp

Now that we have the Custom Connector Operations ready, we can proceed to create the PowerApp. We’ll begin with an empty app, create some controls and layouts before we get into the Flows needed.

Start by Create an app:

image

Then select a blank canvas, and phone layout:

image

You should now have an empty app like this:

image

Fast forward, and I’ll assume you have some basic PowerApps skills, add some controls, layout and image after your liking, ending up with something similar like this:

image

A quick summary of the above:

  • In addition to my selected logo and background, I’ve added labels for listing my roles and selected role details.
  • I’ve added three buttons, one for refreshing my roles, and one for activate and deactivate any roles.
  • I also have a text box to provide an activation reason, as well as a message label to show/hide any error message if I try to activate without a reason. We’ll get to that later.

Now we have an empty powerapp with some layouts and controls. It’s time to get into the Flows that will trigger the Microsoft Graph operations. First go to App Settings and specify an App name and choose a descriptive icon and color:

image

And then save the App:

image

Creating the Flow for Getting My Role Assignments

In the PowerApps main menu, find the link to Flows, and the select to create a Flow from blank:

image

After creating a blank Flow from here there will already be a step for input from PowerApps as shown below:

image

Click New step, add an action, and search for variables, and select the Variables – Initialize variable action:

image

Type the name MyRolesArray and select Type Array:

image

Add a new step of type action, and this time we will search for the custom connector “powerapps microsoft graph”, and that will list any operations we defined earlier. We will now select the operation for “My Privileged Role Assignments”:

image

Our Flow should look like this now:

image

When we tested via Graph Explorer earlier in this blog post, PIM role assignments returned with only role id’s, so we had to do an additional call to list directoryroles to get the displaynames of the roles. We will now implement some logic in the Flow to achieve this.

Add a new step, this time selecting More and Add an apply to each:

image

In the Apply to each, select “value” as output from the previous step as shown below:

image

It’s also a good idea to rename the step, like I have done below before you proceed:

image

Inside the For Each loop, add a new action, searching for the PowerApps Microsoft Graph connector again, this time selecting the List directoryRoles operation:

image

We need to provide a value for the $filter parameter, this is done by typing the filter definition and selecting the roleId from the dynamic content provided by previous step:

image

I also rename the step before I proceed:

image

Next, add another Apply to each section, using the value output from the List directoryRoles to get DisplayName:

image

Next add an action and search for append to array, and select that:

image

Now comes the most important part. I want to use the array variable I initialized in the beginning of the Flow, and build a custom JSON object array which integrates my role assignments as well as the displaynames in one single output. So in the following I select the array variable name, and for value I create my own custom JSON as shown below. In addition I use the dynamic content to search for the values I want to add:

image

At the end of the Flow, outside of the two nested Apply to each loops, add a Request – Response action:

image

In the Response, specify the MyRolesArray as Body, and provide a Response Body JSON Schema. The best way to get a JSON schema is to Save and Test the Flow, and look at the default Response. This is how it looks in my definition:

image

This is the JSON schema I used:

{
"type": "array",
"items": {
"type": "object",
"properties": {
"roleId": {
"type": "string"
},
"displayName": {
"type": "string"
},
"isElevated": {
"type": "boolean"
},
"expirationDateTime": {
"type": "string"
}
},
"required": [
"roleId",
"displayName",
"isElevated",
"expirationDateTime"
]
}
}

Next, Save and Test the Flow. Look for the Test button, and select like below:

image

Follow the on-screen instructions for choosing test connection, and then start the Flow. Click the link to see the Flow run activity, and you should be able to see that the Flow executed successfully and you can look at the details on each step. I’m mostly interested in the Response output at the end, and it looks like this:

image

If I scroll down I can see that the output contains all my roles, and have the display name included in the output. This is the output I eventually will work with in my PowerApp.

Remember to give the Flow a describing name, and Save it before you proceed to the next section.

image

Creating the Flows for Self Activate and Deactivate Roles

Now we need to create the Flows for self activating and deactivating the selected roles. First start by creating a new blank Flow, starting with the input from PowerApps:

image

Add a new step and action for the Microsoft PowerApps Graph connector and the Privileged Role Self Activate operation:

image

When choosing this operation we will get the opportunity to specify input fields, where id is required, as this is the role id for the role we want to activate. In addition we can specify a reason, as well as duration and ticketing info as optional fields:

image

In my solution I want to specify id and reason, and just use the default duration. For the id field and reason field, just click “Ask in PowerApps”, which will create two parameters to use from PowerApps when I will call the flow:

image

In the third step I will add a Request – Response action, and use the Body from the previous step, like this:

image

Save the Flow with a name like I have done below:

image

Then its a good idea to test the Flow, select the test button, provide the trigger for the flow, and when running we need to manually specify the role id to activate, and a reason, like shown below:

(PS! Remember to test with a role that does not require MFA on elevation, because of the previously reported bug.)

image

After clickin Run Now, verify that the Flow successfully started, and then click into the activity details. In the example below I can verify that indeed the role was activated:

image

So that is the Flow for self activating a role, now we need a similar Flow for deactivating a role. Now that we should start getting the hang of this, this is how that Flow should look after creating and saving it:

image

Deactivating a role only requires the role id as a parameter, as shown above. Lets test this as well:

image

The Flow should start successfully, and you can verify the steps like in the following:

image

So, now we have created 3 Flows that we will use in the previously created PowerApp. In the next section we will add the flows and provide some logic to the application.

Connecting the PowerApp to the Flows

Back in the PowerApp created earlier, open it in Edit mode, and select the Refresh My Roles button. Click on the Action menu, and then on Flows, and from the Data section select the Flow we created earlier for Get My Role Assignments:

image

When selecting that Flow, the OnSelect event will populate with the name of the Flow and the Run method. As this Flow doesn’t have any input arguments we can just close the parenthis after like this .Run(), as shown below:

image

So now our button will get any role assignments for the connected user, but we have store the output we get back from the Flow, and use that in the listbox and in the details labels below. So while the Refresh My Roles is still selected, add the following to the OnSelect event:

Set(wait,true);
ClearCollect(MyPIMRoles,GetMyRoleAssignments.Run());
Set(wait,!true)

Like this:

image

A little explanation, the Set(wait,true) and Set(wait,!true) are used at the beginning and end of the action for indicating that the PowerApp is busy when calling the Flow. The ClearCollect is used to store the output response we get back from the Flow in a variable; MyPIMRoles.

Next, set the Items property of the listbox for My Roles to MyPIMRoles:

image

If we now du a test run of the PowerApp, the easiest way to do that is to hold the ALT button down and then click on the Refresh My Roles button. This should return the roles you are assigned to like this:

image

If your listbox is not showing the displayname of the roles, you can change that from the advanced properties of the listbox:

image

While the listbox i still selected, change to the OnSelect method and add the following:

Set(SelectedRole,First(lstMyRoles.SelectedItems))

It should look like this:

image

A quick explanation of this: I’m setting a variable “SelectedRole”, every time I click on a role in the listbox, by getting the first instance of the lstMyRoles.SelectedItems. (In fact, as my listbox only allows to select one item at a time, the first will always be the one I selected).

This “SelectedRole” variable can now be used in my other label details. First, set the lblRoleIdValue.Text property to the following:

image

Likewise, set the lblRoleElevatedValue.Text property to the following:

image

And then set the lblRoleExpiresValue.Text property to: Text(DateTimeValue(SelectedRole.expirationDateTime), DateTimeFormat.ShortDateTime24), like this:

image

As you can see, I’ve added some format functions to display any date and time values from the selected role in the format of short datetime 24 hour clock.

Now, if you hold down the ALT button again, you can click on the selected roles in the listbox, and the labels below will update with the selected role id, if it is elevated or not, and any expiry of existing elevations:

AzureADPIMApp1

Now it’s time to add the other Flows to the Activate and Deactivate buttons, first select the Activate button, and on the Action and Flow menu, select to add the Priviliged Role Self Activate Flow:

image

This Flow needs two inputs:

image

The first input we will get from SelectedRole.roleId, and the second from the txtActivationReason.Text, so it would look like this:

image

Similarly, add the Flow for the Deactivate button, specifying the SelectedRole.roleId as input:

image

Now, at this point we should be able to get my role assignments in the list box, and also to be able to activate or deactivate the selected roles. I do want to add some more logic to the app though. Starting with activating/deactivating the buttons regarding the status of the role. On the Activate button, change the DisplayMode property to:

If(!SelectedRole.isElevated, DisplayMode.Edit, DisplayMode.Disabled)

Like this:

image

And similarly for the DisplayMode property for the Deactivate button:

If(SelectedRole.isElevated, DisplayMode.Edit, DisplayMode.Disabled)

image

Next, I want to add some hint text to the text box for activation reason, this is done this way:

image

At the bottom I have created a label with a message, this lblShowMessage control I want to set visible if I try to activate a role without specifying a reason:

image

Now I need to make some changes to the Activate button and OnSelect method to implement some logic:

image

Lets break that down: First I use the Set method to control wait to indicate that the App is busy, then I do an If check on the txtActivationReason text box, and if I have specified a reason I proceed to run the Flow to self activate the role. After that I clear the txtActivationReason text box, and call the flow for refresh the roles in the list box. At the end I use a ShowMessage variable, setting it to true or false, which in turn is connected to the Visible property of the lblShowMessage control like this:

image

Here is the Activate button OnSelect code for you to copy:

Set(wait,true);
If(!IsBlank(txtActivationReason.Text),
PrivilegedRoleSelfActivate.Run(SelectedRole.roleId,txtActivationReason.Text);
Reset(txtActivationReason);
ClearCollect(MyPIMRoles,GetMyRoleAssignments.Run());
Set(wait,!true),
UpdateContext({ShowMessage: true});
UpdateContext({ShowMessage: false}))

And for the Deactivate button I change the OnSelect to:

image

I don’t need to check the txtActivationReason text box now, so I’ll just clear it and refresh the roles. Here is the code:

Set(wait,true);
PrivilegedRoleSelfDeactivate.Run(SelectedRole.roleId);
Reset(txtActivationReason);
ClearCollect(MyPIMRoles,GetMyRoleAssignments.Run());
Set(wait,!true)

I’ll also add a reset of the activation reason text box to the Refresh My Roles button:

image

And finally, at the OnSelect method of the lstMyRoles listbox, I’ll set the ShowMessage variable to false whenever I click on different roles in the list, so that any previous activation error message is not shown.

image

That should be it! We’ve now implemented some logic to the PowerApp, and are ready to publish and run the App.

Publish and Run the Azure AD PIM App

On the File menu click Save, and the Publish:

image

You can also Share the PowerApp in your organization:

image

(please see my previous blog post https://gotoguy.blog/2017/12/17/access-microsoft-graph-api-using-custom-connector-in-powerapps-and-flows/, and the sharing section at the end for details on the experience on this).

After you have published the PowerApp, you can click the Play button to run the PowerApp. First time you will need to accept permission:

image

After that you should be able to refresh your roles:

image

Let’s try to activate a role:

image

After I click the Activate button, the role will be activated, the list will be refreshed, and I can look at the Device Administrators role that it is now elevated and with an expiry time:

image

The Activate button is now disabled for that role, and the Deactivate button is enabled. Let’s try to deactivate the role again, clicking the Deactivate button. After a short time the role is deactivated, elevation status is false:

image

So now the Azure AD PIM App is working as intended, every user that have been assigned a role can now elevate themselves using the App. Even better is that my users also now can use the mobile PowerApps app to run this from their mobile phones!

As an administrator I can also see the results of the activations in the Directory roles audit history:

image

Known issues and tips

The biggest issue right now is a problem with the Microsoft Graph beta endpoint for selfactivate the role, as it currently does not support activating roles that require MFA. So I you want to use Microsoft Graph for activating roles now, you have to disable the requirement of requiring MFA for activation, either by default for all roles or for roles individually:

image

I’ll keep you posted of any changes to this issue, and update the blog post if that changes.

Another tip is that if you want to do some reporting on how many users are using the PowerApp for activating their PIM roles, you can for example use the ticketSystem string for specifying a constant like below:

image

That should wrap up this blog post, hope this will be useful for you, thanks for reading Smile

Using the Azure Run As Account in Azure Automation to Connect to Azure AD with a Service Principal

If you are using Azure Automation and working with Runbooks for automating against your Azure subscription, you can create an Azure Run As Account for authenticating and logging in to your subscription. The Azure Run As Account is configured in your Automation Account, and will do the following:

  • Creates an Azure AD application with a self-signed certificate, creates a service principal account for the application in Azure AD, and assigns the Contributor role for the account in your current subscription.
  • Creates an Automation certificate asset named AzureRunAsCertificate in the specified Automation account. The certificate asset holds the certificate private key that’s used by the Azure AD application.
  • Creates an Automation connection asset named AzureRunAsConnection in the specified Automation account. The connection asset holds the applicationId, tenantId, subscriptionId, and certificate thumbprint.

You can read more about setting up Azure Run As Accounts here, including how to do a more customized setup with PowerShell: https://docs.microsoft.com/en-us/azure/automation/automation-create-runas-account.

Something worth noting is that this Azure Run As Account will by default have the Contributor role to your entire subscription, so it would make sense to look into changing RBAC settings for the subcription or resource groups if you want to limit that. Also, all users that have access to the Automation Account will also have the opprotunity to use this Azure Run As Account.

Having said that, the Azure Run As Account is a great way to authenticate securely with certificates and a service principal name without needing to store a username and password in a credential object.

So I thought, wouldn’t it be great if we could use this same Azure Run As Account to log in to your Azure AD tenant for the possibility to run Azure AD PowerShell commands? The reason I thought of this is because of this article showing how to authenticate with Azure AD v2 PowerShell and Service Principal: https://docs.microsoft.com/en-us/powershell/azure/active-directory/signing-in-service-principal?view=azureadps-2.0. In this short blog post will show you how to do this.

Getting the Azure Run As Account details

First, look into your Automation Account and Account Settings to find any Run as accounts:

image

Click on the Azure Run As Account to see the details (or to create one if you haven’t before). Take a note of the Service Principal Object Id, we will use that later:

image

Creating a Runbook that Authenticates with Service Principal to Azure AD

Now, let’s create a PowerShell runbook using the Azure Run As Account for connecting to Azure AD.

First, I set the connection name  “AzureRunAsConnection”, and then save that as a variable for holding my service principal details using the Get-AutomationConnection cmdlet.

Then, logging in to Azure AD is done with specifiying TenantId, ApplicationId and CertificateThumbprint parameters, as shown below:

image

This will log in my service principal to Azure AD and I’m ready to run some commands, for example getting some organization details for the tenant, or counting different types of user objects:

image

Running this runbook will for example show me this output for my tenant. This shows that I successfully authenticated with the Azure Run As Account service principal:

image

Here is a link to a Gist where I have included the above PowerShell runbook script:


<#
.SYNOPSIS
This Azure Automation runbook connects to Azure AD with a Service Principal and Connect-AzureAD.
.DESCRIPTION
This Azure Automation runbook connects to Azure AD with a Service Principal and Connect-AzureAD.
It uses an Azure Run As Account connection that must be created before.
You have to import the AzureAD module from the Automation module gallery, if it's not already there.
AUTHOR: Jan Vidar Elven [MVP]
LASTEDIT: July 11th, 2018
#>
# Get Azure Run As Connection Name
$connectionName = "AzureRunAsConnection"
# Get the Service Principal connection details for the Connection name
$servicePrincipalConnection = Get-AutomationConnection -Name $connectionName
# Logging in to Azure AD with Service Principal
"Logging in to Azure AD…"
Connect-AzureAD -TenantId $servicePrincipalConnection.TenantId `
-ApplicationId $servicePrincipalConnection.ApplicationId `
-CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint
"List Tenant Org Details:"
Get-AzureADTenantDetail | Select DisplayName, Street, PostalCode, City, CountryLetterCode
"Member Account Synced Count:"
(Get-AzureADUser -All $true -Filter "userType eq 'Member' and accountEnabled eq true" | Where-Object {$_.DirSyncEnabled -eq $true}).Count
"Disabled Users Count:"
(Get-AzureADUser -All $true -Filter 'accountEnabled eq false').Count
"Guest User Count:"
(Get-AzureADUser -All $true -Filter "userType eq 'Guest'").Count
"Cloud Only Account Count:"
(Get-AzureADUser -All $true -Filter "userType eq 'Member'" | Where-Object {$_.userPrincipalName -like "*onmicrosoft.com"}).Count

Role Permissions for the Service Principal

Depending on what kind of automation you want to do against Azure AD, especially if you want to write data, you will have to add the Service Principal to an Azure AD Role. Here is a couple of examples, using the object id for the service principal I told you to note earlier from the Azure Run As Account:

# Get the associated Service Principal for the Azure Run As Account
$runAsServicePrincipal = Get-AzureADServicePrincipal -ObjectId ""

# Add the Service Principal to the Directory Readers Role
Add-AzureADDirectoryRoleMember -ObjectId (Get-AzureADDirectoryRole | where-object {$_.DisplayName -eq "Directory Readers"}).Objectid -RefObjectId $runAsServicePrincipal.ObjectId

# Add the Service Principal to the User Administrator Role
Add-AzureADDirectoryRoleMember -ObjectId (Get-AzureADDirectoryRole | where-object {$_.DisplayName -eq "User Account Administrator"}).Objectid -RefObjectId $aaAadUser.ObjectId

# Add the Service Principal to the Global Administrator Role
Add-AzureADDirectoryRoleMember -ObjectId (Get-AzureADDirectoryRole | where-object {$_.DisplayName -eq "Company Administrator"}).Objectid -RefObjectId $runAsServicePrincipal.ObjectId

That concludes this short blog post, hope it has been helpful! Thanks for reading and remember to share if it was useful Smile