Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

client_secret being passed in post data in Basic auth #950

Closed
2 of 5 tasks
carlbarrdahl opened this issue Dec 11, 2020 · 13 comments
Closed
2 of 5 tasks

client_secret being passed in post data in Basic auth #950

carlbarrdahl opened this issue Dec 11, 2020 · 13 comments
Labels
enhancement New feature or request help needed The maintainer needs help due to time constraint/missing knowledge providers stale Did not receive any activity for 60 days

Comments

@carlbarrdahl
Copy link

Describe the bug
We're trying to integrate Signicat OpenID with Basic auth and get this message:

{
  statusCode: 400,
  data: '{"error":"invalid_request","error_description":"Your client is configured to authenticate using CLIENT_SECRET_BASIC"}'
}

This is because client_secret is sent as post data here, even if its null or undefined:

const postData = querystring.stringify(params)

With empty client secret (note the: &client_secret=):

// formData is: grant_type=authorization_code&client_id=client_id&client_secret=&code=code&redirect_uri=uri
{
  statusCode: 400,
  data: `{"error":"invalid_request","error_description":"Form body malformed! (Body is not a set of key-value pairs separated by '=', delimited by '&' characters)"}`
}

Here are some possible solutions:

if (!params.client_secret) {
  if (provider.clientSecretCallback) {
    params.client_secret = yield provider.clientSecretCallback(provider.clientSecret);
  } else {
    params.client_secret = provider.clientSecret;
  }
}

Steps to reproduce
Use custom oauth2 config:

{
  id: "signicat",
  name: "Signicat BankID",
  type: "oauth",
  version: "2.0",
  scope: "openid profile signicat.national_id signicat.certificate",
  params: { grant_type: "authorization_code" },
  accessTokenUrl: `https://${config.signicatHost}/oidc/token`,
  requestTokenUrl: `https://${config.signicatHost}/oidc/token`,
  authorizationUrl: `https://${config.signicatHost}/oidc/authorize?response_type=code&response_mode=form_post&acr_values=urn:signicat:oidc:method:sbid`,
  profileUrl: `https://${config.signicatHost}/oidc/userinfo`,
  clientId: config.signicatClientId,
  clientSecret: config.signicatClientSecret,
  idToken: true,
  state: false,
  clientSecretCallback: () => null, // Remove client_secret from post data in request
  headers: {
    Authorization: `Basic ${Buffer.from(config.signicatClientId + ":" + config.signicatClientSecret, "ascii").toString("base64")}`,
  }
}

Expected behavior
client_secret should be omitted from postData if headers.Authorization.includes("Basic")

Feedback
Documentation refers to searching through online documentation, code comments and issue history. The example project refers to next-auth-example.

  • Found the documentation helpful
  • Found documentation but was incomplete
  • Could not find relevant documentation
  • Found the example project helpful
  • Did not find the example project helpful
@carlbarrdahl carlbarrdahl added the bug Something isn't working label Dec 11, 2020
@balazsorban44
Copy link
Member

Hi there!

Just out of curiosity, since I'm not familiar with Signicat, have you actually tried to just provide a secret and see if sending it in post data would work?

Is this the documentation of what you are referring to?: https://developer.signicat.com/documentation/authentication/protocols/openid-connect/endpoints/

I think we have to see if we want to/need to expand the public facing API as you suggested above, but I have to discuss this further first.

I'll get back to you with more information,

Thanks!

@balazsorban44 balazsorban44 added enhancement New feature or request help needed The maintainer needs help due to time constraint/missing knowledge providers labels Dec 11, 2020
@carlbarrdahl
Copy link
Author

Hi @balazsorban44. Thanks for your response.

Yes when sending the client_secret Signicat throws me the first error (Your client is configured to authenticate using CLIENT_SECRET_BASIC).

I was able to get around this by monkeypatching querystring to filter out empty keys:

