Fasitify + CORS = no authorization key in header
gjovanov opened this issue · comments
Describe the bug
In this fastify setup:
const corOptions = {
origin: '*',
methods: ['GET', 'PUT', 'POST', 'DELETE', 'OPTIONS'],
allowedHeaders: ['content-type', 'authorization', 'access-control-allow-origin', 'access-control-allow-credentials'],
credentials: true
}
await fastify
.register(require('middie'))
.register(require('fastify-cookie'))
.register(require('fastify-cors'), corsOptions)
await fastify
.use('/api/auth/login', require('cors')(corsOptions))
await fastify
.use('/api/auth/login', sso.auth({ useGroups: false, useOwner: false, useActiveDirectory: false }))
with this route:
const routes = {
authenticate: true,
method: 'GET',
url: '/api/auth/login',
schema: {
response: {
200: authSchema.login.response[200],
409: errorSchema.response[409],
500: errorSchema.response[500]
}
},
handler: authController.login
}
authController being an instance of:
class AuthController {
login (request, reply) {
reply.send(request.raw.sso ? request.raw.sso.user : { error: 'Unknown' })
}
}
await fastify
.decorate('authenticate', async (request, reply, done) => {
const handler = sso.auth({ useGroups: false, useOwner: false, useActiveDirectory: false })
await handler(request, reply, done)
})
.after(() => {
routes.forEach((route) => {
fastify.route(route)
})
})
fastify being started on localhost:3001.
When Accessed without cors:
http://localhost:3001/api/auth/login
Everything works fine.
However, when accessed from another app listening on port 3000, these trace logs can be found:
node-expose-sspi:schManager initCookie +0ms
node-expose-sspi:schManager cookie not found, so generating one +11ms
node-expose-sspi:auth cookieToken: NEGOTIATE_8946007805 +0ms
node-expose-sspi:auth no authorization key in header +0ms
To Reproduce
Fastify + CORs setup as described from above, doesn't work
Trace
node-expose-sspi:schManager initCookie +0ms
node-expose-sspi:schManager cookie not found, so generating one +11ms
node-expose-sspi:auth cookieToken: NEGOTIATE_8946007805 +0ms
node-expose-sspi:auth no authorization key in header +0ms
Expected behavior
Same as without CORS
Screenshots
N/A
Environment version:
- OS: Windows 10 Pro version
- Browser version: Chrome 86.0.4240
- Node version and architecture: v15.1.0
Please indicates also:
- Are you on a Windows domain ? yes
- Can you reach the domain controller ? yes
- Do your session have admin privileges ? no
- Which authentication protocol ? NTLM
- Active Directory, or local window policies that could impact the authentication. N/A
Additional context
N/A
I am going to investigate.
Tnx @jlguenego.
The problem is that you register middie before the fastify-cors. And you should not be using cors and fastify-cors at same time.
The hook registered by middie (sso.auth, etc.) should be registered AFTER the cors. Because the browser always needs the cors headers, even if the answer is 401.
If ok with my answer, please close the defect.
I have investigated a little bit more.
TL;DR : look at the working tested solution at https://github.com/jlguenego/angular-sso-example
First you cannot use origin with '*' when you want to do CORS with XHR, using also cookies. You need to send the origin with the domain of the client (stored in the Origin
request header).
see for instance https://stackoverflow.com/questions/46288437/set-cookies-for-cross-origin-requests
Also CORS credentials must be set (and you have done it)
The snippet:
fastify.use(
cors((req, callback) => {
const options = {
credentials: true,
origin: req.headers.origin,
};
callback(null, options);
}) as NextHandleFunction
);
But... also on client side, you need to set "withCredentials" EVERYWHERE (again for the cookies).
Example with Angular:
const { sso } = await this.http
.get<ConnectWithSSOResponse>(domainUrl + '/ws/is-connected', {
withCredentials: true,
})
.toPromise();
There is also a problem with Fastify : this framework retains the header setting. So it is not because we do reply.header('thing') = 'something'
that the header will be really sent, specially if another middleware decides to send back the HTTP response (like node-expose-sspi).
So please do not use fastify-cors
but cors
and middie
.
Please look at this code.
https://github.com/jlguenego/angular-sso-example/blob/master/back-fastify/src/server.ts
I think now this defect can be closed 😄
Thanks for clarifying @jlguenego . I will give a try today and let you know if it works and close it accordingly.
Hey @jlguenego ,
I tried the above proposal and it worked.
Here is the end reduced changes I've made:
- added the { withCredentials: true } to all ajax requests from the CORs domain
- Replaced the order of
middie
andfastify-cors
plugin registration as follows:
await fastify
.register(require('fastify-cookie'))
.register(require('fastify-cors'), corsOptions)
.register(require('middie'))
- Removed the usage of
cors
plugin - Replaced the asterisk
*
with the exact CORS originhttp://localhost:3000
const options = {
origin: 'http://localhost:3000', // instead of '*'
methods: ['GET', 'PUT', 'POST', 'DELETE', 'OPTIONS'],
credentials: true
}
Pls note that after this, the cors
is not needed anymore. fastify-cors
does the CORs handling just fine, even though sso.auth({ ... })
is registered via middie
.
Anyways, I'm closing as after the steps from above it works just fine.
Many thanks for your help @jlguenego and awesome work with this repo!
I have to retest with fastify-cors, may be I did not see something... but anyway, you have your solution and that is what matters.