If you have ever clicked "Sign in with Google" and wondered how that actually works without Google handing your password to the third-party site, this article is for you. The answer is OAuth 2.0, the dominant protocol for delegated authorisation on the web. It is also one of the most over-explained, jargon-heavy topics in software engineering. By the end of this article you will understand exactly what is happening when you click that button, and why it is designed the way it is.

What OAuth solves

The original problem: you want to give a third-party app access to some of your data on another service, without giving it your password. Imagine a printing service that wants to pull your photos from Google Photos. The naive way: you tell Google to give the printer your password. The safe way: you tell Google to give the printer a special token that allows only the specific things you approved.

That is OAuth. It is a delegation protocol. The user authorises the third party to act on their behalf, with a scope they control. The third party never sees the user's credentials.

The cast of characters

OAuth 2.0 has four roles:

  • Resource owner — the user. Owns the data being accessed.
  • Client — the third-party app that wants access.
  • Authorisation server — issues tokens after the user approves.
  • Resource server — the API the client eventually calls with the token.

In the Google Photos example: you are the resource owner, the printing service is the client, Google's login page is the authorisation server, and the Google Photos API is the resource server.

The flow, step by step

Here is the canonical OAuth 2.0 "authorisation code" flow, the one used for "Sign in with Google":

  1. The user clicks "Sign in with Google" on the client app.
  2. The client redirects the user to Google's authorisation endpoint, passing a client ID, a redirect URI, a requested scope, and a state parameter.
  3. Google asks the user to log in (if they are not already) and shows a consent screen: "The printing service wants to access your photos. Allow?"
  4. The user clicks Allow.
  5. Google redirects the user back to the client's redirect URI, with an authorisation code in the URL.
  6. The client (back-end) exchanges the authorisation code for an access token by calling Google's token endpoint, this time authenticated with the client ID and client secret.
  7. Google returns an access token (and optionally a refresh token).
  8. The client uses the access token to call the Google Photos API on the user's behalf.

That is the whole flow. The key insight: the user never sees the access token. The client exchanges a one-time code for it, server-to-server, with a secret that proves the client is who it says it is.

The tokens

The access token is what the client uses to call the API. It is usually short-lived (an hour) and tied to the specific scope the user approved. If the client asks for read:photos, the token only allows reading photos — not deleting them or posting on the user's behalf.

The refresh token (if issued) is long-lived and used to obtain new access tokens. The client stores it securely and uses it when the access token expires. Refresh tokens can be revoked server-side.

Why authorisation code and not something simpler?

You will sometimes see "implicit flow" or "password grant" in old tutorials. Those are simpler but less secure. The authorisation code flow protects the access token by ensuring it is only ever exchanged server-to-server (where the client secret is safe). Implicit flow gave the token directly to the browser, which is no longer recommended. Password grant had the user type their password into the client app, which defeats the point of OAuth.

For new applications in 2026, always use the authorisation code flow with PKCE (pronounced "pixy"). PKCE adds an extra layer of protection against authorisation code interception attacks.

Scopes: what the client is allowed to do

When the client asks for authorisation, it declares a list of scopes. The user sees those scopes on the consent screen and approves or denies. Common scopes:

  • openid — required for OpenID Connect (authentication on top of OAuth).
  • profile, email — access to the user's basic profile.
  • read:photos, write:photos — resource-specific scopes.

Keep scopes minimal. Asking for more than you need erodes user trust and may require additional verification from the platform.

Implementing OAuth in your own app

For most apps, you do not need to implement OAuth — you consume it. "Sign in with Google" means your back-end talks to Google's authorisation server and exchanges the result for your own session.

For ASP.NET Core, there are libraries for the major providers:

dotnet add package Microsoft.AspNetCore.Authentication.Google
builder.Services.AddAuthentication()
  .AddGoogle(o =>
  {
    o.ClientId = builder.Configuration["Google:ClientId"];
    o.ClientSecret = builder.Configuration["Google:ClientSecret"];
  });

That single block gives you the full "Sign in with Google" button, the redirect handling, the code exchange, and the user info retrieval. For other providers (GitHub, Apple, Microsoft), similar packages exist.

If you ARE building an authorisation server

Writing your own OAuth server is a serious undertaking. Use OpenIddict or IdentityServer (now Duende) instead of rolling your own. The protocol is deceptively complex; there are dozens of edge cases around PKCE, token lifetimes, scope handling, and refresh token rotation. Every implementation has had security bugs; the libraries have been hardened over years.