const _stringify = querystring.stringify;
querystring.stringify = (...args) => {
  args[0] = Object.keys(args[0]).reduce(
    (obj, x) => (args[0][x] ? { ...obj, [x]: args[0][x] } : obj),
    {}
  );
  return _stringify.call(null, ...args);
};

However, I got stuck again when trying to parse the profileData:

if (typeof profileData === 'string' || profileData instanceof String) { profileData = JSON.parse(profileData) }

Since we're using encryption Signicat is sending back an encrypted JWT which obviously fails to JSON.parse.

This is unfortunate because if I could only get past that line, I would be able to decode it in the provider.profile function.

profile = await provider.profile(profileData)

I really wish to use this excellent library but for now I don't see how to get it working without doing a fork and rewrite parts of:
/src/server/lib/oauth/callback.js to change:

  • Omit client_secret from post data if headers.Authorization is set in _getOAuthAccessToken
  • Allow encrypted profileData (don't throw if JSON.parse fails) in _getProfile

@balazsorban44
Copy link
Member

balazsorban44 commented Dec 14, 2020

Hmm, if you have the time and interest, I think you could try create the changes and propose it in a PR, and we can discuss further what we could incorporate into the core package. I think if it is a general enough solution that we can back up with links to official OAuth and/or OIDC specs and doesn't alter the user facing api much (or at all), I am pretty sure we could (and should) accept such a change.

@carlbarrdahl
Copy link
Author

After playing around with the code base and with panva/node-openid-client, I think perhaps this could be a solution:

  • Provide a way to add a private keystore to provider config
  • Set provider.idToken to an object with encryption algorithms
  • Decrypt idToken if keystore and algorithms are present

Here is the relevant code in panva/node-openid
https://github.com/panva/node-openid-client/blob/82382e8e8904e9cfb87228d11fd01ca7c425af9d/lib/client.js#L449-L457

Basically there needs to be a way to decrypt tokens before they get decoded if keys are present.

@stale stale bot added the stale Did not receive any activity for 60 days label Feb 16, 2021
@nextauthjs nextauthjs deleted a comment from stale bot Feb 16, 2021
@stale stale bot removed the stale Did not receive any activity for 60 days label Feb 16, 2021
@mod-flux
Copy link

mod-flux commented Feb 24, 2021

@balazsorban44 I've just come across this issue with an OAuth service that doesn't accept via POST. As per the most up-to-date IETF OAuth 2.0 Authorization Framework spec, providing the client credentials via the request-body (POST) is explicitly called out as NOT RECOMMENDED and only to be used in situations where clients are unable to directly utilize the HTTP Basic authentication scheme, which doesn't cover this library's primary use case. Here is the snippet in question in the RFC:

Including the client credentials in the request-body using the two
parameters is NOT RECOMMENDED and SHOULD be limited to clients unable
to directly utilize the HTTP Basic authentication scheme (or other
password-based HTTP authentication schemes). The parameters can only
be transmitted in the request-body and MUST NOT be included in the
request URI.

Reference: https://tools.ietf.org/html/rfc6749#section-2.3.1

Not having the ability to choose between Basic Auth and POST by easily stripping out the client_id and client_secret from the request body and provide it via Basic Auth is a blocker for us as our OAuth provider is following the latest OAuth specifications and doesn't marry with the support available with NextAuth. :-(

As per your previous comments with regards to links, does this cover that requirement?

@Fronix
Copy link

Fronix commented Mar 12, 2021

@carlbarrdahl We have the exact same issue using Signicat's OpenID-connect solution. Did you manage to get it working with the modifications?

@balazsorban44 balazsorban44 removed the bug Something isn't working label Mar 28, 2021
@balazsorban44
Copy link
Member

balazsorban44 commented Apr 25, 2021

Please have a look at #1846 and tell me if it could help! (feel free to suggest options that could benefit this case).

Also a migration to openid-client is in progress at #1698

@balazsorban44
Copy link
Member

A hacky way is to set id: "reddit" in the Provider config for now as I found out yesterday:

if (provider.id === 'reddit') {
headers.Authorization = 'Basic ' + Buffer.from((provider.clientId + ':' + provider.clientSecret)).toString('base64')
}

#1846 would hopefully help to deal with this more elegantly

@mod-flux
Copy link

A hacky way is to set id: "reddit" in the Provider config for now as I found out yesterday:

if (provider.id === 'reddit') {
headers.Authorization = 'Basic ' + Buffer.from((provider.clientId + ':' + provider.clientSecret)).toString('base64')
}

#1846 would hopefully help to deal with this more elegantly

Yeap @balazsorban44 that gets us halfway there, the only other thing that's needed is removing client_secret from params before this line:

const postData = querystring.stringify(params)

Otherwise, the OAuth vendor complains that you're sending through the client credentials in an invalid format and rejects the request.

@Fronix
Copy link

Fronix commented May 17, 2021

A hacky way is to set id: "reddit" in the Provider config for now as I found out yesterday:

if (provider.id === 'reddit') {
headers.Authorization = 'Basic ' + Buffer.from((provider.clientId + ':' + provider.clientSecret)).toString('base64')
}

#1846 would hopefully help to deal with this more elegantly

Yeap @balazsorban44 that gets us halfway there, the only other thing that's needed is removing client_secret from params before this line:

const postData = querystring.stringify(params)

Otherwise, the OAuth vendor complains that you're sending through the client credentials in an invalid format and rejects the request.

There is another way to get you there using patch-package, modify the client.js file and make a patch.

  // Added as a fix for Reddit Authentication
  if (provider.id === 'reddit' || provider.id === 'mycustomprovider') {
    headers.Authorization = 'Basic ' + Buffer.from((provider.clientId + ':' + provider.clientSecret)).toString('base64')
  }
  if(provider.id === 'mycustomprovider') {
    delete params.client_secret;
  }

This is how we solved it for now.

@stale
Copy link

stale bot commented Jul 16, 2021

Hi there! It looks like this issue hasn't had any activity for a while. It will be closed if no further activity occurs. If you think your issue is still relevant, feel free to comment on it to keep it open. (Read more at #912) Thanks!

@stale stale bot added the stale Did not receive any activity for 60 days label Jul 16, 2021
@balazsorban44
Copy link
Member

I Will close this in favor of #1846

@wowczarczyk
Copy link

wowczarczyk commented Apr 3, 2023

In case anyone wanders in here trying to use Signicat Express with next-auth, here is a config that worked for me:

{
      id: "signicat",
      name: "Signicat BankID",
      type: "oauth",
      version: "2.0",
      wellKnown:
        "https://login-test.signicat.io/.well-known/openid-configuration",
      token: {
        params: {
          scope: "openid nin phone email profile",
        },
      },
      client: {
        redirect_uris: ["http://localhost:3000/api/auth/callback/signicat"],
        client_id: "**********",
        client_secret:
          "************",
      },
      checks: ["pkce", "nonce"],

      issuer: "https://login-test.idfy.no",

      clientId: "**********",
      clientSecret:
        "***********",
      idToken: true,
      profile: (profile) => {
        return {
          id: profile.sub,
          name: profile.name,
          email: profile.email,
          nin: profile.nin,
          phone: profile.phone,
        };
      },
      jwks_endpoint:
        "https://login-test.signicat.io/.well-known/openid-configuration/jwks",
      authorization: {
        url: "https://login-test.signicat.io/connect/authorize",
        params: {
          scope: "openid nin phone email profile",
          grant_type: "authorization_code",
          redirect_uri: "http://localhost:3000/api/auth/callback/signicat",
        },
      },
    },
  

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request help needed The maintainer needs help due to time constraint/missing knowledge providers stale Did not receive any activity for 60 days
Projects
None yet
Development

No branches or pull requests

5 participants