For reference I am attempting to reproduce the solution talked about here: https://www.tech-findings.com/2020/02/securing-logic-app-with-azure-active-directory.html to use API Management to secure an Azure Logic App.
I am getting a JWT Error. When I visit the app url in the browser it gives:
{ "statusCode": 404, "message": "Resource not found" }
In the API Management Service test I get:
HTTP/1.1 401 Unauthorized
Following the trace through it shows:
validate-jwt (-0.111 ms)
{
"message": "JWT Validation Failed: JWT not present.."
}
I did some googling and tried the solutions at:
JWT validation failure error in azure apim
and
https://learn.microsoft.com/en-us/answers/questions/108008/azure-apim-jwt-token-validation-policy.html
Here is the inbound policy of from the API Management design:
<policies>
<inbound>
<base />
<set-method id="apim-generated-policy">POST</set-method>
<rewrite-uri id="apim-generated-policy" template="/request/paths/invoke//?api-version=2016-06-01&sp=/triggers/request/run&sv=1.0&sig={{[[LOGIC APP NAME]]_request-invoke_XXXXXXXXXXXXXXXXXXXXXXXX}}" />
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Request is not authorized or token failed" require-expiration-time="false" require-scheme="Bearer" require-signed-tokens="true">
<openid-config url="https://login.windows.net/[[TENANT NAME]].onmicrosoft.com/.well-known/openid-configuration" />
<audiences>
<audience>[[THE ID OF A REGISTERED APP]]</audience>
</audiences>
</validate-jwt>
<set-header name="Authorization" exists-action="delete" />
<set-header name="apim-generated-policy" exists-action="delete" />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
This is the manifest of the registered app:
{
"id": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"acceptMappedClaims": null,
"accessTokenAcceptedVersion": 2,
"addIns": [],
"allowPublicClient": null,
"appId": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"appRoles": [],
"oauth2AllowUrlPathMatching": false,
"createdDateTime": "2020-12-22T19:48:36Z",
"disabledByMicrosoftStatus": null,
"groupMembershipClaims": null,
"identifierUris": [
"api://XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
],
"informationalUrls": {
"termsOfService": null,
"support": null,
"privacy": null,
"marketing": null
},
"keyCredentials": [],
"knownClientApplications": [],
"logoUrl": null,
"logoutUrl": null,
"name": "LabsTestApp",
"oauth2AllowIdTokenImplicitFlow": false,
"oauth2AllowImplicitFlow": false,
"oauth2Permissions": [],
"oauth2RequirePostResponse": false,
"optionalClaims": null,
"orgRestrictions": [],
"parentalControlSettings": {
"countriesBlockedForMinors": [],
"legalAgeGroupRule": "Allow"
},
"passwordCredentials": [],
"preAuthorizedApplications": [],
"publisherDomain": "[[TENANT NAME]].onmicrosoft.com",
"replyUrlsWithType": [],
"requiredResourceAccess": [],
"samlMetadataUrl": null,
"signInUrl": null,
"signInAudience": "AzureADandPersonalMicrosoftAccount",
"tags": [],
"tokenEncryptionKeyId": null
}
Hoping you can help out - point me in the right direction.
For this question, there are more than one problem in your steps.
1. You mentioned the error { "statusCode": 404, "message": "Resource not found" } when you request the url in browser. The reason is when you request it in browser, it request with Get method but the url should be request with Post method. So it shows 404 not found.
2. When you test in API Management service, it shows 401 Unauthorized. The reason for this error is you did not provide the access token or the access token you provided is invalid. The steps in the document you mentioned are incomplete, please refer to the steps below:
1). First please make sure you have completed all of the steps in the document you provided.
2). Then go to the app you registered in azure ad and click "Manifest" tab, add a appRole in the json of "Manifest".
You can specify a name(anything you want) for this role, I named the role as Writer as the screenshot above shows. And you can also specify a "id"(in GUID format) as the value of the id field in appRole. For more details of add appRole, you can refer to this document.
3). You need to register another app in azure ad as the client app. Do same register operation as your document shows to register the other app, I registered the app and named huryGetToken4. Go to this app and click "API permissions" tab, click "Add a permission" and find the original app you registered, then add the permission Writer.
After add the Writer permission, you also need to click "Grant admin consent for xxx".
Then click "Certificates & secrets" tab, click "New client secret" to generate a client secret. Copy this secret because it will just show one time.
4). Then you need to get access token, please refer to the screenshot below to request for access token.
In the screenshot above, you need to replace the <tenant id> with your tenant id in the host url. And you also need to input the first three parameters. The last parameter grant_type is static.
5). Request for the access token, you will get the response like below screenshot.
Copy the value of access_token and paste it to this page to decode the token, you can see the claim roles with Writer permission in it. This claim is what you need to check in the <validate-jwt> policy in your APIM.
6). Go to your apim and click the pencil icon of validate-jwt policy.
7). Edit the "Reauired claims" like screenshot below:
8). After that, you can test the api in APIM service. Add a header with key: Authorization, value: Bearer <your access token>(note there is a blank between Bearer and access token).
Related
I have a problem with Azure AD B2C while I want to get users with their custom attribute set during creation. All is being done with Graph API (string url = "https://graph.microsoft.com/" + "v1.0" + apiFunction;). I registered user using API with custom attribute "extension_08eedadaf5f*********53e8389608e_arvatoId": "1001", but when I call /users then I'm not getting this custom attribute in result. All I have are:
{ "#odata.context": "https://graph.microsoft.com/v1.0/$metadata#users/$entity", "id": "40861b91-*********-b25889b0e685", "businessPhones": [], "displayName": "Nowe Testowe", "givenName": null, "jobTitle": null, "mail": null, "mobilePhone": null, "officeLocation": null, "preferredLanguage": null, "surname": null, "userPrincipalName": "NoweTestowe#*********.onmicrosoft.com" }
How should I call the /users API to get user custom attribute value?
Yes, the beta endpoint returns all attributes of the user by default, and the v1.0 endpoint hides user attributes that are not commonly used by default.
I am using Graph API to create user and this is how i am creating a user
let payload = {
"displayName": value.data.displayName,
"identities": [
{
"signInType": "userName",
"issuer": "{tenantName}.onmicrosoft.com",
"issuerAssignedId": value.data.memberNumber
},
{
"signInType": "emailAddress",
"issuer": "{tenantName}.onmicrosoft.com",
"issuerAssignedId": value.data.email
}
],
"passwordProfile": {
"forceChangePasswordNextSignIn": true,
"password": value.password
},
"passwordPolicies": "DisableStrongPassword"
}
When i create user using "forceChangePasswordNextSignIn": true in passwordProfile, i always get an error shown in image below i.e. password has expired even on first login.
It should be a limitation currently.
You can refer to a previous similar post to learn about the sign-in policy. The sign-in policy validates a user credential by sending a grant_type=password request to the Azure AD B2C directory. If the forceChangePasswordNextLogin property is set to true, this request returns an "user_password_expired" error. The sign-in policy handles this error by displaying the "Invalid email address/user or password" error.
So as Chris Padgett suggested, you can create a custom attribute (e.g. ForceResetPasswordNextLogin), set this to true when you create the local account, and then issue this as an application claim from the sign-in policy to your B2C application.
After sign-in, if it is set to true, then your B2C application can initiate the password reset policy. After password reset, then your B2C application can set it to false.
Custom policy is also a choice.
I've picked up something that someone else has set up.
We have an API Management instance, siting behind an Application Gateway, which has a policy on an API:
<inbound>
<choose>
<when condition="#(context.Request.Certificate == null)">
<return-response>
<set-status code="403" reason="Client certificate required..d1PD" />
</return-response>
</when>
</choose>
<choose>
<when condition="#(!context.Request.Certificate.Verify())">
<return-response>
<set-status code="403" reason="Client certificate cannot be verified..d2PD " />
</return-response>
</when>
</choose>
<choose>
<when condition="#(!context.Deployment.Certificates.Any(c => c.Value.Thumbprint == context.Request.Certificate.Thumbprint))">
<return-response>
<set-status code="403" reason="Client certificate is untrusted or invalid..d3PD" />
</return-response>
</when>
</choose>
<base />
</inbound>
In Postman I am passing a cert and key. The Postman Console shows
Client Certificate:
keyPath:"C:\selfsigned\internalscm.X.com.key"
pemPath:"C:\selfsigned\internalscm.X.com.crt"
pfxPath:""
I'm passing Ocp-Apim-Trace in the header of the request so am getting a trace back which contains:
traceEntries {2}
inbound [10]
..
6 {4}
source : authentication-certificate
timestamp : 2019-08-06T08:55:31.3435485Z
elapsed : 00:00:00.0006857
data {2}
message : Certificate was attached to request per configuration.
certificate {...}
7 {4}
source : choose
timestamp : 2019-08-06T08:55:31.3435485Z
elapsed : 00:00:00.0007011
data {3}
message : Expression was successfully evaluated.
expression : context.Request.Certificate == null
value : true
UPDATE:
The authentication-certificate assessment is of the backend's certificate and is nothing to do with the client certificate (.key and .crt) that Postman claims to be including in the request (the same result is returned if I pass a pfx and password instead of .key and .crt).
When I hit the API that the Gateway is protecting, I can see in the trace that it is processing the client certificate (and returning a 200):
{
"source": "client-certificate-handler",
"timestamp": "2019-08-09T15:47:46.3825928Z",
"elapsed": "00:00:00.0005974",
"data": "Requesting client certificate because next handler requires access to it."
},
{
"source": "client-certificate-handler",
"timestamp": "2019-08-09T15:47:46.6950495Z",
"elapsed": "00:00:00.3225172",
"data": "Client certificate thumbprint '6C03F4E7999999999999999999999999' received."
},
{
"source": "choose",
"timestamp": "2019-08-09T15:47:46.6950495Z",
"elapsed": "00:00:00.3225288",
"data": {
"message": "Expression was successfully evaluated.",
"expression": "context.Request.Certificate == null",
"value": false
}
},
{
"source": "choose",
"timestamp": "2019-08-09T15:47:46.9606395Z",
"elapsed": "00:00:00.5849700",
"data": {
"message": "Expression was successfully evaluated.",
"expression": "!context.Request.Certificate.Verify()",
"value": false
}
},
{
"source": "choose",
"timestamp": "2019-08-09T15:47:46.9606395Z",
"elapsed": "00:00:00.5850060",
"data": {
"message": "Expression was successfully evaluated.",
"expression": "!context.Deployment.Certificates.Any(c => c.Value.Thumbprint == context.Request.Certificate.Thumbprint)",
"value": false
}
}
So it looks like AppGateway is dropping the client certificate.
The trace doesn't give me enough to start deducing why the client certificate (assuming that Postman is transmitting it to the Gateway as it does the API) is being dropped. Where should I start?
For ref, when I remove the policy the request is processed as expected.
I'm not sure if you can make AppGateway pass the certificate - you need to check their docs. The reason I'm skeptical is bacuse the whole idea of AppGateway is to look into traffic and provide protection by doing that. And the only way to d othat is to terminate SSL connection at AppGateway level. See here for more information: https://learn.microsoft.com/en-us/azure/application-gateway/ssl-overview there are two modes for AppGateway: SSL temination when AppGateway makes HTTP (not HTTPS) calls to backend, and SSL end-to-end when AppGateway uses own SSL certificate to connect to backend.
Some client certificate information can be passed to backend via server variables: https://learn.microsoft.com/en-us/azure/application-gateway/rewrite-http-headers-url#mutual-authentication-server-variables
I am trying to create a user using azure graph API. For this, I have got the token response as below:
Once I have the token, I have added this as bearer token in Authorization for the below url:
https://graph.microsoft.com/v1.0/users
and posting the below json data:
{
"accountEnabled": true,
"displayName": "Andrew",
"mailNickname": "SanAndrew",
"userPrincipalName": "andrew.san204#gmail.com",
"passwordProfile" : {
"forceChangePasswordNextSignIn": true,
"password": "password"
}
}
But getting below error:
{
" error": {
"code": "Authorization_RequestDenied",
"message": "Insufficient privileges to complete the operation.",
"innerError": {
"request-id": "c06079d2-ff6b-4e5b-b34d-704f16bc312f",
"date": "2019-06-03T11:20:20"
}
}
}
although I have all the permissions as shown below:
Can anyone please suggest what I am doing wrong here.
One possibility for the error is that you are trying to create a user with gmail.com domain. The domain needs to be one of the verified domains in your AAD tenant.
If you want to add a gmail.com user anyway, you need to send an invitation through the invitations endpoint: https://learn.microsoft.com/en-us/graph/api/invitation-post?view=graph-rest-1.0&tabs=cs
I am attempting to validate that a passed in JWT token has the scopes "labresults.read" and "user_impersonation". I did the following policy snippet
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Invalid JWT Token" require-signed-tokens="true">
<openid-config url="(snip)" />
<audiences>
<audience>(snip)</audience>
</audiences>
<required-claims>
<claim name="scp" match="all">
<value>labresults.read</value>
<value>user_impersonation</value>
</claim>
</required-claims>
</validate-jwt>
I pass in a token that looks like
{
"iss": "(snip)",
"exp": 1522334650,
"nbf": 1522331050,
"aud": "(snip)",
"sub": "(snip)",
"email": "(snip)",
"name": "Scott Chamberlain",
"scp": "labresults.read user_impersonation",
"azp": "(snip)",
"ver": "1.0",
"iat": 1522331050
}
When I do a "Try It" from the developer portal looking at the tracing the on-error reports
validate-jwt (648 ms){
"message": "JWT Validation Failed: Claim value mismatch: scp=labresults.read.."
}
Am I forced to use a single claim of
<claim name="scp">
<value>labresults.read user_impersonation</value>
</claim>
I really would not like to as I do not want to force on the consumers of this api that those two scopes will be the only things passed in and in that specific order.
What do I need to do to validate the scope the propper way?
Got an answer from someone at Microsoft on the MSDN Forums.
I would suggest you to specify “separator” attribute in policy
statement to validate the JWT token and see if it helps. For more
information, you might refer this document:
https://learn.microsoft.com/en-us/azure/api-management/api-management-access-restriction-policies#ValidateJWT.
Swikruti BoseMicrosoft-MSFT (MSFT CSG)
I totally overlooked the seperator attribute when i looked at the documentation the first time.