The only missing thing is that in your body, you should provide an array instead of a single object, here is a working example:
$uri = "https://dev.azure.com/bauca/_apis/securityroles/scopes/distributedtask.environmentreferencerole/roleassignments/resources/{project_id}_{env_id}"
$id_of_user = 'YOUR_USER_ID'
$tokenbase = 'YOUR_PAT'
$header = @{
"authority"="dev.azure.com"
"Authorization"= "Basic $tokenbase"
"method"="PUT"
"path"="/{ORG}/_apis/securityroles/scopes/distributedtask.environmentreferencerole/roleassignments/resources/{project_id}_{env_id}"
"scheme"="https"
"accept"="application/json;api-version=5.0-preview.1;excludeUrls=true;enumsAsNumbers=true;msDateFormat=true;noArrayWrap=true"
"accept-encoding"="gzip, deflate, br"
"accept-language"="en-US,en;q=0.9,pt;q=0.8,nl;q=0.7"
"origin"="https://dev.azure.com"
"x-vss-reauthenticationaction"="Suppress"
} `
$body = "[{`"userId`":`"${id_of_user}`",`"roleName`":`"Administrator`"}]"
Invoke-RestMethod -UseBasicParsing -Uri $uri -Method "PUT" -Body $body -ContentType "application/json" -Headers $header
The returned results should be something like:
@{displayName=USER_NAME; id=USERID; uniqueName=USER_UNIQUENAME}
The API documentation is not clear about that, so, in this situations what I'd recommend you to do, is just use Chrome to do the requests through the UI, then inspect element and grab the network information of the request, after that 'Click with the right button' and then select 'Copy to Powershell' you'll see exactly what is the 'body' required to perform the request.