So I want to create an app registration and limit it to a specific mailbox – i.e. only send/ read emails from that mailbox. Can’t do this entirely via the Graph API. All I can do in Graph API is give it permissions to send/ read emails from all mailboxes.
To restrict this to a specific mailbox we need to use application access policies.
But first, let’s see what I can do a with an app registration that’s not restricted.
In this case I was lazy and didn’t create a self-signed cert for the app registration, instead I created a secret. When you use a secret Connect-MgGraph
does not have a way to authenticate using it (it only does the interactive DeviceAuthentication
or certificates) and so we have to resort to Invoke-RestMethod
to authenticate (or Postman like in my previous post).
1 2 3 4 5 6 7 8 9 10 11 12 |
$clientID = 'edeb4326-a47a-4d77-b4ca-c8efb67f7bb7' $tenantID = '9e3b33ee-0fe6-4c87-aea7-24a8b67dfb4f' $clientSecret = '_iN49fbpIPpAV~0Hl9d-oztJ.EPv438sl4' # Required parameters are at https://docs.microsoft.com/en-us/graph/auth-v2-service#4-get-an-access-token $body = @{ "grant_type" = "client_credentials" "scope" = "https://graph.microsoft.com/.default" "client_id" = $clientID "client_secret" = $clientSecret "tenant" = $tenantID } |
Just as an fyi, typically at this point I’d convert the hash to a JSON and use that to connect to the API.
1 |
Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenantID/oauth2/v2.0/token" -Method POST -Body (ConvertTo-Json $body) |
But that gives an error:
1 |
Invoke-RestMethod: {"error":"invalid_request","error_description":"AADSTS900144: The request body must contain the following parameter: 'grant_type'.\r\nTrace ID: 95e1552c-d539-4b75-a7e0-9468ca509301\r\nCorrelation ID: 59d22480-d4f2-4233-ad2f-a8111e8998f7\r\nTimestamp: 2021-07-10 12:14:47Z","error_codes":[900144],"timestamp":"2021-07-10 12:14:47Z","trace_id":"95e1552c-d539-4b75-a7e0-9468ca509301","correlation_id":"59d22480-d4f2-4233-ad2f-a8111e8998f7","error_uri":"https://login.microsoftonline.com/error?code=900144"} |
Instead simply passing the hash table is fine:
1 2 3 4 5 |
❯ Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenantID/oauth2/v2.0/token" -Method POST -Body $body token_type expires_in ext_expires_in access_token ---------- ---------- -------------- ------------ Bearer 3599 3599 eyJ0eXAiOiJKV1QiLCJub25jZSI6IkFlbjRGakl2N0tzXzBwRHQzaWFGNzBjb19oWGlOdll1M2piZkZSQlJDWEkiLCJhbGciOiJSUzI1NiIsIng1dC… |
(As an aside, I know the Url to connect to and the parameters to pass in the body from this doc). This gives me an access token if I successfully authenticate, which is what we have in the output. Ideally I should capture the output in a variable so I have the token.
1 |
$connectGraph = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenantID/oauth2/v2.0/token" -Method POST -Body $body |
Now I have the token in $connectGraph.access_token
. Using that I can authenticate via the Connect-MgGraph
cmdlet too:
1 2 |
❯ Connect-MgGraph -AccessToken $connectGraph.access_token Welcome To Microsoft Graph! |
(So yeah, it is easier to just use a certificate with Connect-MgGraph
but I like to spice things up occassionally) :)
Now to get some emails of a user. I have the userid (not the UPN, Email-Address, or sAMAccountName… this is the Azure AD GUID of the user; get this from the portal or via say Get-MgUser
) already so I am going to skip finding that:
1 2 3 4 5 6 7 8 9 10 11 12 |
❯ Get-MgUserMailFolder -UserId b269b48d-afa4-49ed-a26e-d684531b62c7 Id ChildFolderCount DisplayName -- ---------------- ----------- AAMkADkyZDBkZWQ2LWQ1OTgtNGZmZC04YzFiLThlYjk2MzEzZDExMAAuAAAAAAANUky5hV0qRoBAYi2_z5iZAQAQMxgItKDsQanUIzGFHhktAAAD7S9eAAA= 0 Archive AAMkADkyZDBkZWQ2LWQ1OTgtNGZmZC04YzFiLThlYjk2MzEzZDExMAAuAAAAAAANUky5hV0qRoBAYi2_z5iZAQAQMxgItKDsQanUIzGFHhktAAAAAAFCAAA= 1 Conversation … AAMkADkyZDBkZWQ2LWQ1OTgtNGZmZC04YzFiLThlYjk2MzEzZDExMAAuAAAAAAANUky5hV0qRoBAYi2_z5iZAQAQMxgItKDsQanUIzGFHhktAAAAAAEKAAA= 0 Deleted Items AAMkADkyZDBkZWQ2LWQ1OTgtNGZmZC04YzFiLThlYjk2MzEzZDExMAAuAAAAAAANUky5hV0qRoBAYi2_z5iZAQAQMxgItKDsQanUIzGFHhktAAAAAAEPAAA= 0 Drafts AAMkADkyZDBkZWQ2LWQ1OTgtNGZmZC04YzFiLThlYjk2MzEzZDExMAAuAAAAAAANUky5hV0qRoBAYi2_z5iZAQAQMxgItKDsQanUIzGFHhktAAAAAAEMAAA= 0 Inbox AAMkADkyZDBkZWQ2LWQ1OTgtNGZmZC04YzFiLThlYjk2MzEzZDExMAAuAAAAAAANUky5hV0qRoBAYi2_z5iZAQAQMxgItKDsQanUIzGFHhktAAAAAAEVAAA= 0 Junk Email AAMkADkyZDBkZWQ2LWQ1OTgtNGZmZC04YzFiLThlYjk2MzEzZDExMAAuAAAAAAANUky5hV0qRoBAYi2_z5iZAQAQMxgItKDsQanUIzGFHhktAAAAAAELAAA= 0 Outbox AAMkADkyZDBkZWQ2LWQ1OTgtNGZmZC04YzFiLThlYjk2MzEzZDExMAAuAAAAAAANUky5hV0qRoBAYi2_z5iZAQAQMxgItKDsQanUIzGFHhktAAAAAAEJAAA= 0 Sent Items |
Armed with the folder id I can see the emails in it:
1 2 3 4 5 |
❯ Get-MgUserMailFolderMessage -UserId b269b48d-afa4-49ed-a26e-d684531b62c7 -MailFolderId AAMkADkyZDBkZWQ2LWQ1OTgtNGZmZC04YzFiLThlYjk2MzEzZDExMAAuAAAAAAANUky5hV0qRoBAYi2_z5iZAQAQMxgItKDsQanUIzGFHhktAAAAAAEMAAA= | fl Subject,ToRecipients Subject : Company All Hands ToRecipients : {Microsoft.Graph.PowerShell.Models.MicrosoftGraphRecipient, Microsoft.Graph.PowerShell.Models.MicrosoftGraphRecipient, Microsoft.Graph.PowerShell.Models.MicrosoftGraphRecipient, Microsoft.Graph.PowerShell.Models.MicrosoftGraphRecipient…} |
So how do I limit this? Say I want to limit access to the mailbox of alexw@ragnarak.xyz.
First, install the ExchangeOnlineManagement
module and connect with admin creds. This is Windows PowerShell only, so handy tip for anyone else not having access to Windows – if you have an Azure subscription open up Cloud Shell and install the module there, then login using Device Authentication to the Tenant you want to work with (My bad, this module works with PowerShell Core too; in fact, the Azure Cloud Shell is PowerShell 7.x)
1 2 |
Install-Module ExchangeOnlineManagement Connect-ExchangeOnline -Device |
Then I can limit using the New-ApplicationAccessPolicy
cmdlet. This requires the client id of the app registration, permissions you want to set, and the email address(es) you want to limit to (in case of more than one, put them into a mail enabled security group and pass on the email address of that group).
1 2 3 4 5 6 7 8 9 10 11 |
$clientID = 'edeb4326-a47a-4d77-b4ca-c8efb67f7bb7' $restrictedEmail = "alexw@ragnarak.xyz" $params = @{ AccessRight = "RestrictAccess" AppId = $clientID PolicyScopeGroupId = $restrictedEmail Description = "Restrict app permissions to only allow access to service account" } New-ApplicationAccessPolicy @params |
This could take up to 30 mins to take effect for Graph API calls *yawn*… so I wait patiently… as time crawls by… frequently trying the same call as before… almost giving up…. until… finally… I am denied:
And just to confirm, I can view the mails of the user I restricted this app registration to: