sergiodxa / remix-auth-oauth2

A OAuth2Strategy for Remix Auth
https://sergiodxa.github.io/remix-auth-oauth2/
MIT License
160 stars 63 forks source link

Cognito token fetch fails with 405 'Method Not Allowed' #128

Open danclarke opened 1 month ago

danclarke commented 1 month ago

I've been tracking down an issue with AWS Cognito logins. I've tracked the issue to the request in token.js, the fetch request always fails. I can't see anything wrong with the code, but I've been completely unsuccessful in trying to trace the actual request. If anyone has any ideas, I'd be very happy to give them a go. There is something specifically wrong with the request that is actually sent vs. what is requested.

I replaced the original fetch request with an Axios fetch, and the request went through successfully:

const authHeader = context.headers.get('Authorization');
const response = axios.post(endpoint, Object.fromEntries(context.body), {
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
    'Authorization': authHeader,
  }
});
emiel commented 1 week ago

Heya @danclarke and @sergiodxa . I've been tearing my hair out on this same issue and glad to see I'm not the only one. This is preventing us from upgrading remix-auth-oauth2 to 2.4.0 (from 1.11.2).

I'll share my findings so far.

Succes case

Here is some output for the development (success case).

I'm logging context, request and response in this code: https://github.com/sergiodxa/remix-auth-oauth2/blob/c308c6f0c8b632bf933cf2901ad48e15953e27bb/src/lib/token.ts#L50-L51

context Context {
  method: 'POST',
  body: URLSearchParams {
    'grant_type' => 'authorization_code',
    'code' => '6E9rb7Jxzz6fwmKsf0SvfkKXQAjBNl',
    'redirect_uri' => 'http://localhost:5173/authn/callback',
    'code_verifier' => 'l49E1l6dqNBgGvsTb-yZCVN-uIah3CzlyIzVuW2BPLs',
    'client_id' => 'SgTyEPXXoQysIvwBKoFikMPAfv6ehOjBwJVPy88l',
    'client_secret' => 'T6YY1VbOT87nEhF3jOQqAsnZNey4KZqCrAHK90MWUrRMvAVt55vNKUhxCyiBNgOCVlavbfUuIum8llCnpQQAAC8WRfBWgeJPX66lR8kKGwaheq6Vtcz9k7zLqhYkrtH7' },
  headers: Headers {
    'Content-Type': 'application/x-www-form-urlencoded',
    Accept: 'application/json',
    'User-Agent': 'oslo'
  }
}
request Request {
  method: 'POST',
  url: 'http://localhost:8000/oauth2/token/',
  headers: Headers {
    'Content-Type': 'application/x-www-form-urlencoded',
    Accept: 'application/json',
    'User-Agent': 'oslo'
  },
  destination: '',
  referrer: 'about:client',
  referrerPolicy: '',
  mode: 'cors',
  credentials: 'same-origin',
  cache: 'default',
  redirect: 'follow',
  integrity: '',
  keepalive: false,
  isReloadNavigation: false,
  isHistoryNavigation: false,
  signal: AbortSignal { aborted: false }
}
response Response {
  status: 200,
  statusText: 'OK',
  headers: Headers {
    date: 'Wed, 23 Oct 2024 08:11:26 GMT',
    server: 'WSGIServer/0.2 CPython/3.10.15',
    'content-type': 'application/json',
    'cache-control': 'no-store',
    pragma: 'no-cache',
    'content-length': '184'
  },
  body: ReadableStream { locked: false, state: 'readable', supportsBYOB: true },
  bodyUsed: false,
  ok: true,
  redirected: false,
  type: 'basic',
  url: 'http://localhost:8000/oauth2/token/'
}

Failure Case

I'm logging context, request and response in this code: https://github.com/sergiodxa/remix-auth-oauth2/blob/c308c6f0c8b632bf933cf2901ad48e15953e27bb/src/lib/token.ts#L50-L51

context Context {
  method: 'POST',
  body: URLSearchParams {
    'grant_type' => 'authorization_code',
    'code' => 'NgeoKdbUGvwgzohwT9CmwJHMTy9yYk',
    'redirect_uri' => 'http://localhost:3000/authn/callback',
    'code_verifier' => 'Ep5NR3qvj7Bi9EkJXYyRCmHnOVFcPZnqxlWyaAMu38w',
    'client_id' => 'SgTyEPXXoQysIvwBKoFikMPAfv6ehOjBwJVPy88l',
    'client_secret' => 'T6YY1VbOT87nEhF3jOQqAsnZNey4KZqCrAHK90MWUrRMvAVt55vNKUhxCyiBNgOCVlavbfUuIum8llCnpQQAAC8WRfBWgeJPX66lR8kKGwaheq6Vtcz9k7zLqhYkrtH7' },
  headers: {
    accept: 'application/json',
    'content-type': 'application/x-www-form-urlencoded',
    'user-agent': 'oslo'
  }
}
request Request {
  size: 0,
  follow: 20,
  compress: true,
  counter: 0,
  agent: undefined,
  highWaterMark: 16384,
  insecureHTTPParser: false,
  [Symbol(Body internals)]: {
    body: ReadableStream {
      _state: 'readable',
      _reader: undefined,
      _storedError: undefined,
      _disturbed: false,
      _readableStreamController: [ReadableStreamDefaultController]
    },
    type: 'application/x-www-form-urlencoded;charset=UTF-8',
    size: 379,
    boundary: null,
    disturbed: false,
    error: null
  },
  [Symbol(Request internals)]: {
    method: 'POST',
    redirect: 'follow',
    headers: {
      accept: 'application/json',
      'content-type': 'application/x-www-form-urlencoded',
      'user-agent': 'oslo'
    },
    credentials: 'same-origin',
    parsedURL: URL {
      href: 'http://localhost:8000/oauth2/token/',
      origin: 'http://localhost:8000',
      protocol: 'http:',
      username: '',
      password: '',
      host: 'localhost:8000',
      hostname: 'localhost',
      port: '8000',
      pathname: '/oauth2/token/',
      search: '',
      searchParams: URLSearchParams {},
      hash: ''
    },
    signal: AbortSignal { aborted: false }
  }
}
response Response {
  size: 0,
  [Symbol(Body internals)]: {
    body: ReadableStream {
      _state: 'readable',
      _reader: undefined,
      _storedError: undefined,
      _disturbed: false,
      _readableStreamController: [ReadableStreamDefaultController]
    },
    type: null,
    size: null,
    boundary: null,
    disturbed: false,
    error: null
  },
  [Symbol(Response internals)]: {
    url: 'http://localhost:8000/oauth2/token/',
    status: 400,
    statusText: 'Bad Request',
    headers: {
      'cache-control': 'no-store',
      'content-length': '35',
      'content-type': 'application/json',
      date: 'Wed, 23 Oct 2024 08:08:00 GMT',
      pragma: 'no-cache',
      server: 'WSGIServer/0.2 CPython/3.10.15'
    },
    counter: 0,
    highWaterMark: 16384
  }
}
response-body { error: 'unsupported_grant_type' }

Environment

$ node --version v22.10.0

$ cat package.json

