I'm writing a kind of passwd command line app to change an Azure account's password. Something to work like this:
> passwd someuser Passw*rd1
After reading all the documentations, I have my application created in Azure portal with all the permissions needed and I'm about to get the app authorization, just before getting the access token.
Problem is instead of getting the authorization code what the call for https://login.microsoftonline.com/<tenantName>/oauth2/v2.0/authorize returns to me is an HTML page.
As far as I understand, this page is supposed to give the signed-in user the chance to delegate the required permissions, but this is a command to be used by an administrator user. In fact, there is no signed-in user at this moment.
What am I missing?
There is a code sample about resetting user password.
$tennantid = ''
$SubscriptionId = ''
$ApplicationID = ''
$ApplicationKey = ''
$TokenEndpoint = {https://login.windows.net/{0}/oauth2/token} -f $tennantid
$ARMResource = "https://graph.microsoft.com";
$Body = #{
'resource'= $ARMResource
'client_id' = $ApplicationID
'grant_type' = 'client_credentials'
'client_secret' = $ApplicationKey
'scope' = 'https%3A%2F%2Fgraph.microsoft.com%2FDirectory.AccessAsUser.All'
}
$params = #{
ContentType = 'application/x-www-form-urlencoded'
Headers = #{'accept'='application/json'}
Body = $Body
Method = 'Post'
URI = $TokenEndpoint
}
$token = Invoke-RestMethod #params
$headers = #{}
$headers.Add("authorization","Bearer $($Token.access_token)")
$ResetPwd = #{
"passwordProfile" = #{
"forceChangePasswordNextSignIn" = "false"
"password" = "Test123456!"
}
} | ConvertTo-Json
Invoke-RestMethod -Headers $headers -Method Patch -Uri "https://graph.microsoft.com/beta/users/$($respons.id)" -ContentType "application/json" -Body $ResetPwd
Then used this and the code above works.
$servicePrincipal = Get-MsolServicePrincipal -ServicePrincipalName ServicePrincipalName
$roleId = (Get-MsolRole -RoleName "Company Administrator").ObjectId
Add-MsolRoleMember -RoleObjectId $roleId -RoleMemberObjectId $servicePrincipal.ObjectId -RoleMemberType servicePrincipal
Hope this help.
Where are you call the /authorize endpoint?
You could call it like this in a browser and log in with your account:
https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize?
client_id=6731de76-14a6-49ae-97bc-6eba6914391e
&response_type=code
&redirect_uri=http%3A%2F%2Flocalhost%2Fmyapp%2F
&response_mode=query
&scope=openid%20offline_access%20https%3A%2F%2Fgraph.microsoft.com%2Fuser.read
&state=12345
Then you will get a "code" in the address bar.
Use this code to request access token:
POST /{tenant}/oauth2/v2.0/token HTTP/1.1
client_id=6731de76-14a6-49ae-97bc-6eba6914391e
&scope=https%3A%2F%2Fgraph.microsoft.com%2Fuser.read
&code=OAAABAAAAiL9Kn2Z27UubvWFPbm0gLWQJVzCTE9UkP3pSx1aXxUjq3n8b2JRLk4OxVXr...
&redirect_uri=http%3A%2F%2Flocalhost%2Fmyapp%2F
&grant_type=authorization_code
&client_secret=JqQX2PNo9bpM0uEihUPzyrh
See details from Request an authorization code and Request an access token.
Related
Question
I am trying to write a PowerShell script to get report data via the MS Graph API /reports/credentialUserRegistrationDetails.
When I use Graph Explorer it works just fine, as long as I enable Reports.Read.All on the Modify permissions (Preview) tab.
But, when I try to do it with my script, I just get the error "Calling principal does not have required MSGraph permissions Reports.Read.All"
In all my searches, I can only find how to assign permissions to apps.
Is there some way to make it so I can do it from my script?
My Script
$azContext = Get-AzContext
$token = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate(
$azContext.Account,
$azContext.Environment,
$azContext.Tenant.Id,
$null,
[Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never,
$null,
"https://graph.microsoft.com"
)
$params = #{
Method = "GET"
Uri = "https://graph.microsoft.com/beta/reports/credentialUserRegistrationDetails"
Headers = #{
Authorization = "Bearer $($token.AccessToken)"
"Content-Type" = "application/json"
}
}
Invoke-RestMethod #params
Response
{
"error": {
"code":"Authentication_MSGraphPermissionMissing",
"message":"Calling principal does not have required MSGraph permissions Reports.Read.All",
"innerError": {
"date":"2021-10-19T01:18:36",
"request-id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"client-request-id":"6b8cc3a3-b93b-44bb-b1d4-190c618aa52a"
}
}
}
When I use Graph Explorer it works just fine, as long as I enable
Reports.Read.All on the Modify permissions (Preview) tab.
Its because Microsoft Graph Explorer is a Enterprise Application of Microsoft which is present on every Azure AD tenant just you need to sign in and use it by providing the required permissions.
But when you are writing running your Powershell script it uses Microsoft Azure Powershell . You can verify it by checking the access_token received in JWT Token.So, you need to provide the Reports.Read.All API Permission to the same app in your tenant with appid : 1950a258-227b-4e31-a9cf-717495945fc2 in Enterprise Application >> Permissions and grant the admin consent .After providing the required permissions only it will work.
Another Way will be to create a App registration ,Create a client secret for it and then provide the Reports.Read.All API permission and use the below script:
$TenantName = "tenantname.onmicrosoft.com"
$clientID = "d344e3xxx-xxx-xxxx-xxxx-9c861d363244" # app registration clientId
$clientSecret = "fNc7Q~UNHBgv_xxxxxxxxxxxxxxxxxxxxxx-PD"
$Scope = "https://graph.microsoft.com/.default"
$Body = #{
Grant_Type = "client_credentials"
Scope = $Scope
client_Id = $clientID
Client_Secret = $clientSecret
}
$authUri = "https://login.microsoftonline.com/$TenantName/oauth2/v2.0/token"
$TokenResponse = Invoke-RestMethod -Uri $authUri -Method POST -Body $Body
$Headers = #{
"Authorization" = "Bearer $($TokenResponse.access_token)"
"Content-type" = "application/json"
}
$apiUri = "https://graph.microsoft.com/beta/reports/credentialUserRegistrationDetails"
$response = Invoke-RestMethod -Headers $Headers -Uri $apiUri -Method GET
$response.value
Output:
Note: In Some Tenants Microsoft Azure PowerShell might not be visible from portal , so in that case please use the above solution it will be easier.
For Authorization code flow, try something like this -
#region Auth1
#With User Interaction for Delegated Permission
Add-Type -AssemblyName System.Web
Function Get-AuthCode {
Add-Type -AssemblyName System.Windows.Forms
$form = New-Object -TypeName System.Windows.Forms.Form -Property #{Width = 440; Height = 640 }
$web = New-Object -TypeName System.Windows.Forms.WebBrowser -Property #{Width = 420; Height = 600; Url = ($url -f ($Scope -join "%20")) }
$DocComp = {
$Global:uri = $web.Url.AbsoluteUri
if ($Global:uri -match "error=[^&]*|code=[^&]*") { $form.Close() }
}
$web.ScriptErrorsSuppressed = $true
$web.Add_DocumentCompleted($DocComp)
$form.Controls.Add($web)
$form.Add_Shown( { $form.Activate() })
$form.ShowDialog() | Out-Null
$queryOutput = [System.Web.HttpUtility]::ParseQueryString($web.Url.Query)
$output = #{}
foreach ($key in $queryOutput.Keys) {
$output["$key"] = $queryOutput[$key]
}
#$output
}
Get-AuthCode
#Extract Access token from the returned URI
$regex = '(?<=code=)(.*)(?=&)'
$authCode = ($uri | Select-string -pattern $regex).Matches[0].Value
Write-output "Received an authCode, $authCode"
$tokenBody = #{
Grant_Type = "authorization_code"
Scope = "https://graph.microsoft.com/.default"
Client_Id = $clientId
Client_Secret = $clientSecret
redirect_uri = $redirectUri
code = $authCode
ressource = $resource
}
$tokenResponse = Invoke-RestMethod https://login.microsoftonline.com/common/oauth2/token -Method Post -ContentType "application/x-www-form-urlencoded" -Body $tokenBody -ErrorAction STOP
#endregion Auth1
For delegated permissions use something like below -
$tokenBody = #{
Grant_Type = "password"
Scope = "user.read%20openid%20profile%20offline_access"
Client_Id = $clientId
username = $User
password = $pw
resource = $resource
}
$tokenResponse = Invoke-RestMethod https://login.microsoftonline.com/common/oauth2/token -Method Post -ContentType "application/x-www-form-urlencoded" -Body $tokenBody -ErrorAction STOP
#endregion Auth2
For Application permissions (using client credential flow) use something like this
$tokenBody = #{
Grant_Type = "client_credentials"
Scope = "https://graph.microsoft.com/.default"
Client_Id = $clientId
Client_Secret = $clientSecret
}
$tokenResponse = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$Tenantid/oauth2/v2.0/token" -Method POST -Body $tokenBody
#endregion Auth3
Despite what Method you have chosen, the tokenRepsonse Variable is holding our Key to Query against the Microsoft GRAPH API.
We want a list of all Teams in our Tenant, so this require propriate Application Permission. So for example- our Powershell to get a Full List of all Teams look like this -
$headers = #{
"Authorization" = "Bearer $($tokenResponse.access_token)"
"Content-type" = "application/json"
}
$URL = "https://graph.microsoft.com/beta/groups?`$filter=resourceProvisioningOptions/Any(x:x eq 'Team')"
$AllTeams = (Invoke-RestMethod -Headers $headers -Uri $URL -Method GET).value
Thanks.
I finally gave up on using the REST APIs and started using Microsoft.Graph PowerShell Modules. I find the documentation pretty sparse, but at least it works for what I need. :)
Import-Module "Microsoft.Graph.Identity.Signins"
Import-Module "Microsoft.Graph.Users"
Import-Module "Microsoft.Graph.Groups"
Connect-MgGraph -TenantId $TenantId -Scopes "Directory.Read.All", "UserAuthenticationMethod.Read.All" -ForceRefresh
Select-MgProfile -Name "beta"
$report = Get-MgReportCredentialUserRegistrationDetail
I am trying to use MS Graph API to configure Azure AD Connect Cloud Sync from these instructions but I am having trouble calling this endpoint in Powershell using client credentials:
https://graph.microsoft.com/beta/servicePrincipals/{SERVICE_PRINCIPAL_ID}/synchronization/jobs
I can successfully call this using the Graph Explorer, but no luck using Application permission and authentication with a client secret in Powershell. I get 401 Unauthorized error. I can call other endpoints like:
https://graph.microsoft.com/beta/servicePrincipals/{SERVICE_PRINCIPAL_ID} # no /synchronization/jobs at the end
The application has the API permissions: Directory.ReadWrite.All and Application.ReadWrite.OwnedBy (Application) The permissions has been granted by the admin:
Below is the detail of the code I use to authenticate:
$Body = #{
'tenant' = $TenantId
'client_id' = $ClientId
'scope' = 'https://graph.microsoft.com/.default'
'client_secret' = $ClientSecret
'grant_type' = 'client_credentials'
}
$Params = #{
'Uri' = "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token"
'Method' = 'Post'
'Body' = $Body
'ContentType' = 'application/x-www-form-urlencoded'
}
$AuthResponse = Invoke-RestMethod #Params
And this is how I call the endpoint:
$Headers = #{
'Authorization' = "Bearer $($AuthResponse.access_token)"
}
$Params = #{
Uri = "https://graph.microsoft.com/beta/servicePrincipals/{SERVICE_PRINCIPAL_ID}/synchronization/jobs"
Method = 'Get'
ContentType = 'application/json'
Headers = $Headers
}
$res = Invoke-RestMethod #Params
And the error:
Invoke-RestMethod : The remote server returned an error: (401) Unauthorized
If I use the token from the Graph Explorer it works...
My token from Powershell decoded contains this "roles" section but no "scp" like in the Graph Explorer token:
"roles": [
"Application.ReadWrite.OwnedBy",
"Directory.ReadWrite.All"
],
Full token obfuscated:
{
"aud": "https://graph.microsoft.com",
"iss": "https://sts.windows.net/{TENANT_ID}/",
"iat": 1629836586,
"nbf": 1629836586,
"exp": 1629840486,
"aio": "{AIO}",
"app_displayname": "AppForAdConnect2",
"appid": "{APPID}",
"appidacr": "1",
"idp": "https://sts.windows.net/{TENANT_ID}/",
"idtyp": "app",
"oid": "{OID}",
"rh": "{RH}",
"roles": [
"Application.ReadWrite.OwnedBy",
"Directory.ReadWrite.All"
],
"sub": "{SUB}",
"tenant_region_scope": "NA",
"tid": "{TENANT_ID}",
"uti": "{UTI}",
"ver": "1.0",
"wids": [
"{WID}"
],
"xms_tcdt": 1584535155
}
Thank you for your help!
I tried with same permissions that you have provided for your Service Principal with the below script and it successfully gave me the output.
$TenantName = "<your azure AD tenant primary domain here (ex-abc.onmicrosoft.com)>"
$clientID = "Application (client) ID of the registered App here"
$clientSecret = "client secret for the app"
$Scope = "https://graph.microsoft.com/.default"
$Body = #{
Grant_Type = "client_credentials"
Scope = $Scope
client_Id = $clientID
Client_Secret = $clientSecret
}
$authUri = "https://login.microsoftonline.com/$TenantName/oauth2/v2.0/token"
$TokenResponse = Invoke-RestMethod -Uri $authUri -Method POST -Body $Body
$spobject = "ServicePrincipalObjectID that was returned after you did post operation in the document"
$Headers = #{
"Authorization" = "Bearer $($TokenResponse.access_token)"
"Content-type" = "application/json"
}
$apiUri = "https://graph.microsoft.com/beta/servicePrincipals/$spobject/synchronization/jobs"
$response = Invoke-RestMethod -Headers $Headers -Uri $apiUri -Method GET
$response
Which is same as the output I get from Graph Explorer.
Note: If you are using the highlighted object ID (Image 1 below) in serviceprincipalobjectID it will throw 401 error instead of that you have to go Enterprise Application and use the objectID (image 2 below) shown there .
Update:
As Discussed we are not able to do the above operation using client credentials for the AD2AADSync Service Principal but we can use another way to do those operations.
We can use Microsoft Graph Powershell SDK :
Step-1 : Install the Module in powershell using below command
Install-Module Microsoft.Graph
Step-2 : Set the profile for the above module to beta as we will be using it to get the synchronization jobs.
Select-MgProfile -Name "beta"
Step-3 : Use the below script to get the values of the synschronization job.
Connect-MgGraph -Scopes "Application.ReadWrite.All","Directory.ReadWrite.All"
$value = Get-MgServicePrincipalSynchronizationJob -ServicePrincipalId "AD2AADSync_service_principal_objectId"
$value
$value.Status
Output:
Reference:
Microsoft.Graph.Applications Module | Microsoft Docs
Ok so the solution was to add the role "Hybrid Identity Administrator" to the Service Principal or the User used to call the endpoint "synchronization/jobs". For some reason this application template needed this additional role and the API Permission enough is not enough.
Our company is migrating user info to our Microsoft tenant. Part of this info includes the profile picture.
Most of the information I am able to update using Set-MSolUser, but for the profile picture I've been trying to use Microsoft Graph, with no success.
I am a tenant admin and yet I've had no success in updating users' profile pictures. Here's my code:
$token = [my-token]
$Headers = #{
"Authorization" = "Bearer $token"
"Content-Type" = "image/jpeg"
}
Invoke-RestMethod -Uri 'https://graph.microsoft.com/v1.0/users/{user-id}/photo/$value' -Method Put -Headers $Headers
This has just been for test purposes so I'm trying with a single user id and no picture. This has been the output:
"error": {
"code": "ErrorAccessDenied",
"message": "AccessDeniedException",
And the same thing happens when querying directly through the graph website:
Per the documentation, certain permissions are necessary (contact, group or user read.write) which I have ticked all on the Graph website, but still nothing.
Any ideas would be greatly appreciated.
Under user permissions you able to edit only your own picture.
You have to create an Azure AD application with User.ReadWrite.*All* permission to edit others' pictures.
Go to:
portal.azure.com -> Active Directory -> App Registration -> New Registration. Then under API permissions you grant User.ReadWrite.All and click grant admin consent. Then under Certificates and Secrets you create an app secret.
$AzAppSecret = 'abcDEFghiJKLmnoPQRstuVWXyz01234567' # from AzureAD -> App Registrations -> YourApp -> Certificates & secrets
$AzAppId = 'AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA' # from AzureAD -> App Registrations -> YourApp -> Overview -> Application (client) ID
$AzTenantId = 'BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB' # from AzureAD -> App Registrations -> YourApp -> Overview -> Directory (tenant) ID
$AzUserUPN = 'username#example.com' # from AzureAD -> Users -> YourUser -> User Principal Name
$AzUserImage = 'S:\samplepic.jpg' # Jpeg file
# Request token
$tokenRequestBody = #{
Grant_Type = "client_credentials"
Scope = "https://graph.microsoft.com/.default"
Client_Id = $AzAppID
Client_Secret = $AzAppSecret
}
$tokenRequestUri = [String]::Format('https://login.microsoftonline.com/{0}/oauth2/v2.0/token', $AzTenantId)
$tokenResponse = Invoke-RestMethod -Uri $tokenRequestUri -Method 'POST' -Body $tokenRequestBody -ErrorAction Stop
$accessToken = $tokenResponse.access_token
$uri = [String]::Format('https://graph.microsoft.com/v1.0/users/{0}/photo/$value', $AzUserUPN)
$Headers = #{
'Authorization' = [String]::Format('Bearer {0}', $accessToken)
'Content-Type' = 'image/jpeg'
}
Invoke-RestMethod -Method Put -Uri $uri -InFile $AzUserImage -Headers $Headers
Alternative way:
# Load Assembly System.Net.Http
# In PS7 built-in, on PS5 - Download NuGet package from https://www.nuget.org/packages/System.Net.Http/ and unzip using 7zip
[void][System.Reflection.Assembly]::LoadFile('P:\path-to\system.net.http.4.3.4\runtimes\win\lib\netstandard1.3\System.Net.Http.dll')
# Prepare httpClient and URI
$httpClient = [System.Net.Http.HttpClient]::new()
$httpClient.DefaultRequestHeaders.Authorization = [String]::Format('Bearer {0}', $accessToken)
# Prepare Content
$content = [System.Net.Http.ByteArrayContent]::new([System.IO.File]::ReadAllBytes($AzUserImage));
$content.Headers.ContentType = "image/jpeg";
# Run
$task = $httpClient.PutAsync($uri,$content)
$task.Wait()
$task.Result.IsSuccessStatusCode
Remember to keep Application secret private
This is expected error in case if
for app using application permissions, User.ReadWrite.All permission is missing when calling this Microsoft Graph endpoint
token is acquired with delegated permissions
Documentation says the following in this regard:
update the photo of any user in the organization, your app must have
the User.ReadWrite.All application permission and call this API
under its own identity, not on behalf of a user
And last but not least, profile image is expected to be passed via request body, in case of PowerShell the request could be constructed like this:
$Headers = #{
"Authorization" = "Bearer $access_token"
"Content-Type" = "image/jpeg"
}
$url = "https://graph.microsoft.com/v1.0/users/$($userId)/photo/$value"
$profilePath = "--path to profile file--"
Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/users/$($userId)/photo/$value" -Method Put -InFile $profilePath -Headers $Headers
I am trying to get the list of all groups where the resourceProvisioningOptions = Team, here is the url which gets all the data through API call:
$clientID = xxxx
$tenantName = xxxx
$ClientSecret = xxxx
$resource = "https://graph.microsoft.com/"
$ReqTokenBody = #{
Grant_Type = "client_credentials"
Scope = "https://graph.microsoft.com/.default"
client_Id = $clientID
Client_Secret = $clientSecret
}
$authheader = #{
'Authorization' = "Bearer $($Tokenresponse.access_token)"
'Content-Type'='application\json'
}
$TokenResponse = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$TenantName/oauth2/v2.0/token" -Method POST -Body $ReqTokenBody
$test = "'Team'"
$apiUrl = 'https://graph.microsoft.com/beta/groups?$filter=resourceProvisioningOptions/Any(x:x eq {0})' -f $test
$Data = Invoke-RestMethod -Uri $apiUrl -Headers $authheader -Body $ReqTokenBody -Method Get
$Groups = ($Data | select-object Value).Value | Select-Object displayName, id, description, mail | Out-File .\texxtfile.txt
However, I am getting a 401 error when I try to run the script even though I have all the permissions required to make the API call.
You may have picked application permissions in your AAD application. There is an additioanl step. You will need to admin consent your application in the app registration portal to use Group.Read.All to run this. If you have not done this in the UI this will fail.
I am attempting to make a Microsoft Graph API call using powershell 5.1. I have Registred the app. I use the AppID and secret to obtain an Authorization Bearer token. I then construct my API request. The security/events endpoint only returns a Forbidden response.
API Permissions Granted
My script to make the call looks like this:
# Define AppId, secret and scope, your tenant name and endpoint URL
$AppId = [myAPPID]
$AppSecret = [myAPPSecret]
$Scope = "https://graph.microsoft.com/.default"
$TenantName = "[mytenant].onmicrosoft.com"
$Url = "https://login.microsoftonline.com/$TenantName/oauth2/v2.0/token"
# Add System.Web for urlencode
Add-Type -AssemblyName System.Web
$Body = #{
client_id = $AppId
client_secret = $AppSecret
scope = $Scope
grant_type = 'client_credentials'
}
# Splat the parameters for Invoke-Restmethod for cleaner code
$PostSplat = #{
ContentType = 'application/x-www-form-urlencoded'
Method = 'POST'
# Create string by joining bodylist with '&'
Body = $Body
Uri = $Url
}
$Request = Invoke-RestMethod #PostSplat
$Header = #{
Authorization = "$($Request.token_type) $($Request.access_token)"
}
$Uri = "https://graph.microsoft.com/v1.0/security/events"
$SecurityAlertsRequest = Invoke-RestMethod -Uri $Uri -Headers $Header -Method Get -ContentType "application/json"
Just looking to see If I have missed something obvious. Is there any other reason this would response with Forbidden for this configuration?
You have granted delegated permissions to the app.
Those only apply when there is a user signed in to your app and you acquire an access token to act on their behalf.
You need to grant application permissions to your app in Azure AD.