CORS issue trying to get authorization code with typescript

Hello,

We are developing an SDK with typescript that utilizes the Genesys SDK.

When trying to test the connect function of our SDK on the front-end application (triggered by a button click), the call to get the auth code for use with the loginCodeAuthorizationGrant method returns the following error instead of redirecting to the login page:

Our connect function tries to get the auth code before authenticating and continuing on to other initialization steps such as the following:

this.getAuthCode(window.location.href).then((data) => {
                // extract auth code
                return this._authClient.loginCodeAuthorizationGrant(this._clientId, this._clientSecret, "authcode", window.location.href);
            }).then((authData) => {
                // get current user details
                return this._usersApi.getUsersMe();
            })...

Our getAuthCode method looks like the following:

private getAuthCode(redirectUri: string): Promise<any> {
    return fetch('https://login.mypurecloud.com/oauth/authorize?client_id=' + this._clientId + '&response_type=code&redirect_uri=' + encodeURI(redirectUri))
        .then((response) => {
            if (response.status >= 200 && response.status < 300) {
                return response.json();
            } else {
                throw response;
            }
        });
}

How can we successfully get the auth code?

Auth code grants may not be used by frontend-only applications. Please use the implicit grant: https://developer.genesys.cloud/api/rest/authorization/use-implicit-grant. (loginImplicitGrant in the SDK)

The reason for this is that auth code grants require handling the client secret to exchange the auth code for an auth token. A frontend web app is not capable of handling a client secret securely; using it in any way in the frontend app will directly expose the secret to unauthenticated users. I've you've deployed your app already, please be sure to regenerate the client secret as your code above will have leaked the secret already. Code grants are only for use in an architecture where a backend service is making the request; the frontend gets the code and sends it to the backend where the client secret can be handled securely.

We are still seeing issues now that we've switched to the implicit grant

When the button is initially pressed it seems the login succeeds but then the rest of the chained promises do not execute. When the button is pressed again, we get a 401 Unauthorized response:

Can you share your code that gets executed when the button is pressed? The 401 error only tells me that you haven't authorized the SDK prior to making the request.

public connect(): Promise<void> {
    return new Promise((resolve, reject) => {
        this._authClient.loginImplicitGrant(this._clientId, window.location.href).then((authData) => {
            // get current user details
            return this._usersApi.getUsersMe();
        }).then((currUser) => {
            this._genesysUserId = currUser.id;
            // create notification channel
            return this._notificationsApi.postNotificationsChannels();
        }).then((notificationChannel) => {
            // setup websocket
            let websocket: WebSocket = new WebSocket(notificationChannel.connectUri);
            websocket.onmessage = this.processWebSocketMessage;

            // subscribe to user's active chats
            let topic = [{"id": "v2.users." + this._genesysUserId + ".conversations.chats"}];
            return this._notificationsApi.postNotificationsChannelSubscriptions(notificationChannel.id, topic);
        }).then(() => {
            resolve();
        }).catch((err) => {
            reject(err);
        });
    });
}

I don't have the full picture of what your app is doing, but here's what I would expect based on that code:

  1. The first time the connect() function is run, this._authClient.loginImplicitGrant will cause a navigation event in the browser redirecting the user to the Genesys Cloud login screen where they will authenticate. The user will be directed back to your app where the access token will be passed back in the URL.
  2. After being redirected back, when connect() is called again, this._authClient.loginImplicitGrant will harvest the auth token from the URL and store it in the ApiClient instance to use when making requests.
  3. The then function that calls getUsersMe will be invoked and the rest of the promise chain will continue.

If you're getting the 401 from step 3, I'd guess that this._authClient is a different instance of ApiClient than was used during construction of this._usersApi. It is possible to construct multiple instances of ApiClient (to support multi-tennanted applications). You shouldn't be doing that in a frontend application though; use ApiClient.instance to access the static/default instance that's used by all API classes when you don't specify one in the constructor.

Also, you probably want to enable using local storage to store the token to remember the token and cut down on redirects when you refresh the page. https://developer.genesys.cloud/api/rest/client-libraries/javascript/#access-token-persistence

Thank you, the ApiClient.instance resolved the 401 issue

However, we want the full chain of promises to execute on the first button click; is there a way to create a popup for the login instead of entirely redirecting and reloading the original page?

That's not possible because the user must be redirected away from your application to complete the oauth process. You likely don't actually see it happen since the auth process redirects back to your app with an access token instantly when you're already authenticated, but if you watch the network console, you'll see the redirects to login.mypurecloud.com (or whatever region) and back to your app.

Using the token persistence mentioned above will make this a little smoother because it will save the token to local storage so it can initialize and validate using that token instead of doing a redirect to get a new one every time.

This topic was automatically closed 31 days after the last reply. New replies are no longer allowed.