PHP
Sign in with NIA — Laravel
Add “Sign in with NIA” to a Laravel app with Socialite and the standard OpenID Connect flow. Follow the steps top to bottom — tick each one off as you go.
01Overview
This guide adds “Sign in with NIA” to a Laravel application using the standard OpenID Connect flow. NIA is a conformant OIDC provider, so you use laravel/socialite together with a generic OAuth2/OIDC Socialite driver — no NIA-specific SDK is required.
Laravel runs as a confidential client: the browser is redirected to NIA to sign in, and your Laravel server exchanges the returned code for tokens using your client secret. Socialite handles the OAuth2 redirect and state; the generic OIDC driver validates the ID token.
- Flow: Authorization Code + PKCE (S256)
- Tokens: RS256 access & ID tokens, opaque refresh token
- You write: a Socialite driver config, and two routes — login and callback
02Requirements
- PHP 8.2+ and Laravel 11
- A NIA account with access to the client dashboard
- A registered OAuth client (
client_id+client_secret) - Your app's redirect URI registered on that client
- Laravel's default session driver enabled (cookie/database/redis)
03Installation
Install Socialite and the community SocialiteProviders package, which lets you register arbitrary OAuth2/OIDC providers like NIA without writing HTTP plumbing by hand.
composer require laravel/socialite socialiteproviders/generic04Required OAuth library
Register NIA's endpoints as a Socialite service in config/services.php. This is the single source of truth Socialite reads from when you call Socialite::driver("nia").
'nia' => [
'client_id' => env('NIA_CLIENT_ID'),
'client_secret' => env('NIA_CLIENT_SECRET'),
'redirect' => env('NIA_REDIRECT_URI'),
'host' => env('NIA_ISSUER'),
'authorize_uri' => env('NIA_ISSUER').'/oauth/authorize',
'token_uri' => env('NIA_ISSUER').'/oauth/token',
'userinfo_uri' => env('NIA_ISSUER').'/oauth/userinfo',
],Register the SocialiteProviders event listener so Socialite knows how to build the nia driver from that config:
use SocialiteProviders\Manager\SocialiteWasCalled;
use App\Listeners\AddNiaProvider;
protected $listen = [
SocialiteWasCalled::class => [
AddNiaProvider::class,
],
];offline_access to the requested scopes only if you need refresh tokens (see step 14).05Environment Variables
Never hard-code secrets. Keep them in .env (git-ignored) — config/services.php reads them via env().
NIA_ISSUER=https://dev-api.niaauth.com
NIA_CLIENT_ID=your-client-id
NIA_CLIENT_SECRET=your-client-secret
NIA_REDIRECT_URI=https://app.example.com/auth/callbackphp artisan config:clear after editing .env if your app has cached config — Laravel won't pick up new values otherwise.06Register OAuth Client
In the NIA dashboard, create the client your Laravel app will authenticate as:
- Open Dashboard → Clients → New client
- Client type: Web (confidential)
- Allowed scopes:
openidemailprofile(addoffline_accessfor refresh) - Add your redirect URI (next step)
- Copy the
client_idandclient_secret— the secret is shown once
07Redirect URI
NIA matches redirect URIs exactly — scheme, host, port, and path must be identical, with no trailing-slash difference. Register one URI per environment.
# Development
http://localhost:8000/auth/callback
# Production
https://app.example.com/auth/callbackhttp://localhost:8000/auth/callback/ (trailing slash) will be rejected.08Issuer
The issuer is the single URL every OIDC library needs. NIA's discovery document advertises all endpoints below.
Issuer: https://dev-api.niaauth.com
Discovery: https://dev-api.niaauth.com/.well-known/openid-configuration
Authorization: https://dev-api.niaauth.com/oauth/authorize
Token: https://dev-api.niaauth.com/oauth/token
UserInfo: https://dev-api.niaauth.com/oauth/userinfo
JWKS: https://dev-api.niaauth.com/oauth/jwks.json09Authorization Flow
Add a login route that redirects to NIA with the required scopes. Socialite generates and stores state in the session for you.
use Illuminate\Support\Facades\Route;
use Laravel\Socialite\Facades\Socialite;
use App\Http\Controllers\Auth\NiaAuthController;
Route::get('/auth/login', function () {
return Socialite::driver('nia')
->scopes(['openid', 'email', 'profile'])
->redirect();
});
Route::get('/auth/callback', [NiaAuthController::class, 'callback']);
Route::post('/auth/logout', [NiaAuthController::class, 'logout'])
->middleware('auth');10Token Exchange
On callback, Socialite exchanges the authorization code for tokens and fetches the user profile. Find-or-create a local user, log them in, and stash the tokens you'll need later.
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Laravel\Socialite\Facades\Socialite;
class NiaAuthController extends Controller
{
public function callback()
{
$niaUser = Socialite::driver('nia')->user();
$user = User::updateOrCreate(
['nia_sub' => $niaUser->getId()],
[
'name' => $niaUser->getName() ?? $niaUser->getNickname(),
'email' => $niaUser->getEmail(),
]
);
Auth::login($user, remember: true);
session([
'nia_id_token' => $niaUser->accessTokenResponseBody['id_token'] ?? null,
'nia_refresh' => $niaUser->refreshToken,
]);
return redirect('/');
}
}11JWKS Validation
NIA signs ID tokens with RS256. If you use an OIDC-aware Socialite driver, the ID-token signature, iss, aud, and exp are validated for you during the token exchange.
If you instead used the bare generic OAuth2 driver above, verify the id_token yourself before trusting its claims:
<?php
namespace App\Services;
use Firebase\JWT\JWT;
use Firebase\JWT\JWK;
use Illuminate\Support\Facades\Http;
class NiaIdToken
{
public static function verify(string $idToken): array
{
$jwks = Http::get(config('services.nia.host').'/oauth/jwks.json')->json();
$keys = JWK::parseKeySet($jwks);
$claims = (array) JWT::decode($idToken, $keys);
abort_unless($claims['iss'] === config('services.nia.host'), 401, 'Bad issuer');
abort_unless($claims['aud'] === config('services.nia.client_id'), 401, 'Bad audience');
return $claims;
}
}firebase/php-jwt provides the JWT/JWKS helpers used above.12Session Management
Once Auth::login() has run, use Laravel's built-in authentication and session framework as normal. Protect routes with the standard auth middleware:
Route::middleware('auth')->group(function () {
Route::get('/dashboard', fn () => view('dashboard'));
});13Refresh Tokens
If you requested offline_access, NIA returns a refresh token. Exchange it for a fresh access token when the current one expires. NIA rotates refresh tokens, so always persist the new one.
use Laravel\Socialite\Facades\Socialite;
public function refresh(string $refreshToken): array
{
$newToken = Socialite::driver('nia')->refreshToken($refreshToken);
// NIA rotates the refresh token — persist the replacement.
session(['nia_refresh' => $newToken->refreshToken ?? $refreshToken]);
return [
'access_token' => $newToken->token,
'expires_in' => $newToken->expiresIn,
];
}refreshToken(), POST directly to https://dev-api.niaauth.com/oauth/token with grant_type=refresh_token.14Logout
Log the user out of Laravel and send them to NIA's end-session endpoint so they are signed out of NIA too. Register your post_logout_redirect_uri on the client.
use Illuminate\Support\Facades\Auth;
public function logout()
{
$idToken = session('nia_id_token');
Auth::logout();
session()->invalidate();
session()->regenerateToken();
$params = http_build_query([
'id_token_hint' => $idToken ?? '',
'post_logout_redirect_uri' => url('/'),
]);
return redirect(config('services.nia.host')."/oauth/logout?{$params}");
}15Deployment
Before going live:
- Register the production redirect URI (HTTPS) on the client
- Set all env vars on your host — never commit
.env - Set a strong, unique
APP_KEY(php artisan key:generate) andAPP_ENV=production - Enable secure session cookies:
SESSION_SECURE_COOKIE=true,SESSION_SAME_SITE=lax - Use a persistent session driver (database/redis) behind multiple workers
APP_ENV=production
APP_URL=https://app.example.com
SESSION_DRIVER=redis
SESSION_SECURE_COOKIE=true
SESSION_SAME_SITE=lax
NIA_REDIRECT_URI=https://app.example.com/auth/callback16Troubleshooting
- redirect_uri_mismatch — the URI must match the registered one exactly (no trailing slash, correct scheme/port).
- invalid_client — wrong
client_secret, or a stale cached config; runphp artisan config:clear. - Driver [nia] not defined — the SocialiteProviders event listener isn't registered, or
config/services.phpis missing theniablock. - mismatching_state / InvalidStateException — the session cookie isn't surviving the redirect. Check
SESSION_DOMAIN,SESSION_SECURE_COOKIE, andSESSION_SAME_SITE="lax". - invalid_scope — the scope isn't in the client's
allowed_scopes; add it in the dashboard. - ID-token signature — usually server clock skew; sync time via NTP.