How to call AAD graph from Ibiza extension - azure-active-directory

I'm trying to call AAD Graph, but I'm getting an error.
Here is how I'm trying to make a call:
MsPortalFx.Base.Net.ajax({
uri: `https://graph.windows.net/<id>/servicePrincipals/<id>?api-version=1.6-internal`,
type: "GET",
dataType: "json",
cache: false,
traditional: true,
contentType: "application/json",
setAuthorizationHeader: true,
})
I can see that Bearer token is supplied in the Authorization header, but here is the error I'm getting:
HTTP/1.1 401 Unauthorized
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json;odata=minimalmetadata;streaming=true;charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.5
ocp-aad-diagnostics-server-name: <name>
request-id: <request-id>
client-request-id: <client-request-id>
x-ms-dirapi-data-contract-version: 1.6-internal
DataServiceVersion: 3.0;
Strict-Transport-Security: max-age=31536000; includeSubDomains
Access-Control-Allow-Origin: *
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Duration: 519919
X-Powered-By: ASP.NET
Date: Mon, 23 Nov 2020 22:49:42 GMT
Content-Length: 212
{"odata.error":{"code":"Authentication_MissingOrMalformed","message":{"lang":"en","value":"Access Token missing or malformed."},"requestId":"<id>","date":"2020-11-23T22:49:42"}}
Please let me know if I need to provide any additional information.

As Sruthi said, the error information Authentication_MissingOrMalformed it means that the access resource does not match the AUD of access token.
You need to get the access token following this:
POST https://login.microsoftonline.com/<Your-Tenant-ID>/oauth2/token
// request body:
grant_type=client_credentials
client_id=<Your Portal Application ID>
client_secret=<Your client secret>
resource=https://graph.windows.net // used to call AAD Graph API
Or with the v2.0 endpoint:
POST https://login.microsoftonline.com/<Your-Tenant-ID>/oauth2/v2.0/token
// request body:
grant_type=client_credentials
client_id=<Your Portal Application ID>
client_secret=<Your client secret>
scope=https://graph.windows.net/.default // used to call AAD Graph API

Related

Firefox react fetch does not include cookie

