# Creating grants with Hosted Authentication and an access token

Source: https://developer.nylas.com/docs/v3/auth/hosted-oauth-accesstoken/

Nylas supports Hosted OAuth to get the user's authorization for scopes and create their grant, and PKCE for extra security during the process.

If you're developing a single page application (SPA) or a mobile app, we recommend you use [Hosted OAuth with PKCE](#secure-the-authentication-process-with-pkce). This extra layer of security adds a key to the authentication exchange that you can safely store on a mobile device, instead of including the API key. This is optional for projects that have a backend, but it's a good security practice to implement anyway.

## How Hosted OAuth works


> **Info:** 
> 🔍 **Nylas creates only one grant per email address in each application**. If a user authenticates with your Nylas application using the email address associated with an existing grant, Nylas re-authenticates the grant instead of creating a new one.

1. The user clicks a link or button in your project to [start an authorization request](#start-an-authorization-request).
2. Nylas forwards the user to their provider where they complete the authorization flow.
3. [The provider directs the user to the Nylas callback URI](#accept-authorization-response) and includes URL parameters that indicate whether the authorization succeeded or failed, along with other information.
4. If the authorization succeeded, Nylas creates an unverified grant record.
5. Nylas forwards the user to your project's callback URI and includes the access `code` from the provider as a URL parameter.
6. Your project uses the `code` to [perform a token exchange with the provider](#exchange-code-for-access-token).
7. When the token exchange completes successfully, Nylas marks the grant record as verified and creates a grant ID for the user.

## Start an authorization request

The first step of the authentication process is to start an authorization request. Usually this is a button or link that the user clicks.

Your project redirects the user to the [authorization request endpoint](/docs/reference/api/authentication-apis/get_oauth2_flow/) and includes their information as a set of query parameters, as in the example below. When the user goes to this URL, Nylas starts a secure authentication session and redirects them to their provider's website.

```bash
/v3/connect/auth?
  client_id=<NYLAS_CLIENT_ID>
  &redirect_uri=https://myapp.com/callback-handler  # Your application's callback URI.
  &response_type=code
  &access_type=offline                              # Specifies to generate a refresh token for the user.
  &provider=google                                  # (Optional) The connector Nylas will use to authenticate the user.
  &state=<STATE>
  &scope=<SCOPES>                                   # (Optional) A list of scopes to request from the user.
```

```js [authorization-Node.js SDK]

import Nylas from "nylas";

const nylas = new Nylas({
  apiKey: "<NYLAS_API_KEY>",
  apiUri: "<NYLAS_API_URI>",
});

// Build the URL to start the OAuth 2.0 hosted-authentication flow.
// Redirect the user to this URL; Nylas calls back to `redirectUri` with an
// authorization code that you exchange for a grant via
// `nylas.auth.exchangeCodeForToken()`.
const authUrl = nylas.auth.urlForOAuth2({
  clientId: "<NYLAS_CLIENT_ID>",
  provider: "google",
  redirectUri: "http://localhost:3000/oauth/exchange",
  loginHint: "email_to_connect@example.com",
  accessType: "offline",
});

console.log(authUrl);


```

```python [authorization-Python SDK]

from functools import wraps
from flask import Flask, request, redirect
from nylas import Client

nylas = Client(
    "<NYLAS_CLIENT_ID>",
    "<NYLAS_API_URI>"
)

REDIRECT_CLIENT_URI = 'http://localhost:9000/oauth/exchange'

flask_app = Flask(__name__)

@flask_app.route("/nylas/generate-auth-url", methods=["GET"])

def build_auth_url():
  auth_url = nylas.auth.url_for_oauth2(
      config={
        "client_id": "<NYLAS_CLIENT_ID>",
        "provider": 'google',
        "redirect_uri": REDIRECT_CLIENT_URI,
        "login_hint": "enter-email-address-here",
        "access_type": "offline",
      }
  )
  return redirect(auth_url)

```

```rb [authorization-Ruby SDK]

# frozen_string_literal: true

require 'nylas'
require 'sinatra'

set :show_exceptions, :after_handler

error 404 do
  'No authorization code returned from Nylas'
end

error 500 do
  'Failed to exchange authorization code for token'
end

nylas = Nylas::Client.new(
  api_key: "<NYLAS_API_KEY>"
)

get '/nylas/auth' do
  config = {
    client_id: "<NYLAS_CLIENT_ID>",
    provider: 'google',
    redirect_uri: 'http://localhost:4567/oauth/exchange',
    login_hint: '<email_to_connect>',
    accessType: 'offline',
  }

  url = nylas.auth.url_for_oauth2(config)
  redirect url
end

```

```kt [authorization-Kotlin SDK]

import com.nylas.NylasClient
import com.nylas.models.AccessType
import com.nylas.models.AuthProvider
import com.nylas.models.Prompt
import com.nylas.models.UrlForAuthenticationConfig
import spark.Spark.get

fun main(args: Array<String>) {
  val nylas: NylasClient = NylasClient(
      apiKey = "<NYLAS_API_KEY>"
  )

  get("/nylas/auth") { _, response ->
    val scope = listOf("https://www.googleapis.com/auth/userinfo.email")

    val config = UrlForAuthenticationConfig.Builder(
        "<NYLAS_CLIENT_ID>",
        "http://localhost:4567/oauth/exchange")
        .accessType(AccessType.OFFLINE)
        .provider(AuthProvider.GOOGLE)
        .prompt(Prompt.DETECT)
        .scope(scope)
        .includeGrantScopes(true)
        .state("sQ6vFQN")
        .loginHint("<email_to_connect>")
        .build()

    val url = nylas.auth().urlForOAuth2(config)

    response.redirect(url)
    null
  }
}

```

```java [authorization-Java SDK]

import java.util.*;
import static spark.Spark.*;
import com.nylas.NylasClient;
import com.nylas.models.*;

public class AuthRequest {
  public static void main(String[] args) throws NylasSdkTimeoutError, NylasApiError {
    NylasClient nylas = new NylasClient.Builder("<NYLAS_API_KEY>").build();

    get("/nylas/auth", (request, response) -> {
      List<String> scope = new ArrayList<>();
      scope.add("https://www.googleapis.com/auth/userinfo.email");

      UrlForAuthenticationConfig config = new UrlForAuthenticationConfig.Builder(
          "<NYLAS_CLIENT_ID>",
          "http://localhost:4567/oauth/exchange")
          .accessType(AccessType.OFFLINE)
          .provider(AuthProvider.GOOGLE)
          .prompt(Prompt.DETECT)
          .scope(scope)
          .includeGrantScopes(true)
          .state("sQ6vFQN")
          .loginHint("<email_to_connect>")
          .build();

      String url = nylas.auth().urlForOAuth2(config);

      response.redirect(url);
      
      return null;
    });
  }
}

```

Each provider displays their authorization consent and approval steps differently. The steps are visible only to the user.

> **Info:** 
> **If a user authenticates using their Google account, they might be directed to Google's authorization page twice**. This is a normal part of the Hosted OAuth flow, and it ensures that the user approves all necessary scopes.

### Use `access_type` to request refresh tokens

You can use the `access_type` parameter in your [authorization request](#start-an-authorization-request) to indicate whether you want Nylas to return a refresh token for the grant.

If you're developing a mobile or client-side-only app, we recommend you use `access_type=online`. This prevents the OAuth process from creating a refresh token. When you use this method, your users need to re-authenticate manually when their access token expires.

Otherwise, you can use `access_type=offline` to get a refresh token when a user authenticates. You can use the refresh token to request a new access token for the user when their old one expires, without prompting them to re-authenticate. For more on what happens when grants expire and how to recover them, see [Handling expired grants](/docs/dev-guide/best-practices/grant-lifecycle/).

> **Error:** 
> **For security reasons, we strongly recommend against using `access_type=offline` for mobile and client-side-only apps**. In these cases it's best for your users to manually re-authenticate when their access tokens expire.

### Pass user information in `state` parameter

Nylas Hosted OAuth supports the optional `state` parameter. If you include it in an authorization request, Nylas returns the unmodified value to your project. You can use this as a verification check, or to track information about the user that you need when creating a grant or logging them in.

For more information about the `state` parameter, see the [OAuth 2.0 specification](https://datatracker.ietf.org/doc/html/rfc6749) and the [official OAuth 2.0 documentation](https://www.oauth.com/oauth2-servers/authorization/the-authorization-request/).

## Accept authorization response

After the user completes the authorization process, their provider sends them to Nylas' redirect URI (`https://api.us.nylas.com/v3/connect/callback`) and includes URL parameters with information about the user. Nylas uses the information in the parameters to find your application using its client ID and, if the authentication succeeded, create an unverified grant record for the user.

Nylas then uses your application's callback URI to direct the user back to your project, along with the `code` it received from the provider.

```bash
https://myapp.com/callback-handler?code=<CODE>
```

If you specified a `state` in the initial authorization request, Nylas includes it as a URL parameter.

## Exchange `code` for access token

> **Warn:** 
> **The OAuth `code` is a unique, one-time-use credential**. This means that if your [`POST /v3/connect/token` request](/docs/reference/api/authentication-apis/exchange_oauth2_token/) fails, you'll need to restart the OAuth flow to generate a new `code`. If you try to pass the original `code` in another token exchange request, Nylas returns an error.

Make a [`POST /v3/connect/token` request](/docs/reference/api/authentication-apis/exchange_oauth2_token/) to exchange the user's `code` for an access token. Nylas returns an access token and other information about the user.

```bash {9} [tokenExchange-Request]
POST /token HTTP/1.1
Host: /v3/connect/token
Content-Type: application/json

{
  "client_id": "<NYLAS_CLIENT_ID>",
  "client_secret": "<NYLAS_API_KEY>",
  "grant_type": "authorization_code"
  "code": "<CODE>",
  "redirect_uri": "<CALLBACK_URI>",
}
```

```js [tokenExchange-Node.js SDK]

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 codeExchangeResponse = nylas.auth.exchangeCodeForToken({
      redirectUri: "REDIRECT_URI",
      clientId: "CLIENT_ID",
      clientSecret: "API_KEY",
      code: "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");
  }
});


```

```python [tokenExchange-Python SDK]

import json
from functools import wraps
from io import BytesIO
from flask import Flask
from nylas import Client

nylas = Client(
    "<NYLAS_CLIENT_ID>",
    "<NYLAS_API_URI>"
)

REDIRECT_CLIENT_URI = 'http://localhost:9000/oauth/exchange'

@flask_app.route("/oauth/exchange", methods=["GET"])

def exchange_code_for_token():
  code_exchange_response = nylas.auth.exchange_code_for_token(
      request={
        "code": request.args.get('code'),
        "client_id": "<NYLAS_CLIENT_ID>",
        "redirect_uri": REDIRECT_CLIENT_URI
      }
  )

  return {
    'email_address': code_exchange_response.email,
    'grant_id': code_exchange_response.grant_id
  }

```

```js [tokenExchange-Ruby SDK]

# frozen_string_literal: true

require 'nylas'
require 'sinatra'

nylas = Nylas::Client.new(
  api_key: "<NYLAS_API_KEY>"
)

set :show_exceptions, :after_handler

# Receive the authorization code from Nylas and exchange it for a grant.
get '/oauth/exchange' do
  code = params[:code]
  status 404 if code.nil?

  begin
    response = nylas.auth.exchange_code_for_token({
      client_id: "<NYLAS_CLIENT_ID>",
      redirect_uri: 'http://localhost:4567/oauth/exchange',
      code: code
    })
  rescue StandardError
    status 500
  else
    grant_id = response[:grant_id]
    email = response[:email]

    "Grant_Id: #{grant_id} \n Email: #{email}"
  end
end


```

```kt [tokenExchange-Kotlin SDK]

import com.nylas.NylasClient
import com.nylas.models.CodeExchangeRequest
import spark.Spark.get

fun main() {
  val nylas = NylasClient.Builder("<NYLAS_API_KEY>").build()

  get("/oauth/exchange") { request, response ->
    val code = request.queryParams("code")
    if (code.isNullOrEmpty()) {
      response.status(401)
      return@get "No authorization code returned from Nylas"
    }

    val codeRequest = CodeExchangeRequest.Builder(
        "http://localhost:4567/oauth/exchange",
        code,
        "<NYLAS_CLIENT_ID>")
        .build()

    try {
      val codeResponse = nylas.auth().exchangeCodeForToken(codeRequest)
      "Grant ID: ${codeResponse.grantId}"
    } catch (e: Exception) {
      response.status(500)
      "Failed to exchange authorization code for token: $e"
    }
  }
}


```

```java [tokenExchange-Java SDK]

import static spark.Spark.get;

import com.nylas.NylasClient;
import com.nylas.models.CodeExchangeRequest;
import com.nylas.models.CodeExchangeResponse;

public class TokenExchange {
  public static void main(String[] args) {
    NylasClient nylas = new NylasClient.Builder("<NYLAS_API_KEY>").build();

    get("/oauth/exchange", (request, response) -> {
      String code = request.queryParams("code");
      if (code == null) {
        response.status(401);
        return "No authorization code returned from Nylas";
      }

      CodeExchangeRequest codeRequest = new CodeExchangeRequest.Builder(
          "http://localhost:4567/oauth/exchange",
          code,
          "<NYLAS_CLIENT_ID>")
          .build();

      try {
        CodeExchangeResponse codeResponse = nylas.auth().exchangeCodeForToken(codeRequest);
        return "Grant ID: " + codeResponse.getGrantId();
      } catch (Exception e) {
        response.status(500);
        return "Failed to exchange authorization code for token: " + e;
      }
    });
  }
}


```

The user's provider responds with an access token, a refresh token (because you set `access_type=offline` in your [authorization request](#start-an-authorization-request)), and some other information.

```json
{
  "access_token": "<ACCESS_TOKEN>",
  "refresh_token": "<REFRESH_TOKEN>",
  "scope": "<SCOPES>",
  "token_type": "Bearer",
  "id_token": "<ID_TOKEN>",
  "grant_id": "<NYLAS_GRANT_ID>"
}
```

> **Info:** 
> **OAuth 2.0 access tokens expire after one hour**. When the access token expires, you can either use the _refresh token_ to get a new _access token_, or the user can re-authenticate their grant to access your project.

Nylas marks the user's grant as verified and sends you their grant ID and email address.

Your project should store the user's grant ID, access token, and refresh token (for later re-authentication).

## Verify your setup

After you complete the OAuth flow and receive an access token, verify that your authentication is working before building it into production code. The commands below use the [Nylas CLI](https://cli.nylas.com/) — install it first if you haven't already.

**List all connected grants to confirm the authentication succeeded** with [`nylas auth list`](https://cli.nylas.com/docs/commands/auth-list):

```bash
nylas auth list
```

**Test a simple API call with your access token** using [`nylas email list`](https://cli.nylas.com/docs/commands/email-list):

```bash
nylas email list --limit 1
```

If both commands succeed, your access token is valid and your grant is ready to use. If you see errors, double-check that:
- Your access token hasn't expired (tokens expire after one hour)
- You've stored the grant ID correctly
- The user completed the authorization process successfully
- For PKCE flows, verify your `code_verifier` matches the original `code_challenge`

## Secure the authentication process with PKCE

The OAuth PKCE (Proof Key for Code Exchange) flow improves security for client-side-only applications, such as browser-based or mobile apps that don’t have a backend server. Even if your project _does_ have a backend server, we recommend you use PKCE for extra security.

> **Warn:** 
> **Never store application-wide credentials like API keys in mobile or client-side code**. You should complete the [`code` exchange flow](#exchange-code-for-access-token) without using your Nylas application's API key. If you're using PKCE, you can set `platform` to `android`, `desktop`, `ios`, or `js` when you [create a callback URI](/docs/reference/api/applications/add_callback_uri/) to make the `client_secret` field optional.

### Create a `code_challenge`

Before you make an authentication request using PKCE, you need to create a `code_challenge`. You'll use this when you [make an authorization request].

The following example uses `nylas` as a code verification string and sets the encoding method to `S256` for extra security.

> **Info:** 
> **If you don't set an encoding method, Nylas assumes you're using a plain text code verification string**. We strongly recommend you use SHA-256 encoding to create a more secure `code_challenge`.

1. Hash the verification string using an SHA-256 encoding tool (`SHA256("nylas")` -> `e96bf6686a3c3510e9e927db7069cb1cba9b99b022f49483a6ce3270809e68a2`).
2. Convert the hashed string to Base64 encoding and remove any padding (`e96bf6686a3c3510e9e927db7069cb1cba9b99b022f49483a6ce3270809e68a2` -> `ZTk2YmY2Njg2YTNjMzUxMGU5ZTkyN2RiNzA2OWNiMWNiYTliOTliMDIyZjQ5NDgzYTZjZTMyNzA4MDllNjhhMg`).
3. Save the resulting encoded string to use as the `code_challenge` in your [authorization request].

### Make authorization request with `code_challenge`

Make a [`GET /v3/connect/auth` request](/docs/reference/api/authentication-apis/get_oauth2_flow/) to create a URL that redirects your user to the authorization flow.

```bash {8-9} [connectRequest-Request]
/connect/auth?
  client_id=<NYLAS_CLIENT_ID>
  &redirect_uri=https://myapp.com/callback-handler
  &response_type=code
  &provider=<PROVIDEr>
  &scope=<SCOPES>
  &state=<STATE>
  &code_challenge=ZTk2YmY2Njg2YTNjMzUxMGU5ZTkyN2RiNzA2OWNiMWNiYTliOTliMDIyZjQ5NDgzYTZjZTMyNzA4MDllNjhhMg
  &code_challenge_method=S256
```

```js [connectRequest-Node.js SDK]

import express from "express";
import Nylas from "nylas";

// Nylas configuration
const config = {
  clientId: "<NYLAS_CLIENT_ID>",
  redirectUri: "http://localhost:3000/oauth/exchange",
  apiKey: "<NYLAS_API_KEY>",
  apiUri: "<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);
});


```

```python [connectRequest-Python SDK]

import json
from functools import wraps

from io import BytesIO
from flask import Flask, redirect
from flask_cors import CORS

from nylas import Client

nylas = Client(
    "<NYLAS_CLIENT_ID>",
    "<NYLAS_API_URI>"
)

REDIRECT_CLIENT_URI = 'http://localhost:9000/oauth/exchange'

flask_app = Flask(__name__)

@flask_app.route("/nylas/generate-auth-url", methods=["GET"])
def build_auth_url():
  auth_url = nylas.auth.url_for_oauth2_pkce(
      config={
        "client_id": "<NYLAS_CLIENT_ID>",
        "provider": 'google',
        "redirect_uri": REDIRECT_CLIENT_URI,
        "login_hint": 'enter-email-address-here'
      }
  )

  return redirect(auth_url)

```

```rb [connectRequest-Ruby SDK]

# frozen_string_literal: true

require 'nylas'
require 'sinatra'

set :show_exceptions, :after_handler

error 404 do
  'No authorization code returned from Nylas'
end

error 500 do
  'Failed to exchange authorization code for token'
end

nylas = Nylas::Client.new(
  api_key: "<NYLAS_API_KEY>"
)

get '/nylas/auth' do
  config = {
    client_id: "<NYLAS_CLIENT_ID>",
    provider: 'google',
    redirect_uri: 'http://localhost:4567/oauth/exchange',
    login_hint: '<email_to_connect>',
  }

  authData = nylas.auth.url_for_oauth2_pkce(config)
  redirect authData.url
end


```

```kt [connectRequest-Kotlin SDK]

import com.nylas.NylasClient
import com.nylas.models.*
import spark.Spark.get

fun main(args: Array<String>) {
  val nylas: NylasClient = NylasClient(
      apiKey = "<NYLAS_API_KEY>"
  )

  get("/nylas/auth") { _, response ->
    val scope = listOf("https://www.googleapis.com/auth/userinfo.email")

    val config = UrlForAuthenticationConfig.Builder(
        "<GCP_CLIENT_ID>",
        "http://localhost:4567/oauth/exchange")
        .accessType(AccessType.ONLINE)
        .provider(AuthProvider.GOOGLE)
        .prompt(Prompt.DETECT)
        .scope(scope)
        .includeGrantScopes(true)
        .state("sQ6vFQN")
        .loginHint("<email_to_connect>")
        .build()

    val authData = nylas.auth().urlForOAuth2PKCE(config)

    response.redirect(authData.url)
    null
  }
}

```

```java [connectRequest-Java SDK]

import java.util.*;
import static spark.Spark.*;
import com.nylas.NylasClient;
import com.nylas.models.*;

public class AuthRequest {
  public static void main(String[] args) throws NylasSdkTimeoutError, NylasApiError {
    NylasClient nylas = new NylasClient.Builder("<NYLAS_API_KEY>").build();

    get("/nylas/auth", (request, response) -> {
      List<String> scope = new ArrayList<>();
      scope.add("https://www.googleapis.com/auth/userinfo.email");

      UrlForAuthenticationConfig config = new UrlForAuthenticationConfig.Builder(
          "<GCP_CLIENT_ID>",
          "http://localhost:4567/oauth/exchange")
          .accessType(AccessType.ONLINE)
          .provider(AuthProvider.GOOGLE)
          .prompt(Prompt.DETECT)
          .scope(scope)
          .includeGrantScopes(true)
          .state("sQ6vFQN")
          .loginHint("<email_to_connect>")
          .build();

      PKCEAuthURL authData = nylas.auth().urlForOAuth2PKCE(config);

      response.redirect(authData.getUrl());
      
      return null;
    });
  }
}

```

### Exchange `code` for access token with `code_challenge`

The rest of the OAuth flow proceeds as usual: your project redirects the user to their provider where they authenticate and either accept or reject the requested scopes. The provider then sends them back to Nylas with an authorization `code`, and Nylas creates an unverified grant record. Nylas returns the user to your project with the `code`.

Next, use the `code` in a [`POST /v3/connect/token` request](/docs/reference/api/authentication-apis/exchange_oauth2_token/) to get an access token. Because you're using PKCE, you need to set the `grant_type` to `authorization_code` and include your `code_verifier`.

For readability, the example below sets `code_verifier` to the original plain text `code_challenge` value.

```bash {8,10} [exchange-Request]
POST /token HTTP/1.1
Host: /v3/connect/token
Content-Type: application/json

{
  "client_id": "<NYLAS_CLIENT_ID>",
  "redirect_uri": "<REDIRECT_URI>",
  "grant_type": "authorization_code",
  "code": "<AUTH_EXCHANGE_CODE>",
  "code_verifier": "nylas"
}
```

```js [exchange-Node.js SDK]

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 codeExchangeResponse = nylas.auth.exchangeCodeForToken({
      redirectUri: "REDIRECT_URI",
      clientId: "CLIENT_ID",
      clientSecret: "API_KEY",
      code: "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");
  }
});


```

```python [exchange-Python SDK]

import json
from functools import wraps
from io import BytesIO
from flask import Flask
from nylas import Client

nylas = Client(
    "<NYLAS_CLIENT_ID>",
    "<NYLAS_API_URI>"
)

REDIRECT_CLIENT_URI = 'http://localhost:9000/oauth/exchange'

@flask_app.route("/oauth/exchange", methods=["GET"])

def exchange_code_for_token():
  code_exchange_response = nylas.auth.exchange_code_for_token(
      request={
        "code": request.args.get('code'),
        "client_id": "<NYLAS_CLIENT_ID>",
        "redirect_uri": REDIRECT_CLIENT_URI
      }
  )

  return {
    'email_address': code_exchange_response.email,
    'grant_id': code_exchange_response.grant_id
  }

```

```ruby [exchange-Ruby SDK]

# frozen_string_literal: true

require 'nylas'
require 'sinatra'

nylas = Nylas::Client.new(
  api_key: "<NYLAS_API_KEY>"
)

set :show_exceptions, :after_handler

error 404 do
  'No authorization code returned from Nylas'
end

error 500 do
  'Failed to exchange authorization code for token'
end

# Exchange the authorization code for a grant. Pass the PKCE `code_verifier`
# you stored when you generated the auth URL.
get '/oauth/exchange' do
  code = params[:code]
  status 404 if code.nil?

  begin
    response = nylas.auth.exchange_code_for_token({
      client_id: "<NYLAS_CLIENT_ID>",
      redirect_uri: 'http://localhost:4567/oauth/exchange',
      code_verifier: '<PKCE_CODE_VERIFIER>',
      code: code
    })
  rescue StandardError
    status 500
  else
    grant_id = response[:grant_id]
    email = response[:email]

    "Grant_Id: #{grant_id} \n Email: #{email}"
  end
end


```

```kt [exchange-Kotlin SDK]

import com.nylas.NylasClient
import com.nylas.models.CodeExchangeRequest
import spark.Spark.get

fun main() {
  val nylas = NylasClient.Builder("<NYLAS_API_KEY>").build()

  get("/oauth/exchange") { request, response ->
    val code = request.queryParams("code")
    if (code.isNullOrEmpty()) {
      response.status(401)
      return@get "No authorization code returned from Nylas"
    }

    // PKCE: provide the original plaintext `code_verifier` you generated
    // for the initial authorization request.
    val codeRequest = CodeExchangeRequest.Builder(
        "http://localhost:4567/oauth/exchange",
        code,
        "<NYLAS_CLIENT_ID>")
        .codeVerifier("<PKCE_CODE_VERIFIER>")
        .build()

    try {
      val codeResponse = nylas.auth().exchangeCodeForToken(codeRequest)
      "Grant ID: ${codeResponse.grantId}"
    } catch (e: Exception) {
      response.status(500)
      "Failed to exchange authorization code for token: $e"
    }
  }
}


```

```java [exchange-Java SDK]

import static spark.Spark.get;

import com.nylas.NylasClient;
import com.nylas.models.CodeExchangeRequest;
import com.nylas.models.CodeExchangeResponse;

public class TokenExchangePkce {
  public static void main(String[] args) {
    NylasClient nylas = new NylasClient.Builder("<NYLAS_API_KEY>").build();

    get("/oauth/exchange", (request, response) -> {
      String code = request.queryParams("code");
      if (code == null) {
        response.status(401);
        return "No authorization code returned from Nylas";
      }

      // PKCE: provide the original plaintext `code_verifier` you generated
      // for the initial authorization request.
      CodeExchangeRequest codeRequest = new CodeExchangeRequest.Builder(
          "http://localhost:4567/oauth/exchange",
          code,
          "<NYLAS_CLIENT_ID>")
          .codeVerifier("<PKCE_CODE_VERIFIER>")
          .build();

      try {
        CodeExchangeResponse codeResponse = nylas.auth().exchangeCodeForToken(codeRequest);
        return "Grant ID: " + codeResponse.getGrantId();
      } catch (Exception e) {
        response.status(500);
        return "Failed to exchange authorization code for token: " + e;
      }
    });
  }
}


```

## Make API requests with access token

Now that you have a grant for your user, you can make requests on their behalf with their access token and the [`/me/` syntax](#me-syntax-for-api-calls).

> **Info:** 
> **You can't use an access token to authorize API requests that access or modify data at the application level**. Those requests require an [API key](/docs/dev-guide/dashboard/#get-your-api-key) for authorization.

To authorize an API request, pass the user's access token in the request header and substitute `me` where you'd usually specify a grant ID.

```bash {2-3}
curl --request GET \
  --url 'https://api.us.nylas.com/v3/grants/me/calendars' \
  --header 'Authorization: Bearer <ACCESS_TOKEN>' \
  --header 'Content-Type: application/json'
```

When Nylas receives a request using the `/me/` syntax, it checks the authorization header token, finds the associated grant ID, and uses that ID to locate the user's data.

## Refresh an expired access token

If you set `access_type=offline` in your [authorization request](#start-an-authorization-request), Nylas returns a refresh token along with the access token during the token exchange process. When the access token expires, you can use the refresh token to request a new one.

> **Info:** 
> **Refresh tokens don't expire unless they're revoked**. If your project is client-side-only, you shouldn't request offline access or need this step.

Make a [`POST /v3/connect/token` request](/docs/reference/api/authentication-apis/exchange_oauth2_token/) that sets the `grant_type` to `refresh_token` and includes the user's refresh token. The user's provider returns a fresh access token for their grant.

```bash {8-9} [refreshAccessToken-Request]
POST /token HTTP/1.1
Host: /v3/connect/token
Content-Type: application/json

{
  "client_id": "<NYLAS_CLIENT_ID>",
  "client_secret": "<NYLAS_API_KEY>",
  "grant_type": "refresh_token",
  "refresh_token": "<REFRESH_TOKEN>"
}
```

```json [refreshAccessToken-Response (JSON)]
{
  "access_token": "<ACCESS_TOKEN>",
  "scope": "https://www.googleapis.com/auth/gmail.readonly profile",
  "token_type": "Bearer"
}
```

```js [refreshAccessToken-Node.js SDK]

const config = {
  clientId: "<NYLAS_CLIENT_ID>",
  callbackUri: "http://localhost:3000/oauth/exchange",
  apiKey: "<NYLAS_API_KEY>",
  apiUri: "<NYLAS_API_URI>",
};

const nylas = new Nylas({
  apiKey: config.apiKey,
  apiUri: config.apiUri,
});

const refreshed = await nylas.auth.refreshAccessToken({
  clientId: config.clientId,
  refreshToken: response.refreshToken,
  redirectUri: config.redirectUri,
});


```

```python [refreshAccessToken-Python SDK]

import sys
from nylas import Client

nylas = Client(
    "<NYLAS_API_KEY>",
    "<NYLAS_API_URI>"
)

REDIRECT_CLIENT_URI = 'http://localhost:9000/oauth/exchange'

response = nylas.auth.refresh_access_token(
    request={
      "client_id": "<NYLAS_CLIENT_ID>",
      "refresh_token": '<NYLAS_REFRESH_TOKEN>',
      "redirect_uri": REDIRECT_CLIENT_URI
    }
)

```

```rb [refreshAccessToken-Ruby SDK]

# frozen_string_literal: true

require 'nylas'
require 'sinatra'

nylas = Nylas::Client.new(
  api_key: "<NYLAS_API_KEY>"
)

# Use the user's stored refresh_token to obtain a new access_token.
# The SDK sets `grant_type` to "refresh_token" automatically.
get '/nylas/refresh' do
  request = {
    client_id: "<NYLAS_CLIENT_ID>",
    refresh_token: "<NYLAS_REFRESH_TOKEN>",
    redirect_uri: "http://localhost:4567/oauth/exchange"
  }

  refreshed_token = nylas.auth.refresh_access_token(request)

  "Access token: #{refreshed_token[:access_token]}"
end


```

```kt [refreshAccessToken-Kotlin SDK]

import com.nylas.NylasClient
import com.nylas.models.TokenExchangeRequest
import spark.Spark.get

fun main() {
  val nylas = NylasClient.Builder("<NYLAS_API_KEY>").build()

  get("/nylas/refresh") { _, response ->
    val token = TokenExchangeRequest.Builder(
        "http://localhost:4567/oauth/exchange",
        "<NYLAS_REFRESH_TOKEN>",
        "<NYLAS_CLIENT_ID>")
        .build()

    try {
      val refreshed = nylas.auth().refreshAccessToken(token)
      "New access token: ${refreshed.accessToken}"
    } catch (e: Exception) {
      response.status(500)
      "Failed to refresh access token: $e"
    }
  }
}


```

```java [refreshAccessToken-Java SDK]

import static spark.Spark.get;

import com.nylas.NylasClient;
import com.nylas.models.CodeExchangeResponse;
import com.nylas.models.TokenExchangeRequest;

public class RefreshAccessToken {
  public static void main(String[] args) {
    NylasClient nylas = new NylasClient.Builder("<NYLAS_API_KEY>").build();

    get("/nylas/refresh", (request, response) -> {
      TokenExchangeRequest token = new TokenExchangeRequest.Builder(
          "http://localhost:4567/oauth/exchange",
          "<NYLAS_REFRESH_TOKEN>",
          "<NYLAS_CLIENT_ID>")
          .build();

      try {
        CodeExchangeResponse refreshed = nylas.auth().refreshAccessToken(token);
        return "New access token: " + refreshed.getAccessToken();
      } catch (Exception e) {
        response.status(500);
        return "Failed to refresh access token: " + e;
      }
    });
  }
}


```

## Handle authentication errors

If the authentication process fails, Nylas returns the standard OAuth 2.0 error fields in its response: `error`, `error_description`, and `error_uri`.

```bash
https://myapp.com/callback-handler?
  state=...               # Passed value of initial state if it was provided.
  &error=...              # Error type/constant.
  &error_description=...  # Error description.
  &error_uri=...          # Error or event code.
```

If an unexpected error occurs during the callback URI creation step at the end of the authentication flow, Nylas' response includes the `error_code` field instead of `error_uri`.

```bash {4}
https://myapp.com/callback-handler?
  &error=internal_error                                       # Error type/constant.
  &error_description=Internal+error%2C+contact+administrator  # Error description.
  &error_code=500                                             # Code of internal error.
```