L

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.

~15 minEasylaravel/socialite (OIDC)
Try OAuth Flow
Integration progress
0/16

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
composer require laravel/socialite socialiteproviders/generic

04Required 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").

config/services.php
'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,
    ],
];
Add 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().

.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/callback
Run php 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: openid email profile (add offline_access for refresh)
  • Add your redirect URI (next step)
  • Copy the client_id and client_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.

redirect URIs
# Development
http://localhost:8000/auth/callback

# Production
https://app.example.com/auth/callback
A mismatch is the #1 integration error. http://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 & endpoints
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.json

09Authorization Flow

Add a login route that redirects to NIA with the required scopes. Socialite generates and stores state in the session for you.

routes/web.php
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.

app/Http/Controllers/Auth/NiaAuthController.php
<?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:

app/Services/NiaIdToken.php
<?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;
    }
}
Composer package 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:

routes/web.php
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.

app/Services/NiaAuth.php
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,
    ];
}
If your driver doesn't expose 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.

app/Http/Controllers/Auth/NiaAuthController.php
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) and APP_ENV=production
  • Enable secure session cookies: SESSION_SECURE_COOKIE=true, SESSION_SAME_SITE=lax
  • Use a persistent session driver (database/redis) behind multiple workers
.env (production)
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/callback

16Troubleshooting

  • 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; run php artisan config:clear.
  • Driver [nia] not defined — the SocialiteProviders event listener isn't registered, or config/services.php is missing the nia block.
  • mismatching_state / InvalidStateException — the session cookie isn't surviving the redirect. Check SESSION_DOMAIN, SESSION_SECURE_COOKIE, and SESSION_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.