Netlify Identity isn’t just a user management system; it’s a fully managed, serverless authentication and authorization service that integrates seamlessly with your Jamstack frontend, acting as your backend for user-related operations.

Let’s see it in action. Imagine a simple blog where only logged-in users can leave comments.

First, you enable Netlify Identity in your Netlify project settings. Under "Identity," you’ll see options to configure email notifications, invite users, and set up external providers like Google or GitHub.

# netlify.toml
[functions]
  node_version = "18"

[build]
  functions = "netlify/functions"

On your frontend (let’s say React), you’ll use the netlify-identity-widget.

// src/App.js
import React from 'react';
import IdentityWidget from 'netlify-identity-widget';
import 'netlify-identity-widget/styles.css';

function App() {
  React.useEffect(() => {
    IdentityWidget.init();
  }, []);

  return (
    <div>
      <h1>My Jamstack Blog</h1>
      <IdentityWidget />
      {/* ... rest of your app */}
    </div>
  );
}

export default App;

When a user clicks "Login" or "Sign Up," the widget pops up. After authentication, the widget emits events. We can listen for these to update our UI.

// src/App.js (continued)
import React, { useState, useEffect } from 'react';
import IdentityWidget from 'netlify-identity-widget';
import 'netlify-identity-widget/styles.css';

function App() {
  const [user, setUser] = useState(null);

  useEffect(() => {
    IdentityWidget.init();

    const handleLogin = () => setUser(IdentityWidget.currentUser());
    const handleLogout = () => setUser(null);

    IdentityWidget.on('login', handleLogin);
    IdentityWidget.on('logout', handleLogout);

    // Initial check for logged-in user
    setUser(IdentityWidget.currentUser());

    return () => {
      IdentityWidget.off('login', handleLogin);
      IdentityWidget.off('logout', handleLogout);
    };
  }, []);

  return (
    <div>
      <h1>My Jamstack Blog</h1>
      <IdentityWidget />
      {user ? (
        <p>Welcome, {user.user_metadata.full_name || user.email}!</p>
      ) : (
        <p>Please log in.</p>
      )}
      {/* Conditionally render comment form */}
      {user && <CommentForm />}
    </div>
  );
}

function CommentForm() {
  // ... form logic ...
  return (
    <div>
      <h3>Leave a Comment</h3>
      {/* Comment input fields */}
    </div>
  );
}

export default App;

The magic happens with Netlify Functions. When a user is logged in, the netlify-identity-widget automatically attaches a JWT (JSON Web Token) to outgoing requests made via fetch if you use Netlify’s fetch wrapper or configure your API calls correctly. This token can be verified by your Netlify Functions to ensure the user is authenticated and authorized.

Here’s a simplified Netlify Function to handle comments:

// netlify/functions/submit-comment.js
exports.handler = async (event, context) => {
  // context.clientContext contains information about the logged-in user
  // if the request included a valid JWT.
  const { user } = context.clientContext;

  if (!user) {
    return {
      statusCode: 401,
      body: JSON.stringify({ message: "Unauthorized. Please log in." }),
    };
  }

  const { comment } = JSON.parse(event.body);
  const userId = user.sub; // Unique user ID from the JWT

  console.log(`User ${userId} submitted comment: ${comment}`);

  // In a real app, you'd save this to a database
  // e.g., using FaunaDB, Netlify's own database, or another service.

  return {
    statusCode: 200,
    body: JSON.stringify({ message: "Comment received!", userId }),
  };
};

To make requests to this function from the frontend, you’d typically use netlifyIdentity.gitHub() or similar, which handles the JWT.

// src/CommentForm.js (simplified)
import React, { useState } from 'react';
import netlifyIdentity from 'netlify-identity-widget';

function CommentForm() {
  const [commentText, setCommentText] = useState('');

  const handleSubmit = async (e) => {
    e.preventDefault();
    const currentUser = netlifyIdentity.currentUser();

    if (!currentUser) {
      alert('You must be logged in to comment!');
      return;
    }

    try {
      const response = await fetch('/.netlify/functions/submit-comment', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          // Netlify Identity automatically injects the JWT for requests
          // made through its authenticated fetch wrapper or if configured.
          // For direct fetch, you might need to manually add it:
          // 'Authorization': `Bearer ${currentUser.token.access_token}`
        },
        body: JSON.stringify({ comment: commentText }),
      });

      const data = await response.json();
      if (response.ok) {
        alert('Comment posted successfully!');
        setCommentText('');
      } else {
        alert(`Error: ${data.message}`);
      }
    } catch (error) {
      console.error('Error submitting comment:', error);
      alert('An error occurred. Please try again.');
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <textarea
        value={commentText}
        onChange={(e) => setCommentText(e.target.value)}
        placeholder="Your comment..."
      />
      <button type="submit">Post Comment</button>
    </form>
  );
}

The context.clientContext.user object in your Netlify Function is populated by Netlify’s GoTrue API, which is the backend powering Netlify Identity. It contains details like email, user_metadata (which you can customize with user.update() on the frontend), and a unique sub (subject) ID. This sub ID is crucial for linking comments, posts, or any user-specific data back to the correct user in your external database.

One of the most powerful aspects is how it handles authorization beyond just "logged in." You can define "roles" within Netlify Identity. For instance, you could have an "admin" role. Your Netlify Functions can then check user.app_metadata.roles to see if the logged-in user belongs to specific roles.

// netlify/functions/admin-action.js
exports.handler = async (event, context) => {
  const { user } = context.clientContext;

  if (!user || !user.app_metadata || !user.app_metadata.roles || !user.app_metadata.roles.includes('admin')) {
    return {
      statusCode: 403,
      body: JSON.stringify({ message: "Forbidden. Only admins can perform this action." }),
    };
  }

  // Perform admin action...
  console.log(`Admin user ${user.email} performing action.`);

  return {
    statusCode: 200,
    body: JSON.stringify({ message: "Admin action successful!" }),
  };
};

This allows you to build complex access control logic directly into your serverless functions, keeping your frontend clean and secure. Netlify Identity abstracts away the complexities of OAuth, JWT management, and user data storage, allowing you to focus on building your Jamstack application’s core features.

The next step is often integrating with a database to persist the user-generated content, like comments or user profiles, and then fetching that data securely using Netlify Functions that verify the user’s identity.

Want structured learning?

Take the full Netlify course →