Giter Site home page Giter Site logo

Comments (14)

deepakprabhakara avatar deepakprabhakara commented on June 1, 2024

Thanks for reporting this @SacredMesa, we'll look into the issue.

from jackson.

niwsa avatar niwsa commented on June 1, 2024

@SacredMesa The authorize call should return an authorize_form value that is basically the POST binding response. You can send that back as the response inside the authorize endpoint.
See our service implementation here:

res.send(authorize_form);

from jackson.

SacredMesa avatar SacredMesa commented on June 1, 2024

Thanks for the quick reply @deepakprabhakara and @niwsa. I've tried the implementation you linked, but I am still facing the same error.

From my understanding, the issue comes when Jackson sends a POST request to Azure's login url. The SAMLRequest is sent in the query params, however Azure is expecting this to be in the body, and so returns a 400 and the error screen.

Screenshots of the issue:

POST to Azure login url returns 400:
image001

Payload of POST request to Azure login url (SAMLRequest in query params and absent in body):
image002

from jackson.

niwsa avatar niwsa commented on June 1, 2024

@SacredMesa Jackson would use the redirect binding if the IdP metadata has one, else it uses the POST binding. Can you check your ADFS metadata for the supported bindings?

from jackson.

SacredMesa avatar SacredMesa commented on June 1, 2024

@niwsa It appears my metadata shows both bindings:

image

from jackson.

niwsa avatar niwsa commented on June 1, 2024

If the metadata has a redirect binding then the IdP should support the same. But looks like that is not the case here.

@SacredMesa From the screenshot you shared earlier, it looks like the app is making a POST instead of a GET. Jackson simply returns the redirect_url or the post binding in this case (does not make a request).

from jackson.

deepakprabhakara avatar deepakprabhakara commented on June 1, 2024

@SacredMesa Would you mind posting the code snippet, we can take a look to see if we can spot anything.

from jackson.

SacredMesa avatar SacredMesa commented on June 1, 2024

@niwsa @deepakprabhakara Really appreciate the help so far! I managed to get past the error by removing the redirect binding in the metadata xml file. A bit hacky, but it works. It's properly adding the authorize_form now. Though, not sure why it was making a POST request instead of a GET for the redirect binding. When I logged the incoming req.method in authorize.ts, it was a GET request.

The issue I'm facing now is that I'm getting a timeout error after getting the redirect_url from ACS:

redirect_url in ACS https://<app-base-url>/api/auth/callback/jackson-saml?code=9ac638b03b03a87a61cc35bd5bc41d5e286e8a51&state=lMq9j_GpbQENA-2ZNPadcfUo7eKSrbw4cd4AGCkNFh8
--
[next-auth][error][OAUTH_CALLBACK_ERROR]
https://next-auth.js.org/errors#oauth_callback_error outgoing request timed out after 3500ms {
error: RPError: outgoing request timed out after 3500ms
at /app/node_modules/next-auth/node_modules/openid-client/lib/helpers/request.js:137:13
at async Client.grant (/app/node_modules/next-auth/node_modules/openid-client/lib/client.js:1316:22)
at async Client.oauthCallback (/app/node_modules/next-auth/node_modules/openid-client/lib/client.js:603:24)
at async oAuthCallback (/app/node_modules/next-auth/core/lib/oauth/callback.js:111:16)
at async Object.callback (/app/node_modules/next-auth/core/routes/callback.js:52:11)
at async AuthHandler (/app/node_modules/next-auth/core/index.js:208:28)
at async NextAuthApiHandler (/app/node_modules/next-auth/next/index.js:22:19)
at async NextAuth._args$ (/app/node_modules/next-auth/next/index.js:106:14)
at async Object.apiResolver (/app/node_modules/next/dist/server/api-utils/node.js:366:9)
at async NextNodeServer.runApi (/app/node_modules/next/dist/server/next-server.js:481:9) {
name: 'OAuthCallbackError',
code: undefined
},
providerId: 'jackson-saml',
message: 'outgoing request timed out after 3500ms'
}

Although, haven't debugged far enough to tell if it's an error with jackson, next-auth, or something else entirely. Would still be really grateful if you could take a look at the endpoints and config for jackson though if there might be something wrong/missing.

I'll share the jackson.ts, authorize.ts, acs.ts, token.ts, userinfo.ts and [...nextauth].ts codes here:

lib/jackson.ts

import type { JacksonOption, SAMLJackson } from '@boxyhq/saml-jackson';
import jackson from '@boxyhq/saml-jackson';

const samlAudience = process.env.AZURE_SAML_APPID;
const samlPath = '/api/auth/saml/acs';

const preLoadedConnection = 'lib/connections';

const opts: JacksonOption = {
  externalUrl: `${process.env.NEXTAUTH_URL}`,
  samlAudience,
  samlPath,
  preLoadedConnection,
  db: { engine: 'mem' }
};

const g = global as any;

export default async function init() {
  if (!g.jacksonInstance) {
    g.jacksonInstance = await jackson(opts);
  }

  return g.jacksonInstance as SAMLJackson;
}

pages/api/auth/saml/authorize.ts

import { NextApiRequest, NextApiResponse } from 'next';

import jackson from '../../../../lib/jackson';
import { OAuthReq } from '@boxyhq/saml-jackson';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  try {
    if (req.method !== 'GET' && req.method !== 'POST') {
      throw { message: 'Method not allowed', statusCode: 405 };
    }

    const { oauthController } = await jackson();
    const requestParams = req.method === 'GET' ? req.query : req.body;

    const { redirect_url, authorize_form } = await oauthController.authorize(
      requestParams as unknown as OAuthReq
    );

    if (redirect_url) {
      res.redirect(302, redirect_url);
    } else {
      res.setHeader('Content-Type', 'text/html; charset=utf-8');
      res.send(authorize_form);
    }
  } catch (err: any) {
    console.error('authorize error:', err);
    res.redirect('/');
  }
}

pages/api/auth/saml/acs.ts

import type { NextApiRequest, NextApiResponse } from 'next';

import jackson from '../../../../lib/jackson';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const { oauthController } = await jackson();
  const { RelayState, SAMLResponse } = req.body;

  const { redirect_url } = await oauthController.samlResponse({
    RelayState,
    SAMLResponse
  });

  console.log('redirect_url in ACS', redirect_url);

  return res.redirect(302, redirect_url as string);
}

pages/api/auth/saml/token.ts

import type { NextApiRequest, NextApiResponse } from 'next';

import jackson from '../../../../lib/jackson';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const { oauthController } = await jackson();
  const response = await oauthController.token(req.body);

  return res.json(response);
}

pages/api/auth/saml/userinfo.ts

import type { NextApiRequest, NextApiResponse } from 'next';

import jackson from '../../../../lib/jackson';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const { oauthController } = await jackson();

  const authHeader = req.headers['authorization'];

  if (!authHeader) {
    throw new Error('Unauthorized');
  }

  const token = authHeader.split(' ')[1];
  const user = await oauthController.userInfo(token);

  return res.json(user);
}

provider in pages/api/auth/[...nextauth].ts

    {
      id: 'jackson-saml',
      name: 'Azure-SAML',
      type: 'oauth',
      checks: ['pkce', 'state'],
      authorization: {
        url: `${process.env.NEXTAUTH_URL}/api/auth/saml/authorize`,
        params: {
          scope: '',
          response_type: 'code',
          provider: 'saml'
        }
      },
      token: {
        url: `${process.env.NEXTAUTH_URL}/api/auth/saml/token`,
        params: { grant_type: 'authorization_code' }
      },
      userinfo: `${process.env.NEXTAUTH_URL}/api/auth/saml/userinfo`,
      profile: (profile) => {
        return {
          id: profile.id || '',
          firstName: profile.firstName || '',
          lastName: profile.lastName || '',
          email: profile.email || '',
          name: `${profile.firstName || ''} ${profile.lastName || ''}`.trim(),
          email_verified: true
        };
      },
      options: {
        clientId: `tenant=${process.env.AZURE_SAML_TENANT}&product=${process.env.AZURE_SAML_PRODUCT}`,
        clientSecret: 'dummy'
      }
    },

from jackson.

niwsa avatar niwsa commented on June 1, 2024

Could you try wrapping the token route with try catch ... and check for any errors in the token handler ? It could be that the token handler threw an error which is not handled, hence the timeout.

import type { NextApiRequest, NextApiResponse } from 'next';

import jackson from '../../../../lib/jackson';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
 try { 
  const { oauthController } = await jackson();
  const response = await oauthController.token(req.body);

  res.json(response);
  } catch (err) {
  console.error('token error:', err);
  }
}

Also noticed that you are using the mem db engine. Are you running the app locally or is it deployed somewhere? Do note that the mem db won't work in a serverless environment.

from jackson.

SacredMesa avatar SacredMesa commented on June 1, 2024

@niwsa I've wrapped the token and acs routes with try catch, but seems there's no error being thrown there. From what it seems, is that it hangs as it's trying to redirect. I can't tell if it's actually making the redirect request tbh, though the redirect url is there.

The app is deployed in AWS ECS. If I'm not mistaken the db is to store connection configs? I've set it up to preload the connection from a .js and .xml file though, would this suffice?

from jackson.

niwsa avatar niwsa commented on June 1, 2024

If I'm not mistaken the db is to store connection configs?

We also use it to conduct the Auth flow, as the OAuth 2.0 entities like code and token need to be persisted for some time.

Could you try increasing the HTTP timeout option inside the NextAuth Provider?

 httpOptions: {
        timeout: 30000,
},

from jackson.

SacredMesa avatar SacredMesa commented on June 1, 2024

@niwsa I've increased the timeout option, but doesn't seem to make a difference. We've had to drop this for awhile to handle some other tickets, but back on it now.

The last loggable event is still from the acs endpoint. We're getting the redirect url (https://<app-base-url>/api/auth/callback/jackson-saml?code=9ac638b03b03a87a61cc35bd5bc41d5e286e8a51&state=lMq9j_GpbQENA-2ZNPadcfUo7eKSrbw4cd4AGCkNFh8), but hanging after that.

Do you have any other suggestions on what we could try?

from jackson.

niwsa avatar niwsa commented on June 1, 2024

@SacredMesa We can possibly connect over a call and sort out the issue if that's OK for you. You can ping me in the discord server: https://discord.com/invite/uyb7pYt4Pa.

from jackson.

deepakprabhakara avatar deepakprabhakara commented on June 1, 2024

@SacredMesa Closing this, please re-open if this continues to be an issue.

from jackson.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.