How to restrict Google sign-in to an institutional domain in Laravel
When an organization wants only its own people to access an application (for example, @university.edu emails), it is tempting to rely on Google’s account chooser. That is a common mistake: restricting the domain on the button does not protect your application. This article shows how to do it properly with Laravel Socialite.
1. The hd parameter is only a hint
Google accepts the hd (hosted domain) parameter so the account chooser lists accounts from that domain first. But anyone building the authorization URL by hand can remove or change it, so it is not a security barrier.
return Socialite::driver('google')
->with(['hd' => 'university.edu'])
->redirect();
Use it for user convenience, never as access control.
2. Validate the domain in the callback
The check that matters happens on the server, using the data Google returns:
$googleUser = Socialite::driver('google')->user();
$email = strtolower($googleUser->getEmail());
$allowedDomain = 'university.edu';
abort_unless(str_ends_with($email, '@'.$allowedDomain), 403);
Two details that prevent subtle bugs:
- Compare against
'@'.$domain, not just the domain. Withstr_ends_with($email, 'university.edu'),someone@fakeuniversity.eduwould also pass. - Lowercase the address before comparing.
3. Check that the email is verified
Google’s response says whether the email was verified. Require it before creating or linking an account:
abort_unless(($googleUser->user['email_verified'] ?? false) === true, 403);
4. Create or update the user and assign a role
Once validated, look the user up by email and give them a default role with the fewest privileges. Elevated permissions are granted explicitly later, never inferred from the email address.
$user = User::firstOrCreate(
['email' => $email],
['name' => $googleUser->getName(), 'password' => Str::random(40)],
);
Auth::login($user);
5. Test it
Write at least three tests: an email from the allowed domain gets in, an email from another domain gets a 403, and a lookalike domain (fakeuniversity.edu) also gets a 403. They protect you from regressions when someone refactors the callback.
Summary
| Layer | What it is for | Is it security? |
|---|---|---|
hd parameter |
Better account-chooser experience | No |
| Server-side domain validation | Real access control | Yes |
email_verified |
Reject unconfirmed emails | Yes |
| Least-privilege default role | Limit the blast radius | Yes |