Create grants with OAuth and an API key
Most projects that used Hosted authentication in Nylas v2.x should use the new OAuth method to get authorization from end users, along with your Nylas application's API key. This is the recommended authentication method, and the easiest way to integrate with Nylas.
🔍 Nylas creates only one grant per email address in each application. If an end 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.
In Nylas v3, you use the familiar Hosted OAuth flow to get the end user's authorization for scopes and create their grant ID. Then, you use your application-specific API key to access their data and make other requests. This allows you to use the same request method for everything in your application, including endpoints that don't specify a grant (for example, webhook notifications and some group calendar features).
Before you begin
Before you begin, make sure you set up all the required parts for your authentication system:
- If you haven't already, log in to the v3 Dashboard and create a Nylas application.
- Generate an API key for your application in the v3 Dashboard.
- Create a provider auth app in the provider's console or application. See the detailed instructions for creating a Google or Azure provider auth app.
- Create a connector for the provider you want to authenticate with.
- Add your project's callback URIs ("redirect URIs") in the Nylas Dashboard.
Creating grants with OAuth and API key
📝 Note: Because Nylas uses the schema outlined in RFC 9068 to ensure that it is compatible with all OAuth libraries in all languages, the format for the /tokeninfo
endpoint is different from the other OAuth endpoints.
The rough OAuth process is...
- The end user clicks a link or button in your app to start an OAuth request.
- Nylas forwards the end user to their auth provider where they complete the OAuth flow, either accepting or declining the requested scopes.
- The auth provider directs the end user back to the Nylas callback URI, and includes URL parameters that indicate success or failure, and other information.
- If auth was successful, Nylas creates an unverified grant record, then forwards the user to your application's
callback_uri
and includes the accesscode
from the provider in the URL as a parameter. - Your application uses the
code
to perform a token exchange with the provider. - When the token exchange completes successfully, Nylas marks the grant record as verified and sends the
grant_id
to you.
At this point, instead of using doing a token exchange to get an access token for the specific user, you can use an API key you created to access the Nylas APIs. Your app can use that grant_id
to specify which data to access on behalf of 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 end user clicks.
Your project redirects the end user to the authorization request endpoint (GET api.us.nylas.com/v3/connect/auth
) and includes their information as query parameters, as in the example below. When the user goes to this URL, Nylas starts a secure authentication session and redirects them to the authentication provider website.
/v3/connect/auth?
client_id=<NYLAS_CLIENT_ID>
&redirect_uri=https://myapp.com/callback-handler // Your application callback_uri.
&response_type=code
&access_type=online
&provider=google // (Optional) Provider
&state=sQ6vFQN // (Optional) OAuth context
This cURL request uses access_type=online
to specify not to send a refresh token. This is technically optional, but we've included it in the example because we're using the API key method and don't want a refresh token.
There are several other optional items you can choose to pass:
provider
indicates which connector your Nylas application should use. If you only have one connector you can omit this parameter. If you have more than one connector, you can either specify which one to use, or omit this and Nylas gives the user a list of connectors to choose from when they start the auth process.scopes
are optional if you set default scopes on the connector. Scopes set from this request override the default connector scopes.
You can also start an authorization request using the Nylas SDKs, as in the examples below.
import 'dotenv/config'
import express from 'express'
import Nylas from 'nylas'
const config = {
clientId: process.env.NYLAS_CLIENT_ID,
callbackUri: "http://localhost:3000/oauth/exchange",
apiKey: process.env.NYLAS_API_KEY,
apiUri: process.env.NYLAS_API_URI
}
const nylas = new Nylas({
apiKey: config.apiKey,
apiUri: config.apiUri
})
const app = express()
const port = 3000
// Route to initialize authentication
app.get('/nylas/auth', (req, res) => {
const authUrl = nylas.auth.urlForOAuth2({
clientId: config.clientId,
provider: 'google',
redirectUri: config.callbackUri,
loginHint: 'email_to_connect',
})
res.redirect(authUrl)
})
from dotenv import load_dotenv
load_dotenv()
import json
import os
from functools import wraps
from io import BytesIO
from flask import Flask, request, redirect
from nylas import Client
nylas = Client(
os.environ.get("NYLAS_CLIENT_ID"),
os.environ.get("NYLAS_API_URI")
)
REDIRECT_CLIENT_URI = 'http://localhost:9000/oauth/exchange'
flask_app = Flask(__name__)
CORS(flask_app, supports_credentials=True)
@flask_app.route("/nylas/generate-auth-url", methods=["GET"])
def build_auth_url():
auth_url = nylas.auth.url_for_oauth2(
config={
"client_id": os.environ.get("NYLAS_CLIENT_ID"),
"provider": 'google',
"redirect_uri": REDIRECT_CLIENT_URI,
"login_hint": "email_to_connect"
}
)
return redirect(auth_url)
require 'nylas'
require 'sinatra'
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>"
}
url = nylas.auth.url_for_oauth2(config)
redirect url
end
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(
"<NYLAS_CLIENT_ID>",
"http://localhost:4567/oauth/exchange",
AccessType.ONLINE,
AuthProvider.GOOGLE,
Prompt.DETECT,
scope,
true,
"sQ6vFQN",
"<email_to_connect>"
);
String url = nylas.auth().urlForOAuth2(config);
response.redirect(url);
return null;
});
}
}
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.kotlin.Http
import spark.kotlin.ignite
fun main(args: Array<String>) {
val nylas: NylasClient = NylasClient(apiKey = "<NYLAS_API_KEY>")
val http: Http = ignite()
http.get("/nylas/auth") {
val scope = listOf("https://www.googleapis.com/auth/userinfo.email")
val config : UrlForAuthenticationConfig = UrlForAuthenticationConfig(
"<NYLAS_CLIENT_ID>",
"http://localhost:4567/oauth/exchange",
AccessType.ONLINE,
AuthProvider.GOOGLE,
Prompt.DETECT,
scope,
true,
"sQ6vFQN",
"<email_to_connect>"
)
val url = nylas.auth().urlForOAuth2(config)
response.redirect(url)
}
}
Authorization response and grant creation
Each provider displays the consent and approval steps differently, and it's visible only to the end user. In all cases the user authenticates, and either accepts or declines the scopes your project requested.
🔍 If an end user authenticates using their Google account, they might be directed to Google's authorization screen twice. This is a normal part of Nylas' Hosted auth flow, and ensures that all necessary scopes are approved during the auth process.
Next, the auth provider sends the user to the Nylas redirect_uri
(https://api.us.nylas.com/v3/connect/callback
). Nylas uses the information in the response to find your Nylas application by client_id
and, if the auth was successful, create an unverified grant record for the user and record their details.
Get the user's code
Nylas uses your Nylas application's callback_uri
(for example, app.example.com/callback-handler
) to forward the user back to your project. Nylas includes the code
it got from the auth provider as a query parameter, as in the example below.
https://myapp.com/callback-handler?
code=R2E_t1Mf88lipCSqrKvWZhKKzpMVXb0UOuct-U5LidwJud5vMjH1I4q...
&state=... // If you included a state in the initial auth request, it's returned here.
Token exchange and grant verified
Next you use the /v3/connect/token
endpoint to exchange the code
for an access token, as in the example below. The authentication provider responds with an access token and other information.
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": "<APPLICATION_REDIRECT_URI>", // Your application callback_uri.
"code_verifier": "nylas"
}
{
"access_token": "<ACCESS_TOKEN>",
"token_type": "Bearer",
"id_token": "<id_token>",
"grant_id": "<grant_id>"
}
You can also exchange the code
using the Nylas SDKs, as in the following examples.
app.get('/oauth/exchange', async (req, res) => {
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,
code
})
const { grantId } = response
res.status(200)
} catch (error) {
res.status(500).send('Failed to exchange authorization code for token')
}
})
@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": os.environ.get("NYLAS_CLIENT_ID"),
"redirect_uri": REDIRECT_CLIENT_URI
}
)
return {
'grant_id': code_exchange_response.grant_id
}
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
responde_data = response[:grant_id]
"#{response_data}"
end
end
get("/oauth/exchange", (request, response) -> {
String code = request.queryParams("code");
if(code == null) { response.status(401);}
assert code != null;
CodeExchangeRequest codeRequest = new CodeExchangeRequest(
"http://localhost:4567/oauth/exchange",
code,
"<NYLAS_CLIENT_ID>",
"nylas"
);
try {
CodeExchangeResponse codeResponse = nylas.auth().exchangeCodeForToken(codeRequest);
return "%s".formatted(codeResponse);
} catch(Exception e) {
return "%s".formatted(e);
}
});
http.get("/oauth/exchange") {
val code : String = request.queryParams("code")
if(code == "") { response.status(401) }
val codeRequest : CodeExchangeRequest = CodeExchangeRequest(
"http://localhost:4567/oauth/exchange",
code,
"<NYLAS_CLIENT_ID>",
"nylas"
)
try {
val codeResponse : CodeExchangeResponse = nylas.auth().exchangeCodeForToken(codeRequest)
codeResponse
} catch (e : Exception) {
e
}
}
When this is successfully completed, Nylas marks the end user's grant as verified and sends you their grant_id
and email address.
Record the grant ID and use your API key
As long as authentication succeeded, you can stop the OAuth process here, record the grant_id
created for the end user, and continue using your API key to make requests on behalf of this grant, as in the following examples.
You don't need to record the user's OAuth access token, or any other OAuth information. Nylas stores what it needs in the end user's grant record.
curl --request POST \
--url https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/events?calendar_id=<CALENDAR_ID> \
--header 'Accept: application/json' \
--header 'Authorization: Bearer <NYLAS_API_KEY>' \
--header 'Content-Type: application/json' \
--data '{
"title": "Birthday Party",
"status": "confirmed",
"busy": true,
"participants": [{
"name": "Aristotle",
"email": "aristotle@example.com",
"status": "yes"
}],
"description": "Come ready to skate",
"when": {
"time": 1408875644,
"timezone": "America/New_York"
},
"location": "Roller Rink",
"recurrence": [
"RRULE:FREQ=WEEKLY;BYDAY=MO",
"EXDATE:20211011T000000Z"
]
}'
import 'dotenv/config'
import Nylas from 'nylas'
const config = {
apiKey: process.env.NYLAS_API_KEY,
apiUri: process.env.NYLAS_API_URI,
}
const nylas = new Nylas(config)
const identifier = process.env.NYLAS_GRANT_ID
async function getGrantInformation() {
try {
const account = await nylas.grants.find({ grantId });
console.log('Account Information:', account);
} catch (error) {
console.error('Error retrieving account information:', error);
}
}
getGrantInformation();
from dotenv import load_dotenv
load_dotenv()
import os
import sys
from nylas import Client
nylas = Client(
os.environ.get('NYLAS_API_KEY'),
os.environ.get('NYLAS_API_URI')
)
grant_id = os.environ.get("NYLAS_GRANT_ID")
account = nylas.grants.find(grant_id)
print(account)
require 'nylas'
nylas = Nylas::Client.new(api_key: "<NYLAS_API_KEY>")
query_params = {calendar_id: "<NYLAS_GRANT_ID>"}
events, _request_ids = nylas.events.list(identifier: "<CALENDAR_ID>", query_params: query_params)
events.each {|event|
puts "#{event[:id]} | #{event[:title]}"
}
import com.nylas.NylasClient;
import com.nylas.models.*;
import java.util.List;
public class read_calendar_events {
public static void main(String[] args) throws NylasSdkTimeoutError, NylasApiError {
NylasClient nylas = new NylasClient.Builder("<NYLAS_API_KEY>").build();
ListEventQueryParams query_params = new ListEventQueryParams.Builder("<NYLAS_GRANT_ID>").build();
List<Event> events = nylas.events().list("CALENDAR_ID>", query_params).getData();
for(Event event : events){
System.out.printf(" %s | %s%n", event.getId(), event.getTitle());
}
}
}
import com.nylas.NylasClient
import com.nylas.models.ListEventQueryParams
fun main(args: Array<String>) {
val nylas: NylasClient = NylasClient(apiKey = "<NYLAS_API_KEY>")
var queryParams = ListEventQueryParams.Builder("<CALENDAR_ID>").build()
var events = nylas.events().list("<NYLAS_GRANT_ID>", queryParams).data
for(event in events){
println(event.id + " | " + event.title)
}
}
Using the state
parameter to pass information about the user
Nylas Hosted OAuth includes the standard state
parameter. This is an optional parameter in Nylas, and if you include it in an authorization request, Nylas returns the value unmodified back to the application. You can use this as a verification check, and to track information about the user that you need when creating a grant or logging them in.
Learn more about the state
parameter in the OAuth 2.0 specification or in the official OAuth 2.0 documentation.