Build a private application

How to build a basic private application with CARTO login

Here you will learn how to create an application that requires a login to access. This application will be only accessible to users inside your CARTO organization (regardless of their role), so you will need a CARTO user to access it.

If you're building an application for an Enterprise organization that requires an integration with your own login system, you'll need to first connect and enable the Single Sign-On (SSO) feature in CARTO. We'll cover that in the next guide: Build a private application using SSO.

The CARTO login uses the popular OAuth 2.0 Authorization Framework protocol, so in this guide you will find an easy process to implement it, including login and logout methods.

After completing this guide you will be familiar with the following concepts:

  • Scaffolding your application.

  • Creating a Single Page Application OAuth Client (SPA OAuth Client).

  • Managing the user login using Auth0 SDK.

  • Visualizing a dataset.

  • Executing a query against CARTO APIs.

During this guide, we're using the CARTO Data Warehouse. The process explained here is also compatible with other Warehouses like BigQuery, Snowflake, Redshift, or Postgres. Instead of using connection=carto_dw, you need to use connection=<your_connection>.

Scaffolding your application

As explained here, we recommend Vite to manage the tooling of your app.

To make this guide faster, we're going to start using the basic CARTO template in Vite that already includes a Basemap and

git clone
cp -r carto-for-developers-guides/template private-app
cd private-app
npm install
npm run dev

After that, you should have a project up and running at

Create a SPA OAuth Client

As explained in Key Concepts section we need to create a SPA OAuth Client to obtain a client_id + client_secret that we will use to then obtain an OAuth token after the user successfully authenticates (with user and password or other methods)

Go to Workspace -> Developers -> Credentials -> Create New -> SPA OAuth Client. And complete the form as follows:

Then, go to the .env file, and uncomment VITE_CLIENT_ID with the value of the app that was just created.

Managing the user login using Auth0 SDK

CARTO platform uses Auth0 under the hood to manage the authentication and authorization. When you created a SPA application in the previous step, CARTO created a SPA application in Auth0.

Auth0 provides SDKs to manage the OAuth 2.0 workflow for Angular, Vue, React, and Vanilla Javascript. In this guide, we're going to use the Vanilla Javascript SDKs to manage the login with CARTO.

We're going to implement here two things:

  • Authenticate a user following the standard OAuth 2.0 workflow.

  • Use the authenticated user to obtain an OAuth Access Token to call CARTO APIs.

  1. Install the Auth0 SDK:

npm install @auth0/auth0-spa-js
  1. Create a file src/auth.ts:

import { Auth0Client, createAuth0Client } from '@auth0/auth0-spa-js';

let auth0Client: Auth0Client

export async function initAuth() {
  let accessToken: string | undefined;

  const clientId = import.meta.env.VITE_CLIENT_ID;
  auth0Client = await createAuth0Client({
    domain: '',
    authorizationParams: {
      redirect_uri: window.location.origin,
      audience: 'carto-cloud-native-api'

  if ('state=') && 
      ('code=') ||'error='))) {
    await auth0Client.handleRedirectCallback();
    window.history.replaceState({}, document.title, '/');

  const isAuthenticated = await auth0Client.isAuthenticated();
  const appEl = document.getElementById('app');

  if (isAuthenticated) {
    const userProfile = await auth0Client.getUser();
    accessToken = await auth0Client.getTokenSilently();

    if (userProfile) {
      const profileEl = document.getElementById('profile')!;
      profileEl.innerHTML = `
            <img width="50px" height="50px" src='${userProfile.picture}' />
  } else {
  // Logout
  const logoutButton = document.getElementById('logout');
  logoutButton?.addEventListener('click', (e) => {

  // Login
  const loginButton = document.getElementById('login');
  loginButton?.addEventListener('click', (e) => {
  return accessToken
  1. Replace src/main.ts with:

import './style.css'
import { initAuth } from './auth'
import { createMap } from './map'

document.querySelector<HTMLDivElement>('#app')!.innerHTML = `
  <button id="login">Login</button>
  <div id="login-content">
      <button id="logout">Logout</button>
      <div id="profile"></div>
    <div id="map"></div>
    <canvas id="deck-canvas"></canvas>

initAuth().then((accessToken) => {
  if (accessToken) {
  1. Add this at the end of src/style.css:

header {
  z-index: 1;
  position: absolute;
  padding: 10px;

#login-content {
  display: none;

.isAuthenticated #login-content {
  display: block;
.isAuthenticated #login {
  display: none;

Your application is now ready to authenticate users by sending them to the CARTO login, which will require the user to enter their own credentials, before successfully returning to the application in a classic OAuth 2.0 workflow that returns an OAuth Access Token.

Visualizing a dataset

Once the user is logged in an OAuth Access Token will be available, and we just need to tell to use this token.

Let's modify src/main.ts:

initAuth().then((accessToken) => {
  if (accessToken) {
    const apiBaseUrl = import.meta.env.VITE_API_BASE_URL;
    createMap({apiBaseUrl, accessToken});

And prepare src/map.ts to receive the new parameters, including imports:

import { BASEMAP, vectorTableSource, VectorTileLayer } from '';

interface createMapProps {
  apiBaseUrl: string,
  accessToken: string
export function createMap({apiBaseUrl, accessToken}:createMapProps) {
// ...

Then add the following source and layer to the Deck instance at src/map.ts:

const connectionName = 'carto_dw';
const cartoConfig = {apiBaseUrl, accessToken, connectionName};

const demoTableSource = vectorTableSource({
  tableName: 'carto-demo-data.demo_tables.populated_places'

const deck = new Deck({
  canvas: 'deck-canvas',
  initialViewState: INITIAL_VIEW_STATE,
  controller: true,
  layers: [
    new VectorTileLayer({
      id: 'places',
      data: demoTableSource,
      pointRadiusMinPixels: 3,
      getFillColor: [200, 0, 80],

Remember that OAuth Access Tokens impersonate the user, so it will inherit all the permissions of the user.

Executing a query against CARTO APIs

At this point, we're all clear to execute a query against the CARTO SQL API using the OAuth Access Token we've previously obtained.

Create a file src/api.ts:

interface ExecuteQueryProps {
  apiBaseUrl: string
  connection: string
  query: string
  accessToken: string
async function executeQuery ({apiBaseUrl, connection, query, accessToken}: ExecuteQueryProps) {
  const url = `${apiBaseUrl}/v3/sql/${connection}/query?q=${encodeURI(query)}`
  const options = {
    method: 'GET',
    headers: {
      'Authorization': `Bearer ${accessToken}`
  const response = await fetch(url, options);
  return await response.json();

interface GetAirportsProps {
  apiBaseUrl: string,
  accessToken: string
export function getAirports ({apiBaseUrl, accessToken}: GetAirportsProps) {
  const query = `select * from carto-demo-data.demo_tables.world_airports`
  const connection = 'carto_dw'
  return executeQuery({apiBaseUrl, connection, query, accessToken})

Modify src/main.ts to call the getAirports function:

initAuth().then((accessToken) => {
  if (accessToken) {
    const apiBaseUrl = import.meta.env.VITE_API_BASE_URL;
    setDefaultCredentials({ apiBaseUrl, accessToken})
    getAirports({apiBaseUrl, accessToken}).then((airports) => {

Congratulations! You now have successfully built a private application that will require the user to log in. And after a successful login, you're able to show the user a map layer, and you're able to query an airports dataset right from your application.

What's next?

The full code of this guide is available on GitHub.

git clone
cd carto-for-developers-guides/private-app

We recommend you to visit CARTO + to learn more about the visualizations you can create in There are many examples in our gallery that might be useful to improve your application!

Last updated