Coder Social home page Coder Social logo

badgateway / oauth2-client Goto Github PK

View Code? Open in Web Editor NEW
264.0 264.0 29.0 1.11 MB

OAuth2 client for Node and browsers

Home Page: https://www.npmjs.com/package/@badgateway/oauth2-client

License: MIT License

Makefile 1.01% TypeScript 97.66% JavaScript 1.34%
fetch hacktoberfest middleware oauth oauth2 typescript

oauth2-client's People

Contributors

adambom avatar anentropic avatar asg5704 avatar bbigras avatar bradjones1 avatar debater-coder avatar dependabot[bot] avatar evert avatar mhum avatar parkerduckworth avatar pks1989 avatar south-paw avatar ymajoros2 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

oauth2-client's Issues

provide web library over cdn

hello,

I am trying to use you libraries in a frontend, I am not an expert in nodejs, but it appears that we cannot use an npm module as is. While looking at your build scripts, a browser version can be generate.
While looking on internet it appears that there is already a library with same name: https://www.jsdelivr.com/package/npm/oauth2-client-js

Can you release something? or the only way is to generate the browser version and to embed this js in my front?

Thank you,
CΓ©dric,

Add an optional scope parameter to the client.refreshToken method

Refresh token grant takes an optional parameter scope according to the oauth2 spec