{
  "name": "some-project",
  "private": true,
  "sideEffects": false,
  "type": "module",
  "scripts": {
    "build": "remix vite:build",
    "dev": "remix vite:dev",
    "lint": "eslint --cache --cache-location ./node_modules/.cache/eslint .",
    "format:check": "prettier --check .",
    "start": "remix-serve ./build/server/index.js",
    "typecheck": "tsc",
    "test": "vitest run",
    "db:generate": "drizzle-kit generate",
    "db:generate-custom": "drizzle-kit generate --custom",
    "db:drop": "drizzle-kit drop",
    "db:migrate": "drizzle-kit migrate",
    "db:up": "drizzle-kit up",
    "db:check": "drizzle-kit check",
    "db:studio": "drizzle-kit studio"
  },
  "dependencies": {
    "@amplitude/analytics-browser": "^2.11.7",
    "@amplitude/plugin-session-replay-browser": "^1.8.0",
    "@beefree.io/sdk": "^4.3.1",
    "@headlessui/react": "2.1.0",
    "@heroicons/react": "^2.1.4",
    "@intercom/messenger-js-sdk": "^0.0.11",
    "@remix-run/node": "^2.9.2",
    "@remix-run/react": "^2.9.2",
    "@remix-run/serve": "^2.9.2",
    "@tiptap-pro/extension-unique-id": "^2.11.1",
    "@tiptap/core": "^2.6.5",
    "@tiptap/extension-bold": "^2.6.5",
    "@tiptap/extension-document": "^2.6.5",
    "@tiptap/extension-hard-break": "^2.6.5",
    "@tiptap/extension-heading": "^2.6.5",
    "@tiptap/extension-history": "^2.6.5",
    "@tiptap/extension-image": "^2.6.5",
    "@tiptap/extension-italic": "^2.6.5",
    "@tiptap/extension-link": "^2.6.5",
    "@tiptap/extension-paragraph": "^2.6.5",
    "@tiptap/extension-placeholder": "^2.6.5",
    "@tiptap/extension-text": "^2.6.5",
    "@tiptap/extension-underline": "^2.6.5",
    "@tiptap/pm": "^2.6.5",
    "@tiptap/react": "^2.6.5",
    "bcryptjs": "^2.4.3",
    "class-variance-authority": "^0.7.0",
    "clsx": "^2.1.1",
    "drizzle-orm": "^0.35.1",
    "formik": "^2.4.6",
    "isbot": "^4.4.0",
    "pg": "^8.13.0",
    "rc-slider": "^11.0.1",
    "react": "^18.3.1",
    "react-beautiful-dnd": "^13.1.1",
    "react-dom": "^18.3.1",
    "react-tooltip": "^5.27.1",
    "tailwind-merge": "^2.3.0",
    "tsx": "^4.16.0",
    "use-debounce": "^10.0.1"
  },
  "devDependencies": {
    "@effect/schema": "^0.71.0",
    "@remix-run/dev": "^2.9.2",
    "@tailwindcss/typography": "^0.5.13",
    "@types/bcryptjs": "^2.4.6",
    "@types/jsdom": "^21.1.6",
    "@types/pg": "^8.11.6",
    "@types/react": "^18.3.3",
    "@types/react-beautiful-dnd": "^13.1.8",
    "@types/react-dom": "^18.3.0",
    "@typescript-eslint/eslint-plugin": "^7.15.0",
    "@typescript-eslint/parser": "^7.15.0",
    "csv-parse": "^5.5.6",
    "csv-stringify": "^6.4.5",
    "date-fns": "^3.6.0",
    "drizzle-kit": "^0.26.2",
    "effect": "^3.6.4",
    "entities": "^5.0.0",
    "eslint": "^8.57.0",
    "eslint-import-resolver-typescript": "^3.6.1",
    "eslint-plugin-import": "^2.28.1",
    "eslint-plugin-jsx-a11y": "^6.7.1",
    "eslint-plugin-react": "^7.33.2",
    "eslint-plugin-react-hooks": "^4.6.0",
    "fast-check": "^3.19.0",
    "prettier": "^3.3.3",
    "prettier-plugin-tailwindcss": "^0.6.5",
    "remix-auth": "^3.7.0",
    "remix-auth-oauth2": "^2.4.0",
    "remix-typedjson": "^0.4.1",
    "tailwindcss": "^3.4.4",
    "tailwindcss-animate": "^1.0.7",
    "ts-node": "^10.9.2",
    "typescript": "^5.5.3",
    "typescript-eslint": "^7.15.0",
    "vite": "^5.4.10",
    "vite-tsconfig-paths": "^4.3.2",
    "vitest": "^2.0.5",
    "zeed-dom": "^0.13.3",
    "zod": "^3.23.8",
    "zodix": "^0.4.4"
  },
  "engines": {
    "node": ">=18.18.0"
  }
}