- Knowledge
- technology
- OOP
- Tips
- Programming
- Tips
- Tutorial
- SEO
- Ranking
- Knowledge
- Special Day
- Seo
- Bug
- Data science
- Seo
- artificial intelligence
- Machine Learning
- Robotics
- happyNewYear2021
- newYearEve
- 2021
- Automation
- Smart Home
- Career
- Best Practices
- Git
- Logging
- Web Fundamentals
- DNS
- HTTPS
- Performance
- AI Tools
- ChatGPT
- Claude
- Gemini
- Laravel
- Eloquent
- MySQL
- HTTPS
- TLS
- Web Security
- Certificates
- Developer Life
- Debugging
- Docker
- DevOps
- Transactions
- Queues
- LLMs
- AI
- AI Coding
- Developer Tools
- React Native
- Expo
- Kate PMS
- Mobile Apps
- Laravel
- Authentication
- Sanctum
- Cookies
- API Design
- Payments
- Idempotency
- DeepSeek
- Open Source AI
- LLMs
- AI News
- Git
- Version Control
- AI Coding
- Prompting
- PHP
- Checklist
- MCP
- AI Agents
- OpenAI
- Architecture
- Microservices
- Modular Monolith
- Estimation
- Developer Life
- Project Planning
- Humour
- OAuth
- OpenID Connect
- Authentication
- Embeddings
- Vector Search
- RAG
- pgvector
- OpenAI
- GPT-4.1
- Codex CLI
- Events
- Testing
- Clean Code
- Maintainability
- Code Review
- Webhooks
- API
- Security
- Claude Code
- Workflow
- AI
- LLM
- Prompt Injection
- Mobile
- React
- Networking
- TCP
- UDP
- HTTP/3
- CLAUDE.md
- AWS
- Cloud Security
- Backups
- PHPUnit
- Software Engineering
- Leadership
- Communication
- RAG
- Embeddings
- AI Engineering
- IT Infrastructure
- Networking
- Access Control
- CI/CD
- GitHub Actions
- Gemini CLI
- Claude Code
- JavaScript
- Async/Await
- Node.js
- Promises
- Security
- Cryptography
- Passwords
- MySQL
- Database
- Vibe Coding
- Software Quality
- DNS
- Code Reading
- Onboarding
- Productivity
- Background Jobs
- Developer Humour
- Estimates
- Dev Life
- JWT
- o3-mini
- DeepSeek R1
- Rate Limiting
- Kate PMS
- E-Signing
- Audit Trail
- REST
- GraphQL
- API Design
- Laravel 12
- Upgrade Guide
- Open Source
- Self-Hosting
- Task Scheduling
- Cron
- Secrets
- CORS
- PHP
- PHP-FPM
- OPcache
- GitHub Copilot
- Software Architecture
- Engineering
- TypeScript
- JavaScript
- Type Safety
- AI Security
- React Native
- Product Design
- AI Agents
- Kiro
- Queues
- Redis
- RabbitMQ
- AWS SQS
- Nginx
- Apache
- GPT-5
- gpt-oss
- Clean Code
- Architecture
- Naming
- Documentation
- Career
- ADR
- Teamwork
- Supply Chain
- Kate HRM
- HR Software
- Permissions
- System Design
- Pagination
- SSH
- Linux
- Big O
- Databases
- Laravel Boost
- MCP
- Developer Skills
- Validation
- Databases
- Indexes
- Code Quality
- Deployment
- Developer Humour
- Feature Flags
- Code Review
- Pull Requests
- Docker
- Cursor
- Authorization
- RBAC
- Gemini
- Long Context
- PHP 8.4
- Caching
- Dependency Injection
- Web Performance
- Browser
- CSS
- Database
- Migrations
- ChatGPT
- AI for Developers
- Monitoring
- On-Call
- REST
- Backend
- SQL
- NoSQL
- Database Design
- Coding Agents
- Claude 4
- API Resources
- REST API
- Load Balancing
- Scaling
- AWS
- AI Tools
- Claude
- Sora 2
- CTE
- 2FA
- TOTP
- Programming Languages
- Prompts
- Developer Workflow
- API Gateway
- APIs
- Passport
- API Auth
- Learning
- Burnout
- Developer Growth
- Web Development
- SEO
- Kate Mall
- ChatGPT Atlas
- Agent Skills
- Middleware
- Laravel 12
- Collections
- Context Window
- Monitoring
- Commit Messages
- Self Review
- Growth
- Regex
- Programming Basics
- Text Processing
- Database Design
- Normalization
- Linux
- Server Security
- Linux Foundation
- Open Standards
- Legacy Code
- Documentation
- AI Workflow
- File Uploads
- Test Data
- Hashing
- Performance
- Caching
- Enums
- Scope Creep
- Estimation
- Codex
- Gemini CLI
- Timezones
- Carbon
- Bugs
- PHP 8.5
- Gemini 3
- GPT-5.1
- Data Integrity
- Event Loop
- Async
- Opus 4.5
- AI Models
- React
- Forms
- Frontend
- Backups
- AI Images
- DALL-E
- Midjourney
- Race Conditions
- Concurrency
- Legacy Code
- Refactoring
- Senior Engineer
- Scope
- LLM
- CDN
- Web
- Sub-Agents
- Soft Deletes
- Audit Log
- Concurrency
- AI Learning
- NestJS
- AI Evals
- Policies
- SPF DKIM DMARC
- Unicode
- UTF-8
- Knowledge Graph
- Value Objects
- Technical Debt
- Feature Flags
- Laravel Pennant
- Deployment
- Copilot
- Composer
- Dependencies
- Artisan
- Automation
- AWS S3
- Object Storage
- Cloud
- Small Language Models
- Ollama
- Production
- Sessions
- HTTP
- Mentoring
- SQL
- Virtual Machines
- Web Development
- HTTP/2
- QUIC
- Web Performance
- AI Integration
- LLM API
- SOLID
- OOP
- Hosting
- Serverless
- Merge Conflicts
- Temperature
- AI Development
- Reverse Proxy
- Nginx
- Infrastructure
- Verification
- Passkeys
- WebAuthn
- Teams
- Communication
- Stakeholders
- Monorepo
- CI/CD
- Versioning
- JSON Schema
- Livewire
- Inertia
- Meetings
- Distributed Systems
- Privacy
- Full-Stack
- T-Shaped Skills
- Money
- Notifications
- Web Security
- HTTP Headers
- CSP
- Function Calling
- Load Testing
- k6
- Data Extraction
- Debugging
- WebSockets
- SSE
- Real-Time
- Laravel Reverb
- Infrastructure as Code
- Terraform
- Side Projects
- Laravel Pint
- OpenAPI
- Swagger
- UX
- Multimodal
- Jest
- Pair Programming
- APIs
- Rate Limiting
- Resilience
- Dev Humour
- Design Tokens
- JWT
- API Keys
- Sessions
- PHPStan
- Rector
- Incidents
- Reporting
- Dashboards
- Zero Trust
- IAM
- Search
- Laravel Scout
- Junior Developers
- Mentoring
- Images
- WebP
- AVIF
- Bug Reports
- Let's Encrypt
- Design Docs
- Software Design
- Observers
- Replication
- Accountability
- Data Structures
- Reliability
- LLM Memory
- Error Handling
- Payments
- Payment Gateway
- Webhooks
- PCI DSS
- Observability
- OpenTelemetry
- Personal Brand
- Writing
- Conventions
- Dates
- Scheduling
- Disaster Recovery
- Compression
- Brotli
- Deadlines
- Developer Habits
- State Machines
- Tech Roles
- UUID
- ULID
- Horizon
- Planning
- Engineering Culture
- Ownership
- Soft Skills
- Socialite
- Cost Control
- Collations
- Unicode
- Octane
- PostgreSQL
How "Sign in With Google" Works Behind the Button (and the Checks You Shouldn't Skip)
About Post
"Sign in with Google" is the friendliest button on the internet. One click, pick an account, and you're in. No password to forget, no confirmation email to dig out of spam.
Behind that click, though, there are six distinct steps, a client secret, a signed token and several checks that, if you skip them, turn the friendliest button into a very polite way to let the wrong person into an account.
I covered OAuth in general in an earlier post. This one zooms into the exact thing most of us actually build: Google login in a Laravel app. What happens, what the token really contains, and the checks you shouldn't skip.
OAuth vs OpenID Connect, in two sentences
OAuth 2.0 is about access: "let this app read my calendar". It hands out access tokens and says nothing about who the user is.
OpenID Connect (OIDC) is a thin layer on top of OAuth that adds identity: "this is user X, and Google vouches for it". "Sign in with Google" is OIDC. The key addition is the ID token.
What happens after the click
- Your app redirects the browser to Google with your client ID, the scopes you want (
openid email profile), the URL to come back to, and a randomstatevalue it has also saved in the user's session. - Google handles the login. Password, two-factor, account picker, consent screen. Your app never sees any of it, which is the whole point.
- Google redirects back to your callback URL with a short-lived authorisation
codeand the samestate. - Your app checks the
stateagainst the session. If it doesn't match, someone may be trying to slip their own login into your user's browser, so you stop. - Your server exchanges the code with Google, server to server, using your client secret. Back comes an access token and an ID token.
- Your app reads who the user is, finds or creates their account, and starts a normal session.
Notice what never touches the browser: the client secret and the token exchange. That's why the authorisation code flow is the right one for server-rendered apps. (For apps that can't keep a secret, like SPAs and mobile apps, the same flow is used with PKCE, which proves the app that started the login is the same one finishing it.)
What's inside the ID token
An ID token is a JWT: a signed JSON payload. Decoded, the interesting part looks something like this:
{
"iss": "https://accounts.google.com",
"aud": "1234567890-abc.apps.googleusercontent.com",
"sub": "110169484474386276334",
"email": "[email protected]",
"email_verified": true,
"name": "Sara Ahmed",
"iat": 1786000000,
"exp": 1786003600
}
iss: who issued it. Must be Google.aud: who it was issued for. Must be your client ID, not some other app's.sub: the user's permanent, unique Google ID. This is the one to store.email_verified: whether Google has verified the email address.exp: when it expires.
The most important line there is sub. Emails can change. sub doesn't.
The Laravel side with Socialite
Laravel Socialite handles the redirect, the state check and the code exchange for you. After adding your Google credentials to config/services.php, the two routes look like this (simplified):
Route::get('/auth/google', fn () => Socialite::driver('google')->redirect());
Route::get('/auth/google/callback', function () {
$google = Socialite::driver('google')->user();
abort_unless($google->user['email_verified'] ?? false, 403);
$user = User::firstWhere('google_id', $google->getId());
if (! $user) {
abort_if(User::where('email', $google->getEmail())->exists(), 409,
'Log in with your password first, then connect Google.');
$user = User::create([
'google_id' => $google->getId(),
'name' => $google->getName(),
'email' => $google->getEmail(),
]);
}
Auth::login($user, remember: true);
return redirect()->intended('/dashboard');
});
In a real app you'd show a friendly page instead of the aborts, and you'd catch Socialite's InvalidStateException (an expired session or a replayed callback) and send the user back to the login page.
The checks that actually matter
Match on sub, not on email
❌ "Find the user with this email and log them in."
✅ Store Google's ID (sub, which Socialite returns from getId()) and look users up by it. Email is for contact, not for identity.
Be careful when linking to an existing account
The tricky case: someone signs in with Google, and a password account with that email already exists. Auto-linking feels helpful, but it means anyone who controls that email at a provider can walk into the existing account. The safer pattern, used above, is to ask the user to log in the old way first and then connect Google from their settings. If you do auto-link, at least require email_verified.
Don't turn off state
Socialite has a stateless() method for API-style flows. In a normal web app, leave it alone: the state check is your protection against login CSRF.
Restricting to a company domain? Verify it yourself
If only your organisation's Google Workspace accounts should get in, passing a domain hint to Google just changes the account picker. It isn't enforcement. Check the hd claim (the Workspace domain) on the server, and reject anything else.
Mobile apps: verify the token on the server
A React Native app using Google's native sign-in gets an ID token and sends it to your API. Never trust it just because it decodes. Verify the signature against Google's public keys and check iss, aud and exp, ideally with Google's official client library rather than hand-rolled JWT code. A token issued for some other app has a different aud, and that check is what stops someone replaying it against yours.
The rule: Google proves who the user is. Your app still decides which account that person gets and what they're allowed to do. Identity comes from sub; authorisation stays yours.
Checklist
- Authorisation code flow, client secret only on the server (PKCE for public clients).
statechecked on every callback.- Users matched by
sub, not email. - No silent linking to existing password accounts.
- Domain restrictions enforced server-side.
- ID tokens from mobile apps verified: signature,
iss,aud,exp.
The official Google OpenID Connect guide and the Laravel Socialite docs are both worth a read before you ship.
Do you link social logins to existing accounts automatically, or make users connect them by hand? I'd like to hear how others handle that edge case.

Be first to comment it...