PHPackages                             unimontes-cead/sso-client - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Authentication &amp; Authorization](/categories/authentication)
4. /
5. unimontes-cead/sso-client

ActiveLibrary[Authentication &amp; Authorization](/categories/authentication)

unimontes-cead/sso-client
=========================

Socialite driver and client-app integration helpers for the UNIMONTES CEAD SSO.

1.1.0(1mo ago)06MITPHPPHP ^8.2

Since Jul 10Pushed 1mo agoCompare

[ Source](https://github.com/suporte-sistemas-cead/sso-client)[ Packagist](https://packagist.org/packages/unimontes-cead/sso-client)[ RSS](/packages/unimontes-cead-sso-client/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (4)Dependencies (10)Versions (4)Used By (0)

unimontes-cead/sso-client
=========================

[](#unimontes-ceadsso-client)

Laravel Socialite driver + login glue for applications that authenticate through the UNIMONTES CEAD SSO (a Laravel Passport authorization server).

What this package does
----------------------

[](#what-this-package-does)

- Registers a `sso` Socialite driver against the SSO's own OAuth2 endpoints (`/oauth/authorize`, `/oauth/token`, `/api/user`) — no manual `config/services.php` entry needed.
- Exposes the data returned by the SSO as a typed `SsoUser` DTO (`id`, `name`, `email`, `cpf`, `phone`, `avatarUrl`, `roles`) instead of a generic array. Every field but `id` is nullable: the SSO omits a field entirely when this client wasn't granted the matching scope, so `null`means "not authorized to see this", not "empty".
- `roles` is an array of `SsoUserRole` (`name`, `value`, `startDate`, `endDate`) — every role the user currently holds on the SSO (a user can hold more than one at once, each with its own date range). `value` is typed as the `SsoRole` enum — the SSO's full, closed set of possible roles — so you know exactly what can come back and can map your own local roles against it (e.g. a `match` on `$role->value`) instead of guessing at an opaque ID. `SsoUser::hasRole(SsoRole $role): bool` is a shortcut for checking membership.
- Defines `Contracts\SsoAuthenticatable`, an interface your own `User` model implements to say how SSO data maps to a local user.
- Optionally registers ready-made `GET {prefix}/redirect`, `GET {prefix}/callback`, and `POST {prefix}/logout` routes that do the full login/logout round-trip for you.
- `SsoSyncClient` pushes local edits back up to the SSO — the mirror image of the login flow above — so a "my profile" or "change password" screen in your app can keep the SSO's own copy of the user in sync instead of drifting out of date.

### Scopes are the SSO's decision, not this app's

[](#scopes-are-the-ssos-decision-not-this-apps)

The driver always requests every scope the SSO knows about (`name,email,cpf,phone,avatar,role`) — there's no `SSO_SCOPES` env var to configure here. That's intentional: the SSO's `ScopeRepository` clips whatever's requested down to whatever an admin actually granted *this*client on the SSO's own "Aplicações" screen, regardless of what's asked for. So requesting everything unconditionally is safe — you only ever get back what the SSO already decided this client is allowed to see — and it means scope configuration lives in exactly one place (the SSO), not duplicated into every client's `.env`.

Installation
------------

[](#installation)

```
composer require unimontes-cead/sso-client
composer require laravel/socialite
```

Laravel's package auto-discovery registers `SsoClientServiceProvider`automatically — no manual entry in `bootstrap/providers.php` needed.

Configuration
-------------

[](#configuration)

Publish the config file:

```
php artisan vendor:publish --tag=sso-config
```

Set these in `.env` (get `SSO_CLIENT_ID`/`SSO_CLIENT_SECRET` from an admin on the SSO's "Aplicações" screen — its "URL inicial" doesn't need to point here, but its registered redirect URIs must include your callback URL):

```
SSO_BASE_URL=https://sso.unimontes.example
SSO_CLIENT_ID=
SSO_CLIENT_SECRET=
# Only needed if you call SsoSyncClient (see "Syncing data back to the
# SSO" below) — a separate credential from SSO_CLIENT_SECRET, generated
# on the same "Aplicações" screen, under "Token de API".
# SSO_API_TOKEN=
# Only needed if you disable the built-in routes below and drive
# Socialite yourself with a different callback path.
# SSO_REDIRECT_URI=https://your-app.example/auth/sso/callback
SSO_ROUTES_ENABLED=true
SSO_USER_MODEL=App\Models\User
```

Implementing the contract
-------------------------

[](#implementing-the-contract)

Your `App\Models\User` must implement `SsoAuthenticatable::fromSsoData()` — it receives the `SsoUser` DTO and returns the local user to log in as, typically an `updateOrCreate()` keyed on the SSO's `id`:

```
use UnimontesCead\SsoClient\Contracts\SsoAuthenticatable;
use UnimontesCead\SsoClient\SsoUser;

class User extends Authenticatable implements SsoAuthenticatable
{
    public static function fromSsoData(SsoUser $ssoUser): static
    {
        return static::updateOrCreate(
            ['sso_id' => $ssoUser->id],
            [
                'name' => $ssoUser->name,
                'email' => $ssoUser->email,
                'cpf' => $ssoUser->cpf,
                'phone' => $ssoUser->phone,
                'avatar_url' => $ssoUser->avatarUrl,
            ],
        );
    }

    public function ssoId(): string
    {
        return $this->sso_id;
    }
}
```

You'll need a `sso_id` column (string/uuid, unique) on your `users` table to key that lookup on. `ssoId()` is what `SsoSyncClient` uses to address this user on the SSO when pushing edits back (see "Syncing data back to the SSO" below).

### Using roles

[](#using-roles)

`$ssoUser->roles` is `null` when this client wasn't granted the `role`scope, otherwise an array of `SsoUserRole`, each with a `value: SsoRole`. `SsoRole` is a plain PHP enum listing every role the SSO can ever assign (`Administrador`, `CoordenadorGeral`, `EquipeMultidisciplinar`, `EquipePedagogica`, `EquipeDeMonitoramento`, `CoordenadorDeCurso`, `CoordenadorDeTutoria`, `Professor`, `Tutor`, `Aluno`) — a deliberate copy of the SSO's own `Role` enum, kept in sync by hand, so you get autocomplete and a compile-time-checked `match` instead of comparing against a raw int.

A common pattern is mapping each SSO role to your own local role:

```
use UnimontesCead\SsoClient\SsoRole;
use UnimontesCead\SsoClient\SsoUserRole;

$localRoles = $ssoUser->roles !== null
    ? array_map(fn (SsoUserRole $role) => match ($role->value) {
        SsoRole::Administrador, SsoRole::CoordenadorGeral => 'admin',
        SsoRole::Professor, SsoRole::Tutor => 'staff',
        default => 'member',
    }, $ssoUser->roles)
    : [];

$user->roles()->sync($localRoles);

// or a quick membership check, e.g. gating admin-only UI:
if ($ssoUser->hasRole(SsoRole::Administrador)) { /* ... */ }
```

If you'd rather use the SSO's roles as your local roles directly (no mapping), just persist `$role->value->value` (the enum's backing int) or `$role->value->name` instead.

Logging in
----------

[](#logging-in)

With the built-in routes (default), just link to the redirect route:

```
Entrar com o SSO
```

That's the whole flow — it exchanges the code, fetches `/api/user`, calls your `User::fromSsoData()`, logs the user in, and redirects to `sso.routes.redirect_after_login`.

Logging out
-----------

[](#logging-out)

```

    @csrf
    Sair

```

This ends the local session *and* redirects the browser to the SSO's own single-logout endpoint (`GET /oauth/logout/{client_id}` on the SSO), which ends the SSO's session and revokes every OAuth token this user has been issued, then bounces back to this client's registered "URL inicial".

**What this does and doesn't do:** the SSO won't silently re-authenticate the user anymore, so the next "entrar com o SSO" — in this app or any other — requires fresh credentials. It does **not** reach into other client apps' already-open local sessions and close them; that would require a back-channel logout protocol (each client exposing its own logout webhook that the SSO calls server-to-server) which this package doesn't implement. For most internal tooling, killing the SSO session is the meaningful "logout everywhere" boundary — treat true simultaneous multi-tab/multi-app session termination as a separate, bigger feature if you need it.

Syncing data back to the SSO
----------------------------

[](#syncing-data-back-to-the-sso)

Logging in pulls the user's data *down* from the SSO. If your app has its own "meu perfil" or "alterar senha" screen, `SsoSyncClient` pushes those edits back *up*, so the SSO's own copy doesn't drift out of date.

It's resolved from the container, so just type-hint it:

```
use UnimontesCead\SsoClient\Exceptions\SsoSyncException;
use UnimontesCead\SsoClient\SsoSyncClient;

class ProfileController extends Controller
{
    public function update(Request $request, SsoSyncClient $sync)
    {
        $user = $request->user();

        $user->update($request->validated());

        try {
            $sync->updateProfile($user, $request->only(['name', 'email', 'cpf', 'phone', 'avatar_url']));
        } catch (SsoSyncException $e) {
            report($e); // local save already succeeded; sync is best-effort
        }

        return back();
    }

    public function updatePassword(Request $request, SsoSyncClient $sync)
    {
        $data = $request->validate(['password' => ['required', 'confirmed', Password::defaults()]]);

        $request->user()->update(['password' => $data['password']]);

        $sync->updatePassword($request->user(), $data['password']);

        return back();
    }
}
```

A few things worth knowing:

- **Which fields actually land is the SSO's decision, not yours.**`updateProfile()` accepts any subset of `name`, `email`, `cpf`, `phone`, `avatar_url` and returns the field names the SSO *actually* applied — a field you sent can be silently missing from that list if this client wasn't granted the matching scope on the SSO's "Aplicações" screen. Same rule as reading data at login: this app never needs its own copy of which fields it's allowed to touch.
- **`avatar_url` is a URL, not a file upload.** The SSO fetches the image from that URL itself and stores it as the user's new avatar — nothing to stream or multipart-encode on your end. The URL must be publicly reachable by the SSO server, point directly at the image (jpeg, png, webp or gif, 2MB max), and stay valid at least until the SSO has fetched it. Pass `avatar_url: null` to clear the avatar.
- **Password sync is opt-in per client.** An admin has to explicitly enable "Permitir sincronização de senha" for this client on the SSO; otherwise `updatePassword()` throws `SsoSyncException`.
- **It's authenticated by `SSO_API_TOKEN`, not the user's OAuth token** — there's no token to refresh or that can expire mid-request, so it's safe to call from a background job as well as a request.
- **Both methods throw `SsoSyncException`** on any failure (network, bad credentials, validation rejected by the SSO). Treat it as best-effort: the local write already happened, so a sync failure shouldn't undo it or block the response — log it and move on, as in the example above.

### Rolling your own flow

[](#rolling-your-own-flow)

Set `SSO_ROUTES_ENABLED=false` and drive it yourself:

```
use Laravel\Socialite\Facades\Socialite;

Route::get('/login/sso', fn () => Socialite::driver('sso')->redirect());

Route::get('/login/sso/callback', function () {
    $ssoUser = Socialite::driver('sso')->user(); // SsoUser instance
    $user = \App\Models\User::fromSsoData($ssoUser);
    auth()->login($user, remember: true);
    return redirect('/');
});

Route::post('/logout/sso', function (\Illuminate\Http\Request $request) {
    auth()->guard('web')->logout();
    $request->session()->invalidate();
    $request->session()->regenerateToken();
    return redirect()->away(
        rtrim(config('sso.base_url'), '/').'/oauth/logout/'.config('sso.client_id'),
    );
});
```

###  Health Score

40

—

FairBetter than 86% of packages

Maintenance91

Actively maintained with recent releases

Popularity5

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity48

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 80% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~3 days

Total

3

Last Release

41d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/02b8ce62b10ce1ec68acce420bafe3d4a5e5df71e110ebe237390be82986603f?d=identicon)[Suporte Técnico Sistemas CEAD](/maintainers/Suporte%20T%C3%A9cnico%20Sistemas%20CEAD)

---

Top Contributors

[![henriquecardososouza](https://avatars.githubusercontent.com/u/82218299?v=4)](https://github.com/henriquecardososouza "henriquecardososouza (4 commits)")[![suporte-sistemas-cead](https://avatars.githubusercontent.com/u/227240066?v=4)](https://github.com/suporte-sistemas-cead "suporte-sistemas-cead (1 commits)")

### Embed Badge

![Health badge](/badges/unimontes-cead-sso-client/health.svg)

```
[![Health](https://phpackages.com/badges/unimontes-cead-sso-client/health.svg)](https://phpackages.com/packages/unimontes-cead-sso-client)
```

###  Alternatives

[laravel/socialite

Laravel wrapper around OAuth 1 &amp; OAuth 2 libraries.

5.7k113.1M1.0k](/packages/laravel-socialite)[laravel/boost

Laravel Boost accelerates AI-assisted development by providing the essential context and structure that AI needs to generate high-quality, Laravel-specific code.

3.6k26.0M842](/packages/laravel-boost)[spatie/laravel-health

Monitor the health of a Laravel application

88412.7M191](/packages/spatie-laravel-health)[psalm/plugin-laravel

Psalm plugin for Laravel

3345.4M354](/packages/psalm-plugin-laravel)[illuminate/auth

The Illuminate Auth package.

9328.5M1.4k](/packages/illuminate-auth)[nativephp/mobile

NativePHP for Mobile

1.1k102.1k161](/packages/nativephp-mobile)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
