How to Sync Google and GitHub Logins to Your Database With Neon Auth for Free
Learn how Neon Auth syncs Google and GitHub logins into your Postgres database for free, with no webhooks, and how to join the data in ASP.NET Core EF Core.
Imagine you join a new library in your town. At the front desk, a kind librarian writes your name, your card number, and the date you joined into the big register. You did not have to copy your details into every shelf and every room yourself. You just showed your ID once, and the library kept a neat record of you in one trusted place.
Neon Auth works in almost the same way. When a person signs in to your app with their Google or GitHub account, Neon quietly writes that person's basic details into a special table inside your own database. You do not write the copying code. You do not build webhooks. The "librarian" does it for you, and your app can simply read the register whenever it needs to.
In this article we will learn, in very simple steps, how this works and how to use it from a .NET app. We will see what Neon Auth is, how the magic neon_auth.users_sync table gets filled, and how to join that table to your own data in ASP.NET Core with Entity Framework Core. By the end you will be able to let users log in with Google and GitHub, and have their profiles sitting safely in your Postgres database, for free.
The old, painful way
Before tools like Neon Auth, syncing users was a real headache. Your login provider (the service that checks the Google or GitHub password) lived in one place. Your database lived in another place. You had to glue them together yourself.
The usual plan looked like this. The auth provider would send a "webhook" (a small message) every time a user signed up or changed their name. Your server had to catch that message, understand it, and write the user into your database. If the message was lost, your data was wrong. If two messages arrived at once, you got duplicates. If the user changed their email, you had to handle that too.
That webhook code is boring to write, easy to get wrong, and annoying to keep alive. Every team rebuilds it, and every team hits the same bugs. Neon Auth removes this whole job.
What is Neon Auth?
Neon is a serverless Postgres database in the cloud. "Serverless" just means you do not manage a server; you create a database in a few clicks and pay only for what you use, with a generous free plan.
Neon Auth is a feature built on top of Neon. It connects an authentication provider directly to your Neon database. When users log in or update their accounts, the changes flow straight into your database. There is no custom code and no separate webhook for you to babysit.
Here is the key idea. The full, official copy of the user profile lives in the auth provider. Neon Auth keeps a near real-time mirror of that profile inside a table in your database called neon_auth.users_sync. You read from this mirror like any other table.
What Neon Auth does for you
Steps
Login
User clicks Google or GitHub
Provider
Auth provider checks identity
Sync
Neon mirrors the profile
Your DB
Row appears in users_sync
The magic table: neon_auth.users_sync
When you turn on Neon Auth, it creates a new schema in your database called neon_auth. Inside it sits a table named users_sync. This table is the register from our library story. Every signed-in user shows up here.
The table has a small, friendly set of columns. You do not create them; Neon does.
| Column | Meaning | Can be empty? |
|---|---|---|
id | The unique id of the user | No |
email | The user's main email address | No |
name | The display name | Yes |
raw_json | The full profile as raw JSON | No |
created_at | When the user first signed up | No |
updated_at | When the profile last changed | Yes |
deleted_at | When the user was deleted | Yes |
A few things are worth understanding here.
The id is a text value, not a number. Think of it like a membership card number that the auth provider hands out. You will use this id to link your own data to the user.
The raw_json column holds the entire profile as JSON. If you ever need a field that does not have its own column, like a profile picture URL, you can dig it out of raw_json.
The deleted_at column is special. Neon Auth uses soft deletes. When a user is removed, the row is not erased. Instead, Neon writes the date into deleted_at. The row stays so your old data does not suddenly break. To show only active users, you simply ignore the rows where deleted_at has a value.
Step 1: Create a Neon project
First, go to the Neon website and create a free account. Then create a new project. A project comes with a ready-to-use Postgres database and a connection string. Keep that connection string safe; your .NET app will use it to talk to the database.
Inside the Neon Console you will see an Auth tab. Open it. Neon will guide you to enable Neon Auth for your project. With one click, Neon creates the neon_auth schema and the users_sync table in your database.
The nice part for learners: Google sign-in is enabled by default using shared development credentials. That means you can test a Google login straight away without setting up your own OAuth keys. GitHub and other providers can be added when you are ready.
Step 2: Let users log in
Neon Auth gives you a small frontend starter (for example a Next.js template) that already knows how to talk to the auth provider. You run it, open it in your browser, and you will see "Sign in with Google" and "Sign in with GitHub" buttons.
When a user clicks one of those buttons, the OAuth flow runs. OAuth is the polite handshake where Google or GitHub confirms "yes, this is really them" without your app ever seeing their password. After the handshake, the user is signed in, and Neon Auth writes their profile into users_sync.
After you log in once, go back to the Neon Console and open the neon_auth.users_sync table. You will see your own row sitting there, filled in automatically. That is the whole point: no webhook, no copy code, just data.
Step 3: Read the table from .NET
Now the fun part for us .NET developers. We want our ASP.NET Core app to read the users_sync table and join it to our own data.
There is one rule to remember. You should never run EF Core migrations against the users_sync table. Neon owns that table. Your app only reads it. If you let your migrations touch it, you could break the sync. So we map it as a read-only entity and tell EF Core to leave it alone.
First, install the Postgres provider for EF Core.
// In your terminal, from the project folder:
// dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
// Then map the table with a simple class.
public class NeonUser
{
public string Id { get; set; } = default!;
public string Name { get; set; } = default!;
public string Email { get; set; } = default!;
public string RawJson { get; set; } = default!;
public DateTime CreatedAt { get; set; }
public DateTime? UpdatedAt { get; set; }
public DateTime? DeletedAt { get; set; }
}Next, configure this entity in your DbContext. We point it at the right schema and table, map the snake_case column names, and most importantly call ExcludeFromMigrations() so EF Core never tries to create or change this table.
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options)
: base(options) { }
public DbSet<NeonUser> Users => Set<NeonUser>();
public DbSet<ProductCart> Carts => Set<ProductCart>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<NeonUser>(b =>
{
// Neon owns this table; we only read it.
b.ToTable("users_sync", "neon_auth", t => t.ExcludeFromMigrations());
b.HasKey(u => u.Id);
b.Property(u => u.Id).HasColumnName("id");
b.Property(u => u.Name).HasColumnName("name");
b.Property(u => u.Email).HasColumnName("email");
b.Property(u => u.RawJson).HasColumnName("raw_json");
b.Property(u => u.CreatedAt).HasColumnName("created_at");
b.Property(u => u.UpdatedAt).HasColumnName("updated_at");
b.Property(u => u.DeletedAt).HasColumnName("deleted_at");
});
}
}Now you can query users like any other table. Remember to skip soft-deleted users by filtering out rows where DeletedAt is not null.
// Get all active users (ignore soft-deleted ones).
var activeUsers = await db.Users
.Where(u => u.DeletedAt == null)
.OrderBy(u => u.Name)
.ToListAsync();
// Find one user by their Neon id.
var me = await db.Users
.FirstOrDefaultAsync(u => u.Id == userId);Step 4: Link your own data to a user
Your app has its own data: carts, orders, posts, comments, and so on. You want each of these to belong to a user. You do that by storing the user's Id (the text id from users_sync) on your own entity.
Here is a ProductCart that belongs to a user. Notice that UserId is a string, because the Neon user id is text.
public class ProductCart
{
public Guid Id { get; set; }
public string UserId { get; set; } = default!; // matches NeonUser.Id
public NeonUser User { get; set; } = default!;
public decimal Total { get; set; }
}You can wire the relationship in OnModelCreating. This lets you load a cart together with its user in one query.
modelBuilder.Entity<ProductCart>(b =>
{
b.HasKey(c => c.Id);
b.HasOne(c => c.User)
.WithMany()
.HasForeignKey(c => c.UserId)
.OnDelete(DeleteBehavior.Cascade);
});How your tables connect to users_sync
Steps
users_sync
Holds id, name, email
UserId
Text key on your row
Your table
Cart, order, or post
Query
Join and read together
A word about foreign keys and the small delay
Remember that the sync is asynchronous. There can be a tiny delay, usually under a second, between a user signing in and their row appearing in users_sync. This matters when you choose how strict to be.
If you add a hard foreign key from your table to users_sync, and you try to save a cart for a user whose row has not synced yet, the save could fail. For most apps this is fine because the user reads their profile first. But if you expect to write data the very instant a user appears, consider a softer link.
You also need to pick the right ON DELETE behavior, because Neon uses soft deletes. Here is a simple guide.
| Your data | Suggested ON DELETE | Why |
|---|---|---|
| Personal data like a cart or preferences | Cascade | If the user is gone, this private data can go too |
| Content like blog posts or comments | SetNull | The content should stay even if the author leaves |
| Audit or history records | No FK, keep the raw id | History must never disappear |
Why this is great for beginners
Let us pause and see what we got for free. We never wrote webhook code. We never built a users table by hand. We never worried about a lost sync message creating a duplicate user. We let Neon be the careful librarian, and we just read the register.
This also keeps your code clean. Your app focuses on its real job, like carts and orders, while identity is handled by a service that is built for it. And because everything lives in one Postgres database, your joins are normal SQL joins. No extra API calls to a separate user service.
One last tip: when you need a field that is not a column, reach into raw_json. Postgres can read JSON, and EF Core can query it too. For example, you can pull the avatar URL out of the JSON without adding a new column.
// Read a field from raw_json using PostgreSQL JSON access.
var avatars = await db.Users
.Where(u => u.DeletedAt == null)
.Select(u => new
{
u.Name,
Avatar = EF.Functions.JsonValue(u.RawJson, "$.profile_image_url")
})
.ToListAsync();Quick recap
- Neon Auth syncs users from Google and GitHub into your own Postgres database, with no webhooks and no copy code.
- The data lands in a table called
neon_auth.users_syncthat Neon creates and fills for you. - Key columns are
id,email,name,raw_json,created_at,updated_at, anddeleted_at. - Neon uses soft deletes: filter out rows where
deleted_atis set to show only active users. - In .NET, map the table as a read-only EF Core entity and call
ExcludeFromMigrations()so your migrations never touch it. - Link your own tables (carts, orders, posts) to the user by storing the text
Idfromusers_sync. - The sync is slightly delayed (usually under a second), so choose your foreign keys and
ON DELETErules with care. - Google sign-in works out of the box on Neon's free plan, so you can start learning today.
References and further reading
- Neon Auth overview (Neon Docs)
- Neon Auth best practices and FAQ (Neon Docs)
- Authentication flow (Neon Docs)
- Connect an Entity Framework application to Neon (Neon Docs)
- Keyless and read-only entity types (Microsoft Learn)
Related Posts
Building Resilient Cloud Applications With .NET
Learn to build resilient cloud apps in .NET with retries, timeouts, and circuit breakers using Polly and Microsoft.Extensions.Resilience.
Strongly Typed IDs in .NET: A Safer Way to Identify Entities
Learn how strongly typed IDs in .NET stop you from mixing up entity identifiers, catch bugs at compile time, and work cleanly with EF Core.
6 Steps for Setting Up a New .NET Project the Right Way
A friendly, step-by-step guide to starting a clean .NET 10 project with the right folder layout, central packages, analyzers, EditorConfig, and CI.
Build a Multi-Model AI Chat Bot in .NET with ChatGPT and Neon Postgres Branching
Learn to build a multi-model AI chat bot in .NET 10 using ChatGPT and Neon serverless Postgres branching, with simple steps a beginner can follow.
Building Semantic Search With Amazon S3 Vectors and Semantic Kernel
A beginner-friendly guide to building semantic search in .NET using Amazon S3 Vectors for cheap storage and Semantic Kernel for embeddings.
What Is Vector Search? A Concise Guide for .NET Developers
A simple, friendly guide to vector search for .NET developers: embeddings, similarity, nearest neighbors, and how to build it with Microsoft.Extensions.VectorData.