I'm trying to implement Discord OAuth2 in my React app with Spring Boot REST API. How it should work:
In React app (localhost on port1) I manually set window.location.href to my backend application oauth2 authorization endpoint. (localhost port 2 /api/oauth2/authorization/discord)
async function onSubmit() {
try {
window.location.href =
"http test.local port2/api/oauth2/authorization/discord";
} catch (error) {
console.log(error)
}
}
GET request is sent to test.local, which responds with HTTP 302 to https discordapp /oauth2/authorize?... along with Set-Cookie: ... Http header (this is important)
Response headers:
HTTP/1.1 302
Vary: Origin, Access-Control-Request-Method, Access-Control-Request-Headers
Set-Cookie: oauth2_auth_request=long_cookie_value; Path=/; Max-Age=180; Expires=Sun, 29 Jan 2023 14:13:47 GMT; SameSite=None
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Location: https discordapp com/oauth2/authorize?response_type=code&client_id=...&redirect_uri=http://localhost:5173/login
Content-Length: 0
Date: Sun, 29 Jan 2023 14:10:47 GMT
Keep-Alive: timeout=60
Connection: keep-alive
User sees the discord login page
Now if I open http test local port2 in the browser I can see that the cookie was set correctly
cookie screenshot
User logs in to discord, discord redirects to http localhost port1/login?code=...&state=...
In react app I fetch GET http test.local port2/api/login/oauth2/code/discord?code=...&state=...
Code:
fetch(
`http test local port2/api/login/oauth2/code/discord?code=${searchParams.get("code")}&state=${searchParams.get("state")}`,
{
credentials: "include",
}
)
.then((res) => res.json())
.then((data) => console.log(data))
Request headers:
GET /api/login/oauth2/code/discord?code=...&state=... HTTP/1.1
Host: sss.test:8081
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:108.0) Gecko/20100101 Firefox/108.0
Accept: */*
Accept-Language: pl,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://localhost:5173/
Origin: http://localhost:5173
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Expected result:
The request is sent together with the cookie set in step 2. (required to confirm that both requests in authorization flow are sent from the same origin)
Actual result:
Cookie is not set.
Observations:
If I open http test.local port2/api/login/oauth2/code/discord?code=...&state=... in the browser, the cookie is set
Request headers:
GET /api/login/oauth2/code/discord?code=...&stat=... HTTP/1.1
Host: sss.test:8081
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:108.0) Gecko/20100101 Firefox/108.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: pl,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: keep-alive
Cookie: oauth2_auth_request=long_cookie_value
Upgrade-Insecure-Requests: 1
Pragma: no-cache
Cache-Control: no-cache
Works on the same domain for FE and BE
Notes:
Using http for development purpose only. I will switch to https and BE available on external server soon, but in the meantime I want to make it work for http and local. BE and FE will be hosted on the same server with proxy for /api requests that will direct api requests to BE port. But I will need cross-origin cookie for local FE development with external BE API anyway.
Chrome blocks non-secure, sameSite: none cookies, so it will not work there. Firefox allows it (for now), so I'm testing on firefox.
Spring Security configuration:
...
.cors()
.configurationSource(request -> {
final CorsConfiguration config = new CorsConfiguration().applyPermitDefaultValues();
config.setAllowCredentials(true);
config.setAllowedOrigins(List.of("http://localhost:5173"));
return config;
})
...
FE should receive JWT token in the response of this request for further authentication.
Sorry for weird URLs sometimes but apparently this question is SPAM -.-

Azure AD Authentication Setup with Spring Boot Web App - AADSTS50011

I followed the official configure-spring-boot-starter-java-app-with-azure-active-directory tutorial but I can't seem to get it to work. I've confirmed the redirect url is exactly as written with the same security controller.
Here is my request headers:
HTTP/1.1 302
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Location: https://login.microsoftonline.com/07d020b6-d78f-40cb-b6c7-98eab8c29a94/oauth2/v2.0/authorize?response_type=code&client_id=8ff48ac1-1ddf-479d-ac5a-5db407c70c50&scope=openid%20profile%20https://graph.microsoft.com/User.Read%20https://graph.microsoft.com/Directory.Read.All&state=aUtWlcsG6Oc6NnYxA8z7E339CVlfodi7kBs5HiNIx8M%3D&redirect_uri=http://localhost:8080/login/oauth2/code/&nonce=xhjQJa-IVP_9kXFKsDX_oNrLprt4HnqDzUgyYqrjyBA
Content-Length: 0
Date: Fri, 16 Apr 2021 17:31:11 GMT
Keep-Alive: timeout=60
Connection: keep-alive
Note that the location contains my RedirectURI: http://localhost:8080/login/oauth2/code/azure
I've also reviewed other issues, and it feels close but, as mentioned, the tutorial what was provided -- so it should work.
Please let me know if you need any other information.
Request Id: a6cb6d0d-9a3a-4bd3-a2b7-c16c053c7b01
Correlation Id: 9f74d074-df2c-4a73-8c79-31aa7442a427
Timestamp: 2021-04-16T16:54:36Z
Message: AADSTS50011: The reply URL specified in the request does not match the reply URLs configured for the application: '8ff48ac1-1ddf-479d-ac5a-5db407c70c50'.
You actually sent
http://localhost:8080/login/oauth2/code/
as redirect_uri in logon request. But app was likely defined with following redirect_uri.
http://localhost:8080/login/oauth2/code/azure
They don't match. Hence the error.

401 Unauthorized on unprotected resource

My Setup
I have a server with a REST API that runs on Symfony with API Platform. The GET requests for my resources do not require authorization, however the other operations do. Authorization is handled with a JWT Bearer token.
The client uses React-admin with API Platform Admin. I added this code to send the JWT token along with the operations:
// dataProvider.js
import React from "react";
import { hydraDataProvider, fetchHydra as baseFetchHydra } from "#api-platform/admin";
export default entrypoint => {
const fetchHeaders = { Authorization: `Bearer ${localStorage.getItem("token")}` };
const fetchHydra = (url, options = {}) => baseFetchHydra(url, {
...options,
headers: new Headers(fetchHeaders),
});
return hydraDataProvider(entrypoint, fetchHydra);
};
The Problem
When I log in to my admin interface now, I get a 401 Unauthorized response, because the server did not expect a token for a GET request.
Request Headers:
Host: localhost:8000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:73.0) Gecko/20100101 Firefox/73.0
Accept: application/ld+json
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: http://localhost:3000/
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE1ODQ2NzcwMTYsImV4cCI6MTU4NDY4MDYxNiwicm9sZXMiOlsiUk9MRV9BRE1JTiJdLCJ1c2VybmFtZSI6IlNvbWVib2R5In0.O_StagfEJy5VQS-5s-DjuwzOlUgrl3MTmxPfZUU0J1go06tKOpLjiBrEIJpjo5AK67w93SfsUaIBop8apoacHQ
Content-Type: application/ld+json
Origin: http://localhost:3000
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
TE: Trailers
Response Headers:
HTTP/2 401 Unauthorized
access-control-allow-origin: http://localhost:3000
access-control-expose-headers: link
cache-control: no-cache, private
content-type: application/json
date: Fri, 20 Mar 2020 04:40:39 GMT
link: <https://localhost:8000/api/docs.jsonld>; rel="http://www.w3.org/ns/hydra/core#apiDocumentation"
www-authenticate: Bearer
x-debug-token: 720652
x-debug-token-link: https://localhost:8000/_profiler/720652
x-powered-by: PHP/7.4.1
x-robots-tag: noindex
content-length: 282
X-Firefox-Spdy: h2
When I manually remove the Authorization line from the request headers in the browser and retry it, it works.
My Questions:
Is this even expected behavior?
Should the client always send the token?
If the token should always be sent, how do I tell API Platform to accept it even if it isn't needed?
If the token should only be sent when it's required, how do I let the hydraDataProvider know?
After many many hours of trying different solutions, I finally fixed this problem.
Answers to my Questions
No, sending a valid token should not result in a 401 response.
The token can be sent on every request.
My Solution
The problem was that the JWT authentication was configured incorrectly on my server. None of the guides I followed actually covered the following case:
I have the user email as identifier, not the user name.
So what ended up happening is that the token contained the encoded user name, which is not a unique identifier in my case. To tell JWT to use the email instead, I had to set the user_identity_field to email.
// config/packages/lexik_jwt_authentication.yaml
lexik_jwt_authentication:
secret_key: '%env(resolve:JWT_SECRET_KEY)%'
public_key: '%env(resolve:JWT_PUBLIC_KEY)%'
pass_phrase: '%env(JWT_PASSPHRASE)%'
user_identity_field: email

Random occurrence with preflight response missing allow headers

I've got quite random occurrence with this common error:
OPTIONS https://api.cloudfunctions.net/api/graphql 404
Access to fetch at 'https://api.cloudfunctions.net/api/graphql' from origin 'https://website.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
What I have is a graphql endpoint with apollo server deployed on Google Cloud Functions and a react client. At some points the client will throw the error on browser but if I try refresh or send the request again 2 or 3 times later it will work.
The preflight request headers being sent:
:authority: api.cloudfunctions.net
:method: OPTIONS
:path: /api/graphql
:scheme: https
accept: */*
accept-encoding: gzip, deflate, br
accept-language: en-US,en;q=0.9,id;q=0.8,ms;q=0.7
access-control-request-headers: content-type
access-control-request-method: POST
origin: https://website.com
referer: https://website.com/
sec-fetch-mode: cors
sec-fetch-site: cross-site
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36
Expected response
access-control-allow-credentials: true
access-control-allow-headers: content-type
access-control-allow-methods: POST,OPTIONS
access-control-allow-origin: https://website.com
alt-svc: quic=":443"; ma=2592000; v="46,43",h3-Q050=":443"; ma=2592000,h3-Q049=":443"; ma=2592000,h3-Q048=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000
content-length: 0
content-type: text/html
date: Wed, 08 Jan 2020 00:38:16 GMT
function-execution-id: 84et92k6mvd9
server: Google Frontend
status: 200
vary: Origin, Access-Control-Request-Headers
x-cloud-trace-context: 95d25375171148a66bc629cc41a79d05
x-powered-by: Express
Random failed response
alt-svc: quic=":443"; ma=2592000; v="46,43",h3-Q050=":443"; ma=2592000,h3-Q049=":443"; ma=2592000,h3-Q048=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000
cache-control: private
content-encoding: gzip
content-length: 140
content-security-policy: default-src 'none'
content-type: text/html; charset=utf-8
date: Wed, 08 Jan 2020 00:38:05 GMT
function-execution-id: 84etgky3im1k
server: Google Frontend
status: 404
x-cloud-trace-context: 77040d2c72304cad0d645480b6814f7f;o=1
x-content-type-options: nosniff
x-powered-by: Express
Looking at the failed response above kinda make sense that it's missing the access-control-allow-* headers compared to success one, but again I am not sure how that happened.
Here's my cors config:
const corsConfig = {
origin: ['https://website.com', 'http://localhost:3000'],
methods: ['POST', 'OPTIONS'],
credentials: true,
optionsSuccessStatus: 200,
}
const app = express()
app
.use(cors(corsConfig))
.use(...)
...
apolloServer.applyMiddleware({ app, cors: corsConfig })
Based on few suggestions around I have tried different setup but still sometimes the error happens:
set cors: false in applyMiddleware
remove cors
repeat cors as shown above
add app.options('*', cors()) as per doc says
All and all it happens like 1 in 10, sometimes on first request after the user open the site the other times after the user browsing around the site for a while.
I think there might be other middleware that messes up your cors settings.
You can try use a different path for your graphql endpoint, and apply cors only to that path.
apolloServer.applyMiddleware({ app, path: '/graphql', cors: corsConfig });
Alternatively, you can try the express cors middleware and disable the cors from apollo server
I used the apollo-server-cloud-functions package to solve this problem. Just follow the instructions here (https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-cloud-functions) but instead of using exports.handler = server.createHandler() swap it out for your own function, like this:
exports.api = functions.https.onRequest(
server.createHandler({
cors: {
origin: true,
credentials: true
}
})
);
That solved it for me!

