consoletvs/identify
Identify is a Laravel package that lets users authenticate with a username, email address, or other custom identifier instead of a single fixed field. Add the trait, configure allowed identifiers, and log in flexibly.
consoletvs/identify is a Laravel 5-era wrapper around user-agent parsing that exposes a facade for detecting a request's browser, operating system, device, and language. It is a fork of unicodeveloper/laravel-identify, so the public API mirrors that package. The last release was September 2019 and the upstream repo is unlisted, so treat the source in vendor/consoletvs/identify/src as the documentation.
Install
composer require consoletvs/identify
On Laravel 5.5+ the service provider is auto-discovered. On 5.3/5.4, register it manually in config/app.php:
'providers' => [
// ...
ConsoleTVs\Identify\IdentifyServiceProvider::class,
],
'aliases' => [
// ...
'Identify' => ConsoleTVs\Identify\Facades\Identify::class,
],
Check whether the package ships a config file and publish it if so:
php artisan vendor:publish --provider="ConsoleTVs\Identify\IdentifyServiceProvider"
Look first at: src/Identify.php and src/Facades/Identify.php. That tells you the exact method names (browser, os, device, language variants) and the return shape, which differs between this fork and the original. Do this before writing any code against it — the fork may have renamed methods or changed return types.
First use case: log or display client data for an incoming request.
use ConsoleTVs\Identify\Facades\Identify;
public function show(Request $request)
{
$ua = $request->userAgent();
return view('welcome', [
'browser' => Identify::browser($ua),
'os' => Identify::os($ua),
'device' => Identify::device($ua),
'language' => Identify::language(), // usually derived from Accept-Language
]);
}
If the parser methods default to reading the current request when called with no argument, Identify::browser() is enough. Confirm from source before relying on that.
Middleware that annotates the request once. The most reliable integration is to parse the UA a single time per request and stash the result, so controllers, views, and listeners all read the same values.
namespace App\Http\Middleware;
use Closure;
use ConsoleTVs\Identify\Facades\Identify;
class IdentifyClient
{
public function handle($request, Closure $next)
{
$ua = $request->userAgent();
$request->attributes->set('client', [
'browser' => Identify::browser($ua),
'os' => Identify::os($ua),
'device' => Identify::device($ua),
'language' => Identify::language(),
]);
return $next($request);
}
}
Register it in app/Http/Kernel.php after StartSession but before anything that reads client.
Controller/Blade consumption. Read from the annotated attribute rather than calling the facade repeatedly:
$client = $request->attributes->get('client');
@if (($client['device'] ?? null) === 'mobile')
@include('partials.mobile-nav')
@endif
Audit / analytics logging. This is the classic use case — a login_attempts or sessions table capturing browser, OS, and device at authentication time. Store the raw UA string alongside the parsed result so you can re-parse historical rows later with a better library.
LoginAttempt::create([
'user_id' => $user->id,
'user_agent' => $request->userAgent(),
'browser' => Identify::browser($request->userAgent()),
'os' => Identify::os($request->userAgent()),
'device' => Identify::device($request->userAgent()),
'ip' => $request->ip(),
]);
Cache by UA. Parsing is not free and the same UA repeats constantly. Memoize:
$client = Cache::remember('client:'.md5($ua), now()->addDay(), function () use ($ua) {
return [
'browser' => Identify::browser($ua),
'os' => Identify::os($ua),
'device' => Identify::device($ua),
];
});
Beware cache keys driven by attacker-controlled UA strings — hashing mitigates key bloat; consider capping UA length before hashing.
Language-based routing. Use the detected language to seed App::setLocale() when no explicit user preference exists:
$lang = Identify::language();
app()->setLocale(in_array($lang, config('app.locales')) ? $lang : config('app.fallback_locale'));
Always validate against a whitelist — do not feed the detected value straight into setLocale().
Wrap it behind your own interface. Because the package is unmaintained, bind your own contract and let the facade be one implementation:
interface ClientDetector { public function detect(string $ua): ClientInfo; }
$this->app->bind(ClientDetector::class, IdentifyDetector::class);
This lets you swap to matomo/device-detector or WhichBrowser/Parser-PHP later without touching call sites.
Testing. Drive the UA via request headers rather than mocking the facade:
$this->withHeader('User-Agent', 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2 like Mac OS X)...')
->get('/')
->assertSee('mobile');
Also test an empty UA ('') and a bot UA, since those are the cases that break naive code.
Maintenance risk. Last release 2019. Composer will likely refuse to install on Laravel 8+ because the illuminate/support constraint is pinned to 5.x. Options: fork and widen the constraint, use composer require with a patched fork via a VCS repo, or replace the parser. Do not force-install into a modern app and assume it works.
Verify the API surface first. The fork may have diverged from unicodeveloper/laravel-identify. Method names, return types (array vs. object vs. string), and whether methods accept a UA string or read the request implicitly all need checking in src/.
Return values are not guaranteed. Empty, malformed, or CLI user agents commonly produce null or an "unknown" sentinel. Every call site needs a null/default fallback:
$browser = Identify::browser($ua) ?? ['name' => 'Unknown', 'version' => null];
User-Agent is attacker-controlled. It is trivially spoofed, truncated, or missing. Never use detection for security decisions (e.g., "mobile users don't need CSRF") — only for presentation and analytics. Sanitize before storing; column should be text or truncated varchar(512).
Bots and crawlers. Depending on the underlying parser, Googlebot and friends may come back as a normal browser. If you branch UI on device type, add a bot check (jaybizzle/crawler-detect or a simple regex on bot|crawl|spider) before trusting "mobile".
Repeated parsing is wasteful. Calling Identify::browser(), ::os(), and ::device() separately on the same UA often re-parses the string three times. Cache or parse once in middleware.
Language detection is not the same as locale negotiation. The package reads Accept-Language (via Laravel's request helpers); that header is ordered by preference and can include q-values and wildcards. Always whitelist against your supported locales and fall back to config('app.fallback_locale').
Middleware ordering matters. If you read client in a global middleware placed before your IdentifyClient middleware, it will be null. Place it after session/start but before anything that consumes the data.
Auto-discovery only works on 5.5+. Forgetting the provider/alias registration on older 5.x produces a Class 'Identify' not found or Target [ConsoleTVs\Identify\Contracts\...] is not instantiable error. The latter usually means the provider is registered but the config/binding wasn't published or the facade points at an unbound contract.
Config quirks. If the provider publishes a config file, the facade may read parser options (e.g., which detection library to use) from it. Publish it and commit — reading config that doesn't exist in a fresh environment is a common deploy failure.
Extension points. The cleanest escape hatch is a custom facade accessor or a rebind of the underlying parser contract in a service provider. Combined with the ClientDetector interface pattern above, this makes migrating off the package a one-line DI change.
Debugging tip. When results look wrong, dump the exact string the parser received (dd($request->server('HTTP_USER_AGENT'))) into the same call you make in production — a mismatch between $request->userAgent() and a manually passed UA is the usual culprit.
Pin the version. Add an exact constraint ("consoletvs/identify": "1.0.*") in composer.json so a composer update can't pull an unexpected fork commit, and note in your README that the package is effectively frozen.
How can I help you explore Laravel packages today?