Problem about POST https://api.eu.nylas.com/v3/connect/token request

@ram @Emmad_Altaf I used another oauth request library then it worked. normal guzzlehttp didnt work somehow dunno why. but "league/oauth2-client worked fine

$provider = new GenericProvider([
‘clientId’ => env(‘NYLAS_V3_CLIENT_ID’), // The client ID assigned to you by the provider
‘clientSecret’ => env(‘NYLAS_V3_API_KEY’), // The client password assigned to you by the provider
‘redirectUri’ => base_url(env(‘NYLAS_V3_CALLBACK’)),
‘urlAuthorize’ => self::NYLAS_ENDPOINT.‘/connect/auth/’,
‘urlAccessToken’ =>self::NYLAS_ENDPOINT.‘/connect/token/’,
‘urlResourceOwnerDetails’ =>self::NYLAS_ENDPOINT.‘/connect/token/’,
‘prompt’ => ‘select_provider’,
‘login_hint’ => session(‘nylas_email’)

    ]);


    $authorizationUrl = $provider->getAuthorizationUrl();

    $_SESSION['oauth2state'] = $provider->getState();
    $_SESSION['oauth2pkceCode'] = $provider->getPkceCode();
    header('Location: ' . $authorizationUrl);

and to get token in callback

$provider = new GenericProvider([
‘clientId’ => env(‘NYLAS_V3_CLIENT_ID’), // The client ID assigned to you by the provider
‘clientSecret’ => env(‘NYLAS_V3_API_KEY’), // The client password assigned to you by the provider
‘redirectUri’ => base_url(env(‘NYLAS_V3_CALLBACK’)),
‘urlAuthorize’ => self::NYLAS_ENDPOINT.‘/connect/auth/’,
‘urlAccessToken’ =>self::NYLAS_ENDPOINT.‘/connect/token/’,
‘urlResourceOwnerDetails’ =>self::NYLAS_ENDPOINT.‘/connect/token/’,

            ]);
            // Restore the PKCE code stored in the session.
            $provider->setPkceCode($_SESSION['oauth2pkceCode']);

            // Try to get an access token using the authorization code grant.
            $accessToken = $provider->getAccessToken('authorization_code', [
                'code' => $_GET['code']
            ]);

            $grantId =$accessToken->getValues();

Hi @efeengin , I could not uderstand your reply. can you please check my above code and suggest me what i have to do for this to work with PKCE?

@Emmad_Altaf as a general practice, request to not share any secret keys or values returned. You can mentioned them as API_KEY or CLIENT_ID just to ensure these values are not publicly available.

For now, I edited your post to remove sensitive values.

Hi @ram and @efeengin ,
Any help about this issue please ?
I am sharing my code to generate code verifier and code challenge, check its ok or there is any mistake in my code:

