When attempting to programmatically add/update a function key, I receive the following error:
StatusCode: 401, ReasonPhrase: 'Unauthorized'
Code:
Executing the following code results in the error described above.
static void FunctionKey(string resourceGroupName, string functionAppName, string functionName, NameValuePair kv)
{
var resource = $"subscriptions/{SubscriptionId.Value}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{functionAppName}/functions/{functionName}/keys/{kv.Name}?api-version=2022-03-01";
var httpClient = new HttpClient() { BaseAddress = new Uri("https://management.azure.com/") };
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", AuthToken.Value);
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var json = #"{
""Properties"": {
""Name"": ""ApiKey"",
""Value"": ""some_value""
}
}";
using (var content = new StringContent(json, Encoding.UTF8, "application/json"))
{
var response = httpClient.PostAsync(resource, content).Result;
if (!response.IsSuccessStatusCode)
throw new Exception($"Error: Failed to register function key for {functionName}");
}
}
Research:
I was successful when performing this task in the the documentation emulator.
I tried to reproduce the same in my environment via Postman and got below results:
When I ran the below query without including bearer token, I got same error with 401 Unauthorized like below:
PUT https://management.azure.com/subscriptions/<subID>/resourceGroups/<rgname>/providers/Microsoft.Web/sites/<funcappname>/functions/<funcname>/keys/<keyname>?api-version=2022-03-01
{
"Properties":
{
"Name": "keyname",
"Value": "xxxxxxxxxxxx"
}
}
Response:
After passing the token, I'm able to create function key successfully like below:
When I checked the same portal, srikey appeared under function keys like below:
In your case, you are using httpClient.PostAsync which means
POST method.
When I used POST method for below query, I too got 404 Not found error like below:
POST https://management.azure.com/subscriptions/<subID>/resourceGroups/<rgname>/providers/Microsoft.Web/sites/<funcappname>/functions/<funcname>/keys/<keyname>?api-version=2022-03-01
{
"Properties":
{
"Name": "keyname",
"Value": "xxxxxxxxxxxx"
}
}
Response:
To resolve the error, make sure to use PUT method by changing httpClient.PostAsync method to httpClient.PutAsync method.
Reference:
HttpClient.PutAsync Method (System.Net.Http) | Microsoft
Related
I'm using a mobile app and am receiving an Unauthorized response when attempting to post to an Azure Function and providing a function key.
Error:
StatusCode: 401, ReasonPhrase: 'Unauthorized'
Code:
let postToAsync (baseAddress:string) (resource:string) (payload:Object) =
async {
let tokenSource = new CancellationTokenSource(TimeSpan(0,0,30));
let token = tokenSource.Token;
try
let tokens = resource.Split("?code=")
let functionKey = tokens.[1]
use client = httpClient baseAddress
client.DefaultRequestHeaders.Add("x-functions-key", functionKey)
client.DefaultRequestHeaders.Accept.Add(MediaTypeWithQualityHeaderValue("application/json"))
let json = JsonConvert.SerializeObject(payload)
let content = new StringContent(json, Encoding.UTF8, "application/json")
let! response = client.PostAsync(resource.Replace($"?code={functionKey}",""), content, token) |> Async.AwaitTask
Debug.WriteLine $"\n\n{baseAddress}{resource}\nSuccess: {response.IsSuccessStatusCode}\n\n"
return response
with ex -> ...
} |> Async.StartAsTask
Note:
My Azure Function's AuthorizationLevel is set to Function.
I can call the function successfully when I publish it manually from Visual Studio.
However, when I deploy the function using Pulumi, I receive an Unauthorized response. I believe this is because Pulumi constrains me to add access policies for each Function App.
Versioning:
<TargetFramework>net6.0</TargetFramework>
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
oauth2/v2.0:
I think the following link provides a clue to why I'm observing the issue. However, I still don't know how to resolve it.
Connectivity
I launched Log Stream and observed that the URL is correct:
Access Control:
Please note that the difference between the Function App that I created without using Pulumi, which lets me post successfully, versus the Function App that was generated using Pulumi, is an Access Policy per Function App with Pulumi.
public static class AccessPolicies
{
public static void Build(string policyName, string functionName, Dictionary<string, CustomResource> registry)
{
var resourceGroup = registry[nameof(ResourceGroup)] as ResourceGroup;
var keyVault = registry[nameof(KeyVault)] as KeyVault;
var functionApp = registry[functionName] as FunctionApp;
var result = new AccessPolicy(policyName, new AccessPolicyArgs {
KeyVaultId = keyVault.Id,
TenantId = TenantId.Value,
ObjectId = functionApp.Identity.Apply(v => v.PrincipalId ?? "11111111-1111-1111-1111-111111111111"),
KeyPermissions = new[] { "Get", },
SecretPermissions = new[] { "Get", },
});
registry.Add($"{policyName}-{functionName}", result);
}
}
}
I tried to reproduce the same in my environment via Postman and got below results:
I have one function app with http function named srifunction like below:
I generated one bearer token with same scope as you like below:
POST https://login.microsoftonline.com/<tenantID>/oauth2/v2.0/token
grant_type:client_credentials
client_id: <appID>
client_secret: <secret_value>
scope: https://management.azure.com/.default
Response:
When I used the above token to call function, I got 401 Unauthorized error same as you like below:
POST https://<funcappName>.azurewebsites.net/api/<function_name>
Authorization: Bearer <token>
If you pass function key in token value, you will still get 401 Unauthorized error like below:
POST https://<funcappName>.azurewebsites.net/api/<function_name>
Authorization: Bearer <function key>
To call function using function key, you need to include key value
in x-functions-key header instead of Bearer token.
When I included the above header, I am able to call the function successfully like below:
POST https://<funcappName>.azurewebsites.net/api/<function_name>
x-functions-key: <function key>
I get the following error
Code: OrganizationFromTenantGuidNotFound
Message: The tenant for tenant guid 'tenantId' does not exist.
I created a .Net Core console app to send emails using the following 2 functions
I used the following namespaces
using Microsoft.Graph;
using Microsoft.Graph.Auth; //In .Net Core this is in preview only
using Microsoft.Identity.Client;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
Common Email Message to be sent in both the functions
var message = new Message
{
Subject = "Meet for lunch?",
Body = new ItemBody
{
ContentType = BodyType.Html,
Content = "The new cafeteria is open."
},
ToRecipients = new List<Recipient>()
{
new Recipient
{
EmailAddress = new EmailAddress
{
Address = "my email id"
}
}
},
CcRecipients = new List<Recipient>()
{
new Recipient
{
EmailAddress = new EmailAddress
{
Address = "2nd email id"
}
}
}
};
Scope required in the following functions
string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
1st Method
var confidentialClient = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithClientSecret(clientSecret)
.WithAuthority(new Uri($"https://login.microsoftonline.com/{tenantId}/v2.0"))
.Build();
// Retrieve an access token for Microsoft Graph (gets a fresh token if needed).
var authResult = await confidentialClient
.AcquireTokenForClient(scopes)
.ExecuteAsync().ConfigureAwait(false);
var token = authResult.AccessToken;
// Build the Microsoft Graph client. As the authentication provider, set an async lambda
// which uses the MSAL client to obtain an app-only access token to Microsoft Graph,
// and inserts this access token in the Authorization header of each API request.
GraphServiceClient graphServiceClient =
new GraphServiceClient(new DelegateAuthenticationProvider(async (requestMessage) =>
{
// Add the access token in the Authorization header of the API request.
requestMessage.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", token);
})
);
try
{
await graphServiceClient.Users["my user guid"]
.SendMail(message, false)
.Request()
.PostAsync();
//I also tried with
await graphServiceClient.Me
.SendMail(message, false)
.Request()
.PostAsync();
}
catch (Exception ex)
{
}
2nd Method
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantId)
.WithClientSecret(clientSecret)
.Build();
var authResultDirect = await confidentialClientApplication.AcquireTokenForClient(scopes).ExecuteAsync().ConfigureAwait(false);
//Microsoft.Graph.Auth is required for the following to work
ClientCredentialProvider authProvider = new ClientCredentialProvider(confidentialClientApplication);
GraphServiceClient graphClient = new GraphServiceClient(authProvider);
try
{
await graphClient.Users["my user id"]
.SendMail(message, false)
.Request()
.PostAsync();
//I also tried the following
await graphClient.Me
.SendMail(message, false)
.Request()
.PostAsync();
}
catch (Exception ex)
{
}
I have given all the required permissions. Some of the permissions are extra and may not be required. I gave the permissions to check if those permissions are the reason why I am getting the error but nothing changed.
I have also checked the token I am getting on jwt.io. I am getting the following roles
"roles": [
"Mail.ReadWrite",
"User.ReadWrite.All",
"Mail.ReadBasic.All",
"User.Read.All",
"Mail.Read",
"Mail.Send",
"Mail.ReadBasic"
],
I don't see any issue with the code or with the permissions that I have given but I am still missing something which I am unable to figure out. The reason why I say this is because when I tried to get user information by calling the api - https://graph.microsoft.com/v1.0/users, I get the users information as below.
value = [
{
"businessPhones": [],
"displayName": "user display name",
"givenName": "user first name",
"jobTitle": null,
"mail": null,
"mobilePhone": null,
"officeLocation": null,
"preferredLanguage": "en",
"surname": "user last name",
"userPrincipalName": "user information",
"id": "user id"
}
]
Any help is appreciated
This is because your Azure AD tenant does not have an Exchange online license under the O365 subscription.
As a result, your tenant does not have the ability to send Email messages.
If you have an o365 subscription, you'll see it here.
1.
2.
3.
#Chauncy Zhou was absolutely right with the solution.
But there are a couple of things you need to do if you are an individual because you will not get Office 365 license in your azure account as an individual.
I created a Developer.Microsoft.com account and then I used that account to create a new Azure account where I was able to add the license for Office for the Active Directory and that user. Rest of the code is already there and it works fine.
I'm trying to read all accounts my user is associated with. The Documentation claims that this should be possible by calling:
GET https://app.vssps.visualstudio.com/_apis/accounts?api-version=5.1
Because the docs are kind of confusing it could be that I have to call
GET https://app.vssps.visualstudio.com/_apis/accounts?ownerId={GUID}&api-version=5.1
instead.
I'm using OAuth-Authentication. In order to get it work I created an ASP.NET Core application. I created an app registration in DevOps and I retrieve an OAuth token with the following scopes without any problem:
vso.auditlog
vso.connected_server
vso.dashboards
vso.entitlements
vso.environment_manage
vso.graph
vso.identity
vso.loadtest
vso.machinegroup_manage
vso.memberentitlementmanagement
vso.profile
vso.project
vso.securefiles_read
vso.security_manage
vso.taskgroups_read
vso.tokenadministration
vso.tokens
vso.variablegroups_read
vso.wiki
According to the docs only vso.profile should be necessary for this request.
However the result I receive is always:
HttpRequestException: Response status code does not indicate success: 401 (TF400813: The user '{AZURE_TENANT_ID}\{MY_MAIL_ADDRESS}' is not authorized to access this resource.).
Other requests are working just fine e.g.:
GET https://dev.azure.com/{organization}/{project}/_apis/build/builds?api-version=5.1
The accounts-request is special because it can be sent without setting the context to a specific organisation or project. I guess this the reason for the different results.
EDIT
After trying ot today using the both URLs mentioned above I now get 400 as the response status code. This is a sample Bearer-Token I got after I decoded it:
{
"nameid": "1340eb0b-cabf-476c-a950-a070c34ca367",
"scp": "vso.profile",
"aui": "a2d8bdf0-9406-415a-aa79-bee9e2600c37",
"appid": "e1bea2a2-****-****-****-************",
"iss": "app.vstoken.visualstudio.com",
"aud": "app.vstoken.visualstudio.com",
"nbf": 1587395030,
"exp": 1587398630
}
Here is some simplified C# code I use:
HttpClient client = _clientFactory.CreateClient("DevOps");
var token = await _authHelper.GetTokenAsync(tokenType);
client.DefaultRequestHeaders.Add("Authorization", $"Bearer { token.AccessToken}");
var uri = "https://app.vssps.visualstudio.com/_apis/accounts?api-version = 5.1";
try
{
var response = await client.GetAsync(uri);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
return content;
}
catch (Exception ex)
{
// ex.Message = Response status code does not indicate success: 400 (Bad Request).
// ...
throw;
}
I have created a Lambda Authorizer on a AWS API Gateway, which calls a Lambda Function. Following is the code in the Lambda Function written in Node.js 8.0 code.
exports.handler = function(event, context, callback) {
var token = event.authorizationToken;
switch (token.toLowerCase()) {
case 'allow':
callback(null, generatePolicy('user', 'Allow', event.methodArn));
break;
case 'deny':
callback(null, generatePolicy('user', 'Deny', event.methodArn));
break;
case 'unauthorized':
callback("Unauthorized"); // Return a 401 Unauthorized response
break;
default:
callback("Error: Invalid token");
}
};
// Help function to generate an IAM policy
var generatePolicy = function(principalId, effect, resource) {
var authResponse = {};
authResponse.principalId = principalId;
if (effect && resource) {
var policyDocument = {};
policyDocument.Version = '2012-10-17';
policyDocument.Statement = [];
var statementOne = {};
statementOne.Action = 'execute-api:Invoke';
statementOne.Effect = effect;
statementOne.Resource = resource;
policyDocument.Statement[0] = statementOne;
authResponse.policyDocument = policyDocument;
}
// Optional output with custom properties of the String, Number or Boolean type.
authResponse.context = {
"stringKey": "stringval",
"numberKey": 123,
"booleanKey": true
};
return authResponse;
}
(The above sample code if from the web site https://markpollmann.com/lambda-authorizer/)
If I save and Test this function by passing an invalid value for authorizationToken, I get expected result which is below.
Response:
{
"errorMessage": "Error: Invalid token"
}
Request ID:
"e93567c0-fcbb-4cb1-b0b3-28e9c1b30162"
But If I call this API from Postman, by passing the value in the header, I get the following response. I am getting this error for any value in the header i.e, deny, allow, unauthorized, wrong etc.
{
"message": null
}
The status message in postman shows "500 Internal Server Error". Following is the detail from header section in postman.
content-length →16
content-type →application/json
date →Fri, 08 Mar 2019 14:07:57 GMT
status →500
x-amz-apigw-id →W89kFDRDoEFxYg=
x-amzn-errortype →AuthorizerConfigurationException
x-amzn-requestid →92f31d11-41ab-11e9-9c36-97d38d96f31b
I do not understand why the API is returning the above response and error message, while the Lambda test is working fine.
I have already gone through the following two threads in SO, but the answers/comments couldn't help in my case.
AWS API Gateway with custom authorizer returns AuthorizerConfigurationException
AWS API Gateway Custom Authorizer AuthorizerConfigurationException
I have understood the reason why I was getting message = null for the invalid input. The default block in the switch case was using parameter "Error: Invalid token" in the callback() method. API Gateway only identifies "Allow", "Deny" and "Unauthorized" as valid values. These values are also case sensitive. If any string values other than these values are passed to the callback() method, then API Gateway will return message=null to the client.
I am new to developing Alexa skills so I am using a sample I found on the web as a C# endpoint hosted on Azure. It works correctly with the Alexa console but when I try to test the same endpoint with the Postman app, I get a 400 error.
When I use the Alexa console, it displays the JSON input that it sends to the endpoint and the JSON output that it receives from the endpoint. If I copy the JSON input and paste it into Postman and send it to the same endpoint, I get a 400 error. Obviously, I am missing something.
The following are my two source files and the JSON input.
RollTheDice.cs
public static class RollTheDice
{
[FunctionName("RollTheDice")]
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]HttpRequestMessage req, TraceWriter log)
{
log.Info("C# HTTP trigger function processed a request.");
var speechlet = new RollTheDiceSpeechlet();
return await speechlet.GetResponseAsync(req);
}
}
RollTheDiceSpeechlet.cs
public class RollTheDiceSpeechlet : SpeechletBase, ISpeechletWithContext
{
public SpeechletResponse OnIntent(IntentRequest intentRequest, Session session, Context context)
{
try
{
// Default to 6 sides if not specified
if (!int.TryParse(intentRequest.Intent.Slots["DiceType"].Value, out int numSides))
numSides = 6;
var rollResults = new Random().Next(Math.Max(1, numSides - 1)) + 1; // Account for random returning '0'
return new SpeechletResponse
{
ShouldEndSession = false,
OutputSpeech = new PlainTextOutputSpeech { Text = $"I rolled a {numSides} sided die and got a {rollResults}." }
};
}
catch (Exception ex)
{
return new SpeechletResponse
{
ShouldEndSession = false,
OutputSpeech = new PlainTextOutputSpeech { Text = ex.Message }
};
}
}
public SpeechletResponse OnLaunch(LaunchRequest launchRequest, Session session, Context context)
{
return new SpeechletResponse
{
ShouldEndSession = false,
OutputSpeech = new PlainTextOutputSpeech { Text = "Welcome to the Roll the Dice. Ask me to roll the dice." }
};
}
public void OnSessionEnded(SessionEndedRequest sessionEndedRequest, Session session, Context context)
{
return;
}
public void OnSessionStarted(SessionStartedRequest sessionStartedRequest, Session session, Context context)
{
return;
}
}
JSON Input
Again, everything works fine but when I test it with Postman I get a 404 error.
The endpoint is C# serverless function that I developed in Visual Studio 201.
When I run it locally, I copy/paste the URL in the Postman app and send a post. See attached screenshots.
As the error suggest you are missing Signature and SignatureCertChainUrl headers. These helps to protect your endpoint and verify that incoming requests were sent by Alexa. Any requests coming from other sources should be rejected. When you test it via Test Console these headers are included and you get successful response.
Headers:
Signature
SignatureCertChainUrl
There are two parts to validating incoming requests:
Check the request signature to verify the authenticity of the request.
Check the request timestamp to ensure that the request is not an old request.
More information on verifying that the request was sent by Alexa here