Librenms: Microsoft Oauth

So, after eventually falling blank with the documentations on getting the Socialite configs working with Microsoft Entra ( specifically the role mappings ). I had to patch the Socialite code to get it to work. The way Libre Maps scopes etc and groups is a bit backwards. But to get this to work. Set up per the documentations with Librenms.

lnms plugin:add socialiteproviders/microsoft

lnms config:set auth.socialite.configs.microsoft.client_id ds22389d-cea2-4a78-ab19-ee11213e90b1725
lnms config:set auth.socialite.configs.microsoft.client_secret 71148Q~L0Z21313eph0nS2kc7231sddddd2PmlaM9
lnms config:set auth.socialite.configs.microsoft.tenant 912sds56-8223-481112-8658e-4115166eb5

lnms config:set auth.socialite.configs.microsoft.listener “\SocialiteProviders\Microsoft\MicrosoftExtendSocialite”

lnms config:set auth.socialite.default_role global-read

lnms config:set auth.socialite.claims.sdf3c8a-9c2a-43sd-92c1-43fsdcc515d.roles ‘[“admin”]’

lnms config:set auth.socialite.scopes.0 “openid”
lnms config:set auth.socialite.scopes.1 “profile”
lnms config:set auth.socialite.scopes.2 “email”
lnms config:set auth.socialite.scopes.3 “https://graph.microsoft.com/User.Read”
lnms config:set auth.socialite.scopes.4 “https://graph.microsoft.com/Group.Read.All”

[ This is example data]

Make sure your user is part of the group you are using for the claims.

Now on your group claims:

click the hotdog, and edit:

you need to emit the claims as roles:

You now need to patch the Socialite Controller to actually read these.

/opt/librenms/app/Http/Controllers/Auth/SocialiteController.php

Look for the function setRolesFromClaim and add as per the comments below:

private function setRolesFromClaim(string $provider, $user): bool
{
    $scopes = LibrenmsConfig::get('auth.socialite.scopes');
    $claims = LibrenmsConfig::get('auth.socialite.claims');

    if (is_array($scopes) &&
        $this->socialite_user instanceof \Laravel\Socialite\AbstractUser &&
        ! empty($claims)
    ) {
        $roles = [];
        $attributes = $this->socialite_user->getRaw();

        if (is_object(current($attributes)) && method_exists(current($attributes), 'getName') && method_exists(current($attributes), 'getAllAttributeValues')) {
            $parsed_attributes = [];
            foreach ($attributes as $attribute_object) {
                $attribute_name = $attribute_object->getName();
                $attribute_values = $attribute_object->getAllAttributeValues();
                $parsed_attributes[$attribute_name] = $attribute_values;
            }
            $attributes = $parsed_attributes;
        }

        // CHECK FOR 'roles' ATTRIBUTE EXPLICITLY (for Microsoft groups emitted as roles)
        if (isset($attributes['roles']) && is_array($attributes['roles'])) {
            foreach ($attributes['roles'] as $group_id) {
                $roles = array_merge($roles, $claims[$group_id]['roles'] ?? []);
            }
        }

        // ORIGINAL LOGIC - check scope matching
        foreach ($scopes as $scope) {
            foreach ($attributes as $attribute_name => $attribute_values) {
                if (str_contains($attribute_name, $scope)) {
                    foreach (Arr::wrap($attributes[$attribute_name] ?? []) as $scope_data) {
                        $roles = array_merge($roles, $claims[$scope_data]['roles'] ?? []);
                    }
                }
            }
        }

        if (count($roles) > 0) {
            $user->syncRoles(array_unique($roles));

            return true;
        }
    }

    return false;
}

Hope this helps others that have been trying to get this working.

I’ve submitted a pull request to get this into the code base. Will see if its accepted.