Unable to retrieve agentUserId trough OAuth 2.0 Playground

When following the instructions on the site below, I get stuck in Step 2.
https://developers.google.com/assistant/smarthome/tools/smart-home-test-suite
After clicking on "Exchange authorization code for tokens" I get no codes, but a 401 Unauthorized error.
No idea what is going wrong during this test, because the action itself works fine.
This is the output from the OAuth playground:
Host: oauth.teletask.be
Content-length: 169
content-type: application/x-www-form-urlencoded
user-agent: google-oauth-playground
code=XIspHj&redirect_uri=https%3A%2F%2Fdevelopers.google.com%2Foauthplayground&client_id=***********&client_secret=************&scope=&grant_type=authorization_code
HTTP/1.1 401 Unauthorized
X-xss-protection: 1; mode=block
X-content-type-options: nosniff
Transfer-encoding: chunked
Expires: 0
Www-authenticate: Basic realm="oauth2/client"
Server: nginx/1.14.1
Connection: keep-alive
Pragma: no-cache
Cache-control: no-cache, no-store, max-age=0, must-revalidate
Date: Thu, 21 Nov 2019 14:59:59 GMT
Strict-transport-security: max-age=31536000 ; includeSubDomains
Content-type: application/json;charset=UTF-8
X-frame-options: DENY
{
"status": 401,
"timestamp": "2019-11-21T14:59:59.599+0000",
"message": "Unauthorized",
"path": "/auth/oauth/token",
"error": "Unauthorized"
}
That 401 error is being generated by your OAuth server (oauth.teletask.be), specifically the endpoint you entered as the Token endpoint, when it receives the authorization code grant from Google. You should check your server logs to identify more details about why it rejected the request.
Common issues here include rejecting the redirect_uri or client_id because they don't match expected values. It may also be possible that your Authorization code has too short a lifetime and the code expires before you hit the button to exchange it for a token.

Resources