The client just takes the (refresh) token as a parameter link:

  async refreshToken(token: OAuth2Token): Promise<OAuth2Token> {

How about adding an optional scope to the client.refreshToken method.

Can be bypassed for now by using the request('tokenEndpoint', body) method directly.

Also:
README.md seems to have the method name incorrectly as refresh instead of refreshToken.

Thanks!

Public client without authentication method

I want to use OAuth public client with code flow and PKCE. The issue is with token request, which must include client_id in this case according to the https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.3. This library adds the client_id to the token request only if the "settins.authenticationMethod" equals to "client_secret_post" which is wrong IMO. It should be added even if no authenticationMethod is used.
Another issue is if I do not provide "authenticationMethod" in settings, this library will use the first authentication method from OAuth server metadata (token_endpoint_auth_methods_supported). For example, if the server metadata contains ["client_secret_basic"], then the library will include Authorization HTTP header with basic authentication even if I do not intend to use any.

2.1.0 dist folder doesn't contain updated code

πŸ‘‹

Looks like the dist js files in 2.1.0 don't contain the latest changes

image

Odd, because the .d.ts does have correct types.

image

Side note: have you considered publishing via GitHub actions so the build+publish is handled consistently rather than manually publishing? I'm happy to offer a PR for that if you'd like.

Thanks

Couple of things not working as expected

I'm using authorization_code grant, and got it working like so:

const client = new OAuth2Client({
	"authorizationEndpoint": "/auth",
	"tokenEndpoint": new URL("./token", AUTH_URL).href,
	"server": "https://developer.foo.com/oauth",
	"clientId": INTEGRATION_KEY,
	"clientSecret": SECRET_KEY
});

From the README:

// OAuth2 client secret. Only required for 'client_credentials', 'password'
// flows. You should not specify this for authorization_code.

I'm not sure why this suggests not using the clientSecret. I needed it for it to work.


From the types:

/**
 * The hostname of the OAuth2 server.
 * If provided, we'll attempt to discover all the other related endpoints.
 *
 * If this is not desired, just specify the other endpoints manually.
 *
 * This url will also be used as the base URL for all other urls. This lets
 * you specify all the other urls as relative.
 */
server?: string;

I couldn't get relative URLs to work, and looking here, I think these need to be ./ instead of /.

This would also explain why I needed to specify tokenEndpoint at all, since the default should have worked.


From the README:

/**
 * You are responsible for implementing this function.
 * it's purpose is to supply the 'initial' oauth2 token.
 */
getNewToken: async () => {

  // Example
  return client.clientCredentials();

  // Another example
-  return client.authorizationCode({
    code: '..',
    redirectUri: '..',
  });

This needed to be client.authorizationCode.getToken().

Automatic refresh with PKCE and `FetchWrapper` (codeverifer not included)

Hello,

I'm using this library with PKCE and FetchWrapper and I am having trouble with the refresh tokens. I instantiate FetchWrapper like so:

const fetchWrapper = new OAuth2Fetch({
  client,

  getNewToken: () => {
    // Set the status to expired
    useAuthStore.setState({ status: AuthStatus.EXPIRED });

    return null; // Fail this step, we don't want to log out until the user does so explicitly
  },

  storeToken: (token) => {
    useAuthStore.setState({
      token,
    });
  },

  getStoredToken: () => useAuthStore.getState().token,
});

From my understanding, this fetchWrapper should automatically refresh the tokens when they expire and it tries to send a request.
image
Indeed, it does appear that it is sending a refresh request to the token endpoint. However, the server rejects it, because it does not include the code_verifier. Before using this library, I built a custom solution which does send the code_verifier in the refresh requests, and I can verify that if it sends that then it does work.

I believe the solution is to make some API change that allows the FetchWrapper to access the code_verifier (potentially by storing the code_verifier in the OAuth2Token).

Thank you.

Use URL searchParams when construction in `getAuthorizeUri`

Google requires ?access_type=offline in the authorization url:

https://accounts.google.com/o/oauth2/auth?access_type=offline&prompt=consent

This library concats ?client_id= which leads to two ?.

A more elegant way would be to use new URL() and then .searchParams.append('client_id', ...).

discovery bugs

Because of a misplaced ! in client.ts, discovery document isn't accepted if its content-type is right:
``` if (!resp.headers.has('Content-Type') || resp.headers.get('Content-Type')!.startsWith('application/json')) {``

Additionnaly, when that is fixed, this code gives me wrong endpoints:

    const authorizeParams = {
        redirectUri: serviceWautherConfig.redirectUri,
        codeVerifier: codeVerifier
    };
    const silentRefreshAuthorizeParams = {
        redirectUri: serviceWautherConfig.silentRefreshRedirectUri,
        codeVerifier: codeVerifier
    };
    const silentRefreshAuthorizeUriPromise = oidcClient.authorizationCode.getAuthorizeUri(silentRefreshAuthorizeParams);
    const authorizeUriPromise = oidcClient.authorizationCode.getAuthorizeUri(authorizeParams);
    const [authorizeUri, silentRefreshAuthorizeUri] = await Promise.all([authorizeUriPromise, silentRefreshAuthorizeUriPromise]);

returns this:

authorizeUrl =  http://localhost:26800/realms/burger/protocol/openid-connect/auth?response_type=code&client_id=.....
silentRefreshAuthorizeUrl =  http://localhost:26800/authorize?response_type=code&client_id=...

(second url seems to be defaulted instead of using the discovery document)

Handle Scope

What's the best way to attach scope to an OAuth client? I don't see it included in the types, but looking at the util.ts code it's included.

Abort and retry any other in-flight requests at time of refresh

I'm trying to figure out how to handle the situation where any number of requests may be in-flight at a moment when token refresh occurs as the result of a 401.

In particular I'm debugging a situation in which I have, say, 8 parallel requests and only one is refreshing, and strangely (not sure if it's a result of the middleware or something else) the others continue without use of the middleware altogether.

I think the answer would be some sort of global state/singleton which coordinates an AbortController and signals all in-flight requests, and then retries them after the refresh?

Fetch is not defined

I checked the library, it is very useful and readme is understandable to anyone. Applause for that.
I tried to run your source code, but unfortunately i got the error with client_credentials flow,

But then I checked your source code, there is no declaration for fetch anywhere in the files.

~\node_modules\@badgateway\oauth2-client\dist\client.js:157
        const resp = await fetch(uri, {
                     ^

ReferenceError: fetch is not defined;


A simple test file to reproduce the bug:

const { OAuth2Client} = require('@badgateway/oauth2-client');

const client = new OAuth2Client({
scope: [
'email',
'offline_access',
'openid'
],

grantType: 'client_credentials',
clientId: 'a*****************************',
clientSecret: '******************************3',
discoveryEndpoint: 'https://login.microsoftonline.com/*****************8/v2.0/.well-known/openid-configuration',
tokenEndpoint: 'https://login.microsoftonline.com/*****************8/oauth2/v2.0/token',
authorizationEndpoint: 'https://login.microsoftonline.com/*****************8/oauth2/v2.0/authorize'
});

const express = require('express');

async function main() {
const token = await client.clientCredentials();
console.log('called ::: ', token);
}

const app = express();

app.listen(3001, () => {
console.log(
Msal Node Auth Code Sample app listening on port ${3001}!
);
main();
});

Please help and provide a solution to fix it.

/token: Authorization header is not set

I'm using this and the Authorization header doesn't seem to be set in the request:

clientSecret is set.

  const oauth2Token = await client.authorizationCode.getToken({
    code: code,
    redirectUri: "http://localhost:3000/callback"
  });

I'm wondering if this condition should be body.grant_type === 'authorization_code':

oauth2-client/src/client.ts

Lines 268 to 271 in 00f1cd3

if (body.grant_type !== 'authorization_code' && this.settings.clientSecret) {
const basicAuthStr = btoa(this.settings.clientId + ':' + this.settings.clientSecret);
headers.Authorization = 'Basic ' + basicAuthStr;
}

Constructors of `OAuth2Client` and `OAuth2Fetch` capture values causing race conditions sometimes

When I updated from 2.0.18 to 2.2.1 I noticed that some of my tests stopped passing. Some of these tests relied on msw. When used in a Node.js test environment, it alters the fetch function initially supplied by jsdom. However in update 2.2.0, the current value of fetch is captured here. This means that if the constructor is called before msw has finished mocking fetch, the initial jsdom implementation is captured and used instead.

Previously, I had a file which looked like this:

import { OAuth2Client } from "@badgateway/oauth2-client";
import config from "./config";

export const client = new OAuth2Client({
      server: config.server,
      clientId: config.client_id,
      tokenEndpoint: "/api/token",
      authorizationEndpoint: config.authorization_endpoint,
    });

This means that the constructor is called as soon as the module is imported, and msw may not have had a chance to mock yet. The solution for me, was to defer the creation of the client until it is needed like so:

import { OAuth2Client } from "@badgateway/oauth2-client";
import config from "./config";

let _client: OAuth2Client | null = null;

export const getClient = () => {
  if (!_client) {
    _client = new OAuth2Client({
      server: config.server,
      clientId: config.client_id,
      tokenEndpoint: "/api/token",
      authorizationEndpoint: config.authorization_endpoint,
    });
  }
  return _client;
};

However, this may not be obvious to new users of the library unfamiliar with the source code. A similar issue occurs with the getStoredToken option, which is invoked immediately by the constructor, which can be an issue if the persistent store needs initialisation.

Expected behaviour for the first case would be that instead of capturing the value at the constructor, the client could check the existence of the custom implementation when it is needed, like this:

this.settings.fetch?.(...) ?? fetch(...)

This could be made into a method on the OAuth2Client class:

customFetch(request: Request, init?: RequestInit | undefined) {
    return this.settings.fetch?.(...) ?? fetch(...)
}

Alternatively, if this is intended behaviour it could be documented in the README, but this may be a breaking change.

For the second case the expected behaviour would be to only call getStoredToken when actually making the request in a similar pattern.

Token expires_in response formatted as string

If the authentication server client_credentials response has the expires_in formatted as a string grant_base.ts will throw an error.

Body Example:
{
access_token: "/DsAH2ykgeGGfbLY7GEhduWMCc9H0Gih1XVrUZQaeNJNJONEWJm0uuZpmrXSK5mzMXC5jEXSJVjYpF+Xy6FRdarw646T+4pPaJ5KMawlaVY=",
expires_in: "2700",
token_type: "Bearer",
}

Code block at line 114 in grant_base.ts:

if (
body.expires_in !== undefined && typeof body.expires_in !== "number"
) {
throw new TokenResponseError(
"expires_in is not a number",
response,
);
}

Testing for both number and string would handle this situation and in the tokens.expiresIn assignment below convert type from string to number if needed.

CORS policy

Access to fetch at from origin 'http://localhost:1420' has been blocked by CORS policy: 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.

is this supposed to happen?

OAuth2: onAuthError callback not invoked

Hi, iΒ΄m using version 7.1.1 of this library and i try to handle the case when refresh token is expired. There is the option onAuthError i found in your docs but canΒ΄t get it to work.

token refresh is working and the onTokenUpdate callback is triggered on update. Whereas on error with status code 401 the onAuthError callback is just not called.

halClient.use(oauth2({
                clientId: 'managerclient',
                clientSecret: 'secret',
                accessToken: access_token,
                refreshToken: refresh_token, 
                tokenEndpoint: endpoint, 
                onTokenUpdate: () => { // called on update
                    Logger.info('token refreshed');
                },
                onAuthError: (error) => { // not called. 
                    Logger.error(error);

                    // redirect to login
                    dispatch(navigateToLoginRoute());
                }
}));

Is there something iΒ΄m missing?

Fetch not bound to window

I am getting the following error in a browser: TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation at t.OAuth2Client.request

The problem appears to be that fetch binds to the settings object but it needs to be bound to the window. Probably the easiest fix is to pass the fetch call as a lambda in the client constructor like this:

const originalFetch = fetch;
const client = new OAuth2Client({
  ...
  fetch: (...args) => originalFetch(...args),
});

I do not think this is a problem when running in a node environment. I am not sure how this plays out in a service worker.

The best fix is probably to change this:

clientSettings.fetch = fetch;

to something like what I've done in the constructor.

As a side note, I use a cached copy of the original fetch because I think it's best practice for all oauth related calls to protect against XSS. This provides a good explanation of why, though the next comment shows that there's still more a hacker could do. That's why it's even better to make sure this code can run in a properly implemented service worker.

Support optional audience on clientCredentials call

Lib is great πŸ‘, thanks for your hard work!

The Kinde management API requires an audience be sent on client credentials grant

On client.ts#L144

// client.ts L144
async clientCredentials(params?: { audience?: string, scope?: string[] }): Promise<OAuth2Token> {
    const body:ClientCredentialsRequest = {
      grant_type: 'client_credentials',
      audience: params?.audience,
      scope: params?.scope?.join(' '),
    };

   // ...
}

Wrong authentication endpoint with discoveryEndpoint used

Hello,
I am trying to get OIDC to work using just the discovery endpoint. I did point it ti the .well-known/openid-configuration url and checked that the correct endpoints are configured there. When I use this.client.getEndpoint('authorizationEndpoint') it gets me the default configured /authorize instead of what is configured in the .well-known. My best guess is that this happens because authorizationEndpoint has a default value, so that one is returned before the code actually checking the discoveryEndpoint. This should not happen because if I give it an discorverEndpoint, I expect it to be used over default values.

oauth2-client/src/client.ts

Lines 283 to 295 in 7ce4f29

async getEndpoint(endpoint: OAuth2Endpoint): Promise<string> {
if (this.settings[endpoint] !== undefined) {
return resolve(this.settings[endpoint] as string, this.settings.server);
}
if (endpoint !== 'discoveryEndpoint') {
// This condition prevents infinite loops.
await this.discover();
if (this.settings[endpoint] !== undefined) {
return resolve(this.settings[endpoint] as string, this.settings.server);
}
}

endpoint domain addresses could differ.

new endpoint as follows
"accounts.google.com/o/oauth2/v2/auth"
"www.googleapis.com/oauth2/v4/token"

as per google,
Screen Shot 2022-09-11 at 13 42 50
endpoints could differ, so it could be easier to have full url's on the configuration.
and a few real-world examples with public api's could be nice...

OAuth2Client `server` value gets truncated to just the base URL

I thought this would work (notice the value of "server"):

const client = new OAuth2Client({
  clientId: 'id',
  clientSecret: 'secret',
  server: 'https://api.schwabapi.com/v1/oauth',
});

export async function getAuthorizeUri() {
  return client.authorizationCode.getAuthorizeUri({
    redirectUri: schwabRedirectUri,
    scope: ['readonly'], 
  });
}

But then getAuthorizeUri() returns https://api.schwabapi.com/authorize?client_id=id&response_type=code&redirect_uri=________&scope=readonly, where the URL is missing /v1/oauth.

It should instead return https://api.schwabapi.com/v1/oauth/authorize?client_id=id&response_type=code&redirect_uri=________&scope=readonly.

A workaround would be for me to prepend /v1/oauth to the values for authorizationEndpoint and tokenEndpoint, but it would be convenient to just leave them appended to "server".

Am I misunderstanding something?

Thanks!

Revoke token

Hi there, looking at the client, I don't see a way to revoke a token. I believe (correct me if I'm wrong) that this is part of the OAuth spec, so it seems reasonable that the client ought to support it. For instance, the discovery server I'm working with returns a revocation_endpoint.

If there's already a way to do this with the client, I apologize. Please point me in the right direction. Otherwise, I would be happy to take a stab at a PR.

provide a way to get other endpoint URLs

There seems to be currently no way of getting endpoint URLs from the discovery document for some entries, e.g.: endSessionEndpoint etc.

As not all endpoints are specified and we can expect new ones to arise, maybe it would be a good option to be able to specify a free parameter for endpoints besides the existing enum.

Support for 'client_secret_post'

Hello! First of all, thanks for your hard word on this project :)

While working with this library to replace an existing OIDC auth client, I noticed that there is currently only support for client_secret_basic authentication method. This is a blocker for my team, because we are only allowed to enable client_secret_post.

I went ahead and opened PR #84 which contains the changes need to support client_secret_post for both the client_credentials and password grant types.

Potential loss of information when failing to get a token

I've come across a response from a request('tokenEndpoint') which includes a property in the response body that OAuth2Client is ignoring. I've checked the RFC, and there's no "MUST NOT" or similar forbidding extra properties. And at least for this OAuth server, the ignored property would have been very useful to see earlier!

{
  error: "invalid_request",
  error_description: "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed.",
  hint: "Authorization code has expired",
  message: "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed.",
}

I'd like to request that the responseBody be included in the thrown OAuth2Error at the end of request(), so that non-standard properties like hint can be found by developers.

Does not work with nodejs?

It seems this project doesn't work with plain node is it?

I get errors like this:

./node_modules/fetch-mw-oauth2/src/fetch-wrapper.ts:66
    const request = new Request(input, init);
                    ^
ReferenceError: Request is not defined

State query parameter is required in code flow

The authorization code flow logic contains the lines below. These make it so that the state query parameter is required in the response, regardless of whether it was passed in the authorize call. I have a fix for this but can't seem to push a branch.

if (!queryParams.has('code')) throw new Error(`The url did not contain a code parameter ${url}`);
if (!queryParams.has('state')) throw new Error(`The url did not contain state parameter ${url}`);

if (params.state && params.state !== queryParams.get('state')) {
  throw new Error(`The "state" parameter in the url did not match the expected value of ${params.state}`);
}

Possible difference between source and compiled output in 2.0.17

I'm currently trying to get authorization code flows to work with 2.0.17. This version is supposed to include a fix for passing scope correctly, but despite this my code is still not working. Upon digging into node_modules, it looks like the src folder contains the correct code, but the dist folder's built code does not correspond to it (and is thus missing the fixes required to get scope to work):

$ cat package.json | grep oauth2-client                                                                 
    "@badgateway/oauth2-client": "^2.0.17",

$ cat node_modules/@badgateway/oauth2-client/src/client/authorization-code.ts | grep "const query:" -A15 
    const query: AuthorizationQueryParams = {
      client_id: this.client.settings.clientId,
      response_type: 'code',
      redirect_uri: params.redirectUri,
      code_challenge_method: codeChallenge?.[0],
      code_challenge: codeChallenge?.[1],
    };
    if (params.state) {
      query.state = params.state;
    }
    if (params.scope) {
      query.scope = params.scope.join(' ');
    }

    return authorizationEndpoint + '?' + generateQueryString(query);

$ cat node_modules/@badgateway/oauth2-client/dist/client/authorization-code.js | grep "const query =" -A15
        const query = {
            client_id: this.client.settings.clientId,
            response_type: 'code',
            redirect_uri: params.redirectUri,
            code_challenge_method: codeChallenge === null || codeChallenge === void 0 ? void 0 : codeChallenge[0],
            code_challenge: codeChallenge === null || codeChallenge === void 0 ? void 0 : codeChallenge[1],
        };
        if (params.state) {
            query.state = params.state;
        }
        return authorizationEndpoint + '?' + (0, client_1.generateQueryString)(query);
    }
    async getTokenFromCodeRedirect(url, params) {
        const { code } = await this.validateResponse(url, {
            state: params.state
        });

Can anyone else reproduce this? I don't think it's just my setup -- the "Code" tab on npmjs.com seems to confirm that dist and src are out of sync in the packaged version of 2.0.17 (https://www.npmjs.com/package/@badgateway/oauth2-client?activeTab=explore). Cloning the repository and running npm install && make build produces correct artifacts, so perhaps something went wrong with the release?

Thanks for the great library and any time you are able to give to this!

Scope not being passed to refreshToken() in fetch wrapper

I'm not a big js guy so please forgive me if I'm doing a fridge temperature IQ move here but...

I'm using the fetch wrapper to send POST requests from a chrome extension to an app. I can oauth between the two successfully, but when a refreshToken happens from the fetch wrapper, my backend returns a 400 bad request.

Looking into this, I noticed the difference between the two requests is that the refresh requests are missing the scope.

I saw this PR which adds an optional scope param to refreshToken (https://github.com/badgateway/oauth2-client/pull/135/files) but it doesn't look like it's been added to the fetch wrapper https://github.com/badgateway/oauth2-client/blob/main/src/fetch-wrapper.ts#L157

Am i missing something? Is there a way to ensure the scope from the client makes it through to refresh requests in the fetch wrapper?

code verifier generation fails in a service worker

This code from authorization-code.ts failed:

      const arr = new Uint8Array(32);
      crypto.webcrypto.getRandomValues(arr);
      return base64Url(arr);

So I had to create my own solution:

function generateCodeVerifier() {
    const emptyCodeVerifierArray = new Uint8Array(32);
    const codeVerifierArray = crypto.getRandomValues(emptyCodeVerifierArray);
    return btoa(String.fromCharCode(...codeVerifierArray));
}

Allow Refresh Logic Execution Without Refresh Token (fetch-wrapper.ts)

I would like the option to execute the refresh timer without a refresh token.

In fetch-wrapper.ts https://github.com/badgateway/oauth2-client/blob/main/src/fetch-wrapper.ts#L255

The conditional
if (!this.token?.expiresAt || !this.token.refreshToken)
Causes it to not set a refreshTimer when no refreshToken is supplied. I would like to have to option to still execute the refresh logic even if no refresh token is supplied.

Since refreshToken will be called anyway when https://github.com/badgateway/oauth2-client/blob/main/src/fetch-wrapper.ts#L148 getToken is called.

Possible fix:
Replace the conditional with:
if (!this.token?.expiresAt)

Or maybe add an additional option like was done with this.options.scheduleRefresh e.g. this.options.forceScheduleRefresh ?

Thanks in advance!
Greetings,
Koen

Ability to add headers to the token requests

I would like the ability to add custom headers to requests made by OAuth2Client.OAuth2Client

Example usage would be something like;

const client = new Client({
  server: 'https://auth-server.example/',

  clientId: '...',

  clientSecret: '...',

  additionalHeaders: '...',

});

Which I guess should be used in the client.ts's async request method

Linkedin API: Missing client_secret upon redirect

I am having trouble using the library to connect to the Linkedin API. I am using the authorization_code flow, and it fails upon handling the redirection.

I am calling client.authorizationCode.getToken({code, redirectUri, state}) and getting the error from the server:

OAuth2 error invalid_request. A required parameter \"client_secret\" is missing

The Linkedin documentation does mention that the client_secret (and client_id) are required: https://learn.microsoft.com/en-us/linkedin/shared/authentication/authorization-code-flow?context=linkedin%2Fconsumer%2Fcontext&tabs=HTTPS1#step-2-request-an-authorization-code

I just want to know whether the issue is with Linkedin not following the standard, or if this could be an extra option to add to the library (eg. add options like includeClientSecret to GetTokenParams?), in which case I would do a PR.

Calling `oAuth2Fetch.fetch()` immediately after invoking constructor causes race condition with async IIFE invoking `getStoredToken`

This following code fails because getStoredToken is always invoked as an async function, meaning that fetchWrapper.fetch() will be called before getStoredToken() has even been called. See this async IIFE. By the time the fetchWrapper.fetch() is called, the private property fetchWrapper.token is still null, so getToken() attempts to refresh it, which fails, invoking refreshToken() which attempts to refresh the non-existent token, which fails invoking getNewToken() which also fails, finally invoking onError, which prompts the user to log in again. When this is finished, control returns to the async IIFE, which finally sets the token to the correct value, and the next time fetchWrapper.fetch() is invoked, it behaves correctly. I have verified this using the debugger in Chrome DevTools. It is possible to create an async constructor by returning an IIAFE from the constructor. However, this could be tricky without introducing a breaking change. Alternatively, the IIAFE could be stored in a private property which could be awaited inside of the fetchWrapper.fetch() function, ensuring that getStoredToken() has been invoked. Until a fix is released, I have a temporary workaround, but it is quite hacky, so I would appreciate a stable fix.

Problematic Code

const fetchWrapper = new OAuth2Fetch({
    client,
    getNewToken: () => {
      return null; // Fail this step, we don't want to log out until the user does so explicitly
    },
    storeToken,
    getStoredToken,
    onError: (error) => {
      // Prompt the user to log in again
    },
  });

const res = await fetchWrapper.fetch(...)

Temporary workaround

let ready = false;

const initialiseFetchWrapper = async () => {
  const fetchWrapper = new OAuth2Fetch({
    client,

    getNewToken: () => {
      return null; // Fail this step, we don't want to log out until the user does so explicitly
    },

    storeToken,

    getStoredToken,

    onError: (error) => {
      if (ready) {/* Prompt user to log in */}
    },
  });
  try {
    await fetchWrapper.getToken();
  } catch {
    // Ignore we expect this to error the first time
  }
  ready = true;
  return fetchWrapper;
};

let _fetchWrapper: OAuth2Fetch | null = null;

// We defer initialisation until the first call to fetchAuthenticated to ensure that initialisation has been completed
const getFetchWrapper = async () => {
  if (!_fetchWrapper) {
    _fetchWrapper = await initialiseFetchWrapper();
  }

  return _fetchWrapper;
};

Allow to add extraParams to token request and refresh token request

I am using OAuth public client with authorozation code flow, refresh token and resource imdicators (RFC 8707).
The mentioned RFC adds the resource parameter to the authorization, token and refresh token requests. Unfortunatelly, it is not possible to pass the resource parameter to the token request and refresh token request.

The mentioned resource parameter can be a single string or an array of strings which brings additional issue. extraParams of type Record cannot hold multiple items with the same key. Moreover, serialization to query string using UrlSearchParams does not convert a Record with a property of type array os strings correctly
An example of such a Record is { resource: [ "r1", "r2"]} which should be serialized to "resource=r1&resource=r2".

Token Storage

How does this library store tokens? I am planning to use this in a browser setting (with authorization_code) grant. Does this library store tokens automatically? I'm also trying to understand whether to store using HTTP-only cookies or localStorage. How would a setup with HTTP-only cookies work with this library.

Thanks!

provide .well-known endpoint for oidc

For openid-connect, the spec specifies that the endpoint should be /.well-known/openid-configuration

This conflicts with the currently default path, which seems to suit OAuth2 well though.

I suggest adding a flag to choose between OAuth2 only or OIDC.

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.