If you only need authentication for your own app, do not use OAuth. Use sessions (server-rendered) or JWT (single-page app). OAuth is for delegating access to other apps.

Common pitfalls

  • Storing the client secret in the browser. It is a secret. Only your back-end should see it. For SPAs, use the authorisation code flow with PKCE so no secret is needed.
  • Not validating the state parameter. The state is your CSRF protection. Generate a random value, store it in the session, verify it on the callback.
  • Skipping PKCE. Always use PKCE for public clients (mobile, SPA) and recommended for confidential clients.
  • Asking for too many scopes. Users revoke access for apps that ask for too much. Start with the minimum, ask for more only when you need it.
  • Not handling token refresh. Access tokens expire. Have a plan to refresh them silently or prompt the user to log in again.

OAuth versus OpenID Connect

OpenID Connect (OIDC) is a thin layer on top of OAuth 2.0 that adds authentication. OAuth is for authorisation ("what is this app allowed to do?"). OIDC adds an identity token that says "who is this user?" If you only need "Sign in with Google" to identify users, you want OIDC, which is essentially OAuth plus the openid scope and an ID token (usually a JWT).

A more advanced thing: token introspection and revocation

For OAuth provider

Further reading

OAuth 2.0 has a reputation for being over-engineered because most tutorials skip the specification. The three sources below — the IETF RFC, the OAuth.net community site, and Google’s developer docs — are the ones we recommend in that order.

s that issue opaque (non-JWT) tokens, the resource server cannot validate the token by itself. It must call the authorisation server's introspection endpoint to check if the token is still valid. For JWTs, the resource server can validate locally without a network call — but it cannot tell if the token has been revoked.

The revocation endpoint lets the client or user explicitly invalidate a token before its expiry. Useful for logout flows. Most providers support it; check the docs for each.

Real-world examples: who uses what

Almost every major consumer app today supports "Sign in with Google", "Sign in with Apple", or "Sign in with Facebook" — all variations on the same OAuth 2.0 / OIDC pattern. When you click one of those buttons, the flow is exactly what this article describes.

On the developer side, the most common pattern for B2B apps is OAuth 2.0 to connect to a customer's Salesforce, Slack, or GitHub — the customer grants your app scoped access to their data. If you are building anything in the SaaS ecosystem, you will be on one side or the other of this pattern.

FAQ

What is the difference between OAuth and JWT?

OAuth is a protocol for issuing tokens. JWT is a token format. They are not the same thing. OAuth can issue JWTs, but it can also issue opaque tokens. See our JWT article for the deep dive on JWT specifically.

Is OAuth 2.0 secure?

When implemented correctly with PKCE, state validation, and secret storage, yes. Many implementations have had bugs because the spec is large and the edge cases are subtle. Use libraries, not roll your own.

Should I implement my own OAuth server?

Almost never. Use Auth0, Clerk, AWS Cognito, Google Firebase Auth, or a self-hosted library like OpenIddict. Rolling your own is a six-month project for a competent team.

What is PKCE?

Proof Key for Code Exchange. It is a one-time secret generated by the client, sent with the authorisation request, and required when exchanging the code. It prevents an attacker who intercepts the authorisation code from using it without also having the PKCE secret.

How do I add "Sign in with GitHub"?

Same pattern as Google. Use AspNet.Security.OAuth.GitHub or equivalent for your stack. Register your app on GitHub to get a client ID and secret. Add the middleware. Done.

What is the difference between scopes and claims?

Scopes are what the client asks for on the authorisation request. Claims are what the token contains. A scope is a permission; a claim is a piece of data. The authorisation server maps scopes to claims when it issues the token.

Test thoroughly with multiple browsers and a real Google account before shipping.

Read each error message in full and test on multiple devices before going live.

Take it slow and verify every step with a real browser session.

Verify before shipping.

Homework

Add "Sign in with Google" to a small ASP.NET Core app:

  • Register your app in the Google Cloud Console, get a client ID and client secret.
  • Add the Google authentication middleware to Program.cs.
  • Add a "Sign in" button on the home page.
  • Display the user's name and email on a protected /profile page.
  • Add a sign-out endpoint that revokes the session.

Once you can log in, log out, and read the user's profile, you have the foundation for almost every third-party integration you will ever build. For the underlying token format, see our JWT Authentication article.