public async Task EmailandCalendarLogin(NylasAccountsDTO NylasAccount)
{
var response = new Response();

        try
        {
            //var baseUrl = await GetNylasAuthUrl();
            var baseUrl = "https://api.eu.nylas.com/";
            var callbackUrl = string.Empty;
            var configurations = new List<AppConfiguration>();
            configurations = await _uow.GetAppConfiguration("Client_Id,IndividualAccount_CallbackUrl,MasterAccount_CallbackUrl");
            var individualAccountCallbackUrl = configurations.GetConfiguration<string>("IndividualAccount_CallbackUrl").ToSafeString();
            var masterAccountCallbackUrl = configurations.GetConfiguration<string>("MasterAccount_CallbackUrl").ToSafeString();
            callbackUrl = NylasAccount.AccountType == 1 ? masterAccountCallbackUrl : individualAccountCallbackUrl;

            var clientId = configurations.GetConfiguration<string>("Client_Id").ToSafeString();

            var url = $"{baseUrl}v3/connect/auth";
            var response_type = "code";
            var access_type = "online";
            var login_hint = NylasAccount.EmailAddress;
            var redirect_uri = callbackUrl;

            var providerData = await DetectProvider(NylasAccount);
            var provider = providerData.ResultData.ToSafeString();

            // Generate code verifier and challenge
            var codeVerifier = GenerateCodeVerifier();
            var codeChallenge = GenerateCodeChallenge(codeVerifier);
            var codeChallengeMethod = "S256";

            string sql = string.Empty;

            List<SqlParameter> prams = new List<SqlParameter>
                {
                    new SqlParameter("@UserId", _stateManagmentService.UserId),
                    new SqlParameter("@AccountType", NylasAccount.AccountType)

                };

            var nylasConfiguredAccountDetail = (await _uow.ExecuteReaderSingleDS<NylasConfiguredAccountDetail>("JT7_GetNylasConfiguredAccountDetail", prams.ToArray())).FirstOrDefault() ?? new NylasConfiguredAccountDetail();

            if (nylasConfiguredAccountDetail.EmailAddress == NylasAccount.EmailAddress && nylasConfiguredAccountDetail.AccessToken != string.Empty)
            {
                prams = new List<SqlParameter>
                {
                    new SqlParameter("@Signature", NylasAccount.Signature.ToSafeString()),
                    new SqlParameter("@UserId", _stateManagmentService.UserId),
                    new SqlParameter("@Id", NylasAccount.Id),
                    new SqlParameter("@AccountType", NylasAccount.AccountType)
                };
                await _uow.ExecuteNonQuery<int>("JT7_UpdateNylasSignature", prams.ToArray());
                response.ResultData = 1;
            }
            else
            {
                if (nylasConfiguredAccountDetail != null)
                {
                    prams = new List<SqlParameter>
                     {
                       new SqlParameter("@UserId", _stateManagmentService.UserId),
                       new SqlParameter("@AccountType", NylasAccount.AccountType)
                      };
                    await _uow.ExecuteNonQuery<int>("JT7_UnlinkNylasAccount", prams.ToArray());
                }

                prams = new List<SqlParameter>
                             {
                                 new SqlParameter("@UserId",NylasAccount.AccountType == 1 ? 0 : _stateManagmentService.UserId),
                                 new SqlParameter("@Signature", NylasAccount.Signature.ToSafeString()),
                                 new SqlParameter("@EmailAddress", string.Empty),
                                 new SqlParameter("@AccountType", NylasAccount.AccountType == 1 ? NylasAccount.AccountType : 2),
                             };
                var output = await _uow.ExecuteScalar<int>("JT7_NylasClientConfigurations_Insert", prams.ToArray());
                var nylasHostUrl = $"{url}?client_id={clientId}&redirect_uri={redirect_uri}&response_type={response_type}&access_type={access_type}&provider={provider}&login_hint={login_hint}&code_challenge={codeChallenge}&code_challenge_method={codeChallengeMethod}";
                //var nylasHostUrl = $"{url}?client_id={clientId}&redirect_uri={redirect_uri}&response_type={response_type}&access_type={access_type}&provider={provider}&login_hint={login_hint}";
                _loggingService.AddErrorLog("nylasHostUrl: " + nylasHostUrl);
                response.ResultData = nylasHostUrl;
            }

            response.Status = Status.Success;
        }
        catch (Exception ex)
        {
            response.Status = Status.Failure;
            response.Message = _loggingService.AddErrorLog(ex);
        }
        return response;
    }

    private string GenerateCodeVerifier()
    {
        var bytes = new byte[32];
        RandomNumberGenerator.Fill(bytes);
        var codeVerifier = Base64UrlEncode(bytes);

        _httpContext.Session.SetString("code_verifier", codeVerifier);

        return codeVerifier;
    }

    private string GenerateCodeChallenge(string codeVerifier)
    {
        using (var sha256 = SHA256.Create())
        {
            var bytes = Encoding.ASCII.GetBytes(codeVerifier);
            var hash = sha256.ComputeHash(bytes);
            return Base64UrlEncode(hash);
        }
    }

    private string Base64UrlEncode(byte[] bytes)
    {
        var base64 = Convert.ToBase64String(bytes)
            .Replace('+', '-')
            .Replace('/', '_')
            .TrimEnd('=');
        return base64;
    }

Hi @efeengin @ram ,
Again same error

Hi @ram @Blag,
This issue is still not resolved. Please check and reply
Thanks

Hi @Emmad_Altaf let me review your comments again and see if we can find a way forward. Take a look.

1 Like

@Emmad_Altaf two asks:

  1. Can you share the contents of the Callback URIs page:

  1. when you are testing with postman on the /v3/connect/token endpoint, can you try the following parameters and let me know if it works:
{
  "code": "CODE",
  "client_id": "CLIENT_ID",
  "client_secret": "CLIENT_SECRET",
  "redirect_uri": "REDIRECT_URI",
  "grant_type": "authorization_code"
}

Hi @ram ,
If I try without the code verifier parameter in Postman, it works fine. But as I mentioned many times, I am facing an issue with the PKCE flow. You can check my detailed comment above. When I am passing the code verifier in my application and also in Postman, it’s generating the same error: “Code verifier challenge failed.”
I have highlighted this issue many times here and also emailed the support team, stating that it is an API issue and the API is broken. The Nylas team should resolve it quickly as it is breaking the whole flow. However, I do not understand why the Nylas team is not checking and addressing this error after so many days. Kindly do not simply ignore this error. I assure you that it is an API issue when using the code verifier.

For issue details, please read the entire conversation above, where I have explained it in detail.

@Emmad_Altaf - thanks for sharing, for reference, I have used the PKCE flow here: https://pomcal.com/ and it works as expected, so I do not see a clear API issue at the moment. I am trying to figure out if there is a way I can reproduce the error. It’s not clear to me, but I will double check your messages.

However, to clarify, why is PKCE required on the backend, is this a requirement or a library that is implementing PKCE? You mentioned previously that PKCE is not a requirement.

@Emmad_Altaf - Sorry I cannot be of more help.

I suggest to follow our documentation no implementing PKCE, and to clarify which step you are not able to complete given the code samples that we have available.

Let me know which specific code sample and step does not work for you and I can try to reproduce. I cannot clearly follow the past messages, sorry, but need reproduction steps to follow (especially the code where it does not work).

As an example, something like the following will be helpful:

Documentation followed: Create grants with OAuth and an access token | Nylas Docs

Step 1) Working: Make a connect request with a code verifier:

Code Used:

import 'dotenv/config'
import express from 'express'
import Nylas from 'nylas'

// Nylas configuration
const config = {
  clientId: process.env.NYLAS_CLIENT_ID,
  redirectUri: "http://localhost:3000/oauth/exchange",
  apiKey: process.env.NYLAS_API_KEY,
  apiUri: process.env.SERVER_URL,
}

const config = { 
  apiKey: config.apiKey, 
  apiUri: config.apiUri, 
}

const nylas = new Nylas(config)

const app = express()
const port = 3000

// Route to start the OAuth flow
app.get('/nylas/auth', (req, res) => {
  const authData = nylas.auth.urlForOAuth2PKCE({
    clientId: config.clientId,
    provider: 'google',
    redirectUri: config.redirectUri,
    loginHint: 'enter-email-address-here',
  })

  res.redirect(authData.url)
})   

…more steps…

Step 3) [Not Working] Exchange authorization code for access token

Code Used:

app.get('/oauth/exchange', async (req, res) => {
  console.log(res.status)

  const code = req.query.code

  if (!code) {
    res.status(400).send('No authorization code returned from Nylas')
    return
  }

  try {
    const response = await nylas.auth.exchangeCodeForToken({ 
      clientId: config.clientId,
      redirectUri: config.redirectUri,
      codeVerifier: 'insert-code-challenge-secret-hash',
      code
    })

    const { grantId } = response

    res.status(200)
  } catch (error) {
    console.error('Error exchanging code for token:', error)

    res.status(500).send('Failed to exchange authorization code for token')
  }
})

Output / Logs:

Logs showing the error and additional logs

Hi @ram ,
Can you please test PKCE flow on postman and share screenshot here ?
it will show error to you then you will understand the issue for which i am asking.

@Emmad_Altaf - let me consider this next, will keep you posted!

1 Like

@Emmad_Altaf

  1. Here is a useful blogpost to follow: OAuth 2.0: Implicit Flow is Dead, Try PKCE Instead | Postman Blog
  2. Reference Postman call: Postman
  3. Here is the screen shot:

This seems more related to configuration, as the API works.

1 Like

@ram The nylas-scheduler-editor component auth returns a code to my redirect-url. Using the nylas ruby SDK, I attempt to exchange the code for a tocken with

response = nylas.auth.exchange_code_for_token({
client_id: ENV[‘NYLAS_V3_CLIENT_ID’],
redirect_uri: ‘http://localhost:3000/nylas_oauth/nylas_oauth_callback’,
code: code
})

I get the error that there is no code verifier present. How is it that the nylas-scheduler-editer component is giving me a code that expects PKCE flow when it doesn’t even return me the information I need to do so?

Hi @ram ,
I have requested for Postman screenshot with PKCE flow.

Hello everyone, can you ensure that the values you get at every step is matching the following:

Plain text code verifier: nylas

SHA256 of code verifier (must be lowercase): e96bf6686a3c3510e9e927db7069cb1cba9b99b022f49483a6ce3270809e68a2

Base64 encoding of the SHA256 hash (without padding): ZTk2YmY2Njg2YTNjMzUxMGU5ZTkyN2RiNzA2OWNiMWNiYTliOTliMDIyZjQ5NDgzYTZjZTMyNzA4MDllNjhhMg

This last value is what you need to pass to the code_challenge query parameter in the /connect/auth API call.

And the code verifier value in the /connect/token API call should be nylas.

1 Like