diff --git a/src/ZB.MOM.WW.ScadaBridge.Security/ScadaBridgeGroupRoleMapper.cs b/src/ZB.MOM.WW.ScadaBridge.Security/ScadaBridgeGroupRoleMapper.cs
new file mode 100644
index 00000000..02a23ee0
--- /dev/null
+++ b/src/ZB.MOM.WW.ScadaBridge.Security/ScadaBridgeGroupRoleMapper.cs
@@ -0,0 +1,40 @@
+using ZB.MOM.WW.Auth.Abstractions.Roles;
+
+namespace ZB.MOM.WW.ScadaBridge.Security;
+
+///
+/// Adapts ScadaBridge's DB-backed to the shared
+/// seam from ZB.MOM.WW.Auth.Abstractions.
+///
+///
+/// Task 1.1 of the Auth-library adoption: this is an additive wrapper. It does not
+/// re-implement the LDAP-group → role resolution or the site-scope union semantics — it
+/// delegates wholesale to and re-shapes the
+/// result onto the shared contract. is
+/// because ScadaBridge roles travel as plain strings in claims. The full
+/// — including
+/// and — is carried verbatim in the
+/// mapping's opaque so no site-scope information
+/// is lost across the seam. The existing login flow is rewired to consume this in a later task.
+///
+public sealed class ScadaBridgeGroupRoleMapper : IGroupRoleMapper
+{
+ private readonly RoleMapper _roleMapper;
+
+ /// Initializes the mapper with the wrapped .
+ /// The DB-backed role mapper whose union semantics are reused.
+ public ScadaBridgeGroupRoleMapper(RoleMapper roleMapper)
+ {
+ _roleMapper = roleMapper ?? throw new ArgumentNullException(nameof(roleMapper));
+ }
+
+ ///
+ public async Task> MapAsync(IReadOnlyList groups, CancellationToken ct)
+ {
+ var result = await _roleMapper.MapGroupsToRolesAsync(groups, ct);
+
+ // Carry the full RoleMappingResult as the opaque Scope so the site-scope
+ // payload (PermittedSiteIds + IsSystemWideDeployment) survives the seam.
+ return new GroupRoleMapping(result.Roles, Scope: result);
+ }
+}
diff --git a/src/ZB.MOM.WW.ScadaBridge.Security/ServiceCollectionExtensions.cs b/src/ZB.MOM.WW.ScadaBridge.Security/ServiceCollectionExtensions.cs
index 33074a80..97c6f785 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Security/ServiceCollectionExtensions.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Security/ServiceCollectionExtensions.cs
@@ -3,6 +3,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
+using ZB.MOM.WW.Auth.Abstractions.Roles;
namespace ZB.MOM.WW.ScadaBridge.Security;
@@ -18,6 +19,14 @@ public static class ServiceCollectionExtensions
services.AddScoped();
services.AddScoped();
+ // Auth-adoption Task 1.1: register the shared IGroupRoleMapper
+ // seam additively, wrapping RoleMapper to reuse its DB-backed mapping +
+ // site-scope union semantics. Scoped to match RoleMapper's lifetime (it
+ // depends on the Scoped ISecurityRepository). The existing RoleMapper
+ // registration and its call sites are left untouched — login is rewired
+ // to consume this seam in a later task.
+ services.AddScoped, ScadaBridgeGroupRoleMapper>();
+
// Security-020: register the IValidateOptions so a
// missing/empty LdapServer or LdapSearchBase fails fast at startup
// with a clear, key-naming message rather than a generic LDAP error
diff --git a/src/ZB.MOM.WW.ScadaBridge.Security/ZB.MOM.WW.ScadaBridge.Security.csproj b/src/ZB.MOM.WW.ScadaBridge.Security/ZB.MOM.WW.ScadaBridge.Security.csproj
index dabf2f2c..faa7131e 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Security/ZB.MOM.WW.ScadaBridge.Security.csproj
+++ b/src/ZB.MOM.WW.ScadaBridge.Security/ZB.MOM.WW.ScadaBridge.Security.csproj
@@ -14,6 +14,7 @@
+
diff --git a/tests/ZB.MOM.WW.ScadaBridge.Security.Tests/ScadaBridgeGroupRoleMapperTests.cs b/tests/ZB.MOM.WW.ScadaBridge.Security.Tests/ScadaBridgeGroupRoleMapperTests.cs
new file mode 100644
index 00000000..8ae32059
--- /dev/null
+++ b/tests/ZB.MOM.WW.ScadaBridge.Security.Tests/ScadaBridgeGroupRoleMapperTests.cs
@@ -0,0 +1,127 @@
+using ZB.MOM.WW.Auth.Abstractions.Roles;
+using ZB.MOM.WW.ScadaBridge.Commons.Entities.Security;
+using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
+using ZB.MOM.WW.ScadaBridge.Security;
+
+namespace ZB.MOM.WW.ScadaBridge.Security.Tests;
+
+#region Task 1.1: ScadaBridgeGroupRoleMapper (IGroupRoleMapper seam)
+
+public class ScadaBridgeGroupRoleMapperTests
+{
+ // A minimal in-memory ISecurityRepository so the seam test needs no database.
+ // Only the two reads RoleMapper.MapGroupsToRolesAsync uses are implemented;
+ // the rest throw to make any accidental dependency on them obvious.
+ private sealed class FakeSecurityRepository : ISecurityRepository
+ {
+ private readonly IReadOnlyList _mappings;
+ private readonly IReadOnlyDictionary> _scopeRules;
+
+ public FakeSecurityRepository(
+ IReadOnlyList mappings,
+ IReadOnlyDictionary> scopeRules)
+ {
+ _mappings = mappings;
+ _scopeRules = scopeRules;
+ }
+
+ public Task> GetAllMappingsAsync(CancellationToken cancellationToken = default)
+ => Task.FromResult(_mappings);
+
+ public Task> GetScopeRulesForMappingAsync(int ldapGroupMappingId, CancellationToken cancellationToken = default)
+ => Task.FromResult(_scopeRules.TryGetValue(ldapGroupMappingId, out var rules)
+ ? rules
+ : (IReadOnlyList)Array.Empty());
+
+ public Task GetMappingByIdAsync(int id, CancellationToken cancellationToken = default)
+ => throw new NotSupportedException();
+ public Task> GetMappingsByRoleAsync(string role, CancellationToken cancellationToken = default)
+ => throw new NotSupportedException();
+ public Task AddMappingAsync(LdapGroupMapping mapping, CancellationToken cancellationToken = default)
+ => throw new NotSupportedException();
+ public Task UpdateMappingAsync(LdapGroupMapping mapping, CancellationToken cancellationToken = default)
+ => throw new NotSupportedException();
+ public Task DeleteMappingAsync(int id, CancellationToken cancellationToken = default)
+ => throw new NotSupportedException();
+ public Task GetScopeRuleByIdAsync(int id, CancellationToken cancellationToken = default)
+ => throw new NotSupportedException();
+ public Task AddScopeRuleAsync(SiteScopeRule rule, CancellationToken cancellationToken = default)
+ => throw new NotSupportedException();
+ public Task UpdateScopeRuleAsync(SiteScopeRule rule, CancellationToken cancellationToken = default)
+ => throw new NotSupportedException();
+ public Task DeleteScopeRuleAsync(int id, CancellationToken cancellationToken = default)
+ => throw new NotSupportedException();
+ public Task SaveChangesAsync(CancellationToken cancellationToken = default)
+ => throw new NotSupportedException();
+ }
+
+ private static LdapGroupMapping Mapping(int id, string group, string role)
+ {
+ var m = new LdapGroupMapping(group, role);
+ m.Id = id;
+ return m;
+ }
+
+ [Fact]
+ public async Task MapAsync_ReturnsSameRolesAsRoleMapper_AndCarriesResultInScope_SiteScoped()
+ {
+ // Two matched mappings: an Admin group and a site-scoped Deployment group.
+ var mappings = new List
+ {
+ Mapping(1, "SCADA-Admins", Roles.Admin),
+ Mapping(2, "SiteDeployers", Roles.Deployment),
+ };
+ var scopeRules = new Dictionary>
+ {
+ [2] = new[]
+ {
+ new SiteScopeRule { LdapGroupMappingId = 2, SiteId = 11 },
+ new SiteScopeRule { LdapGroupMappingId = 2, SiteId = 22 },
+ },
+ };
+ var repo = new FakeSecurityRepository(mappings, scopeRules);
+ var roleMapper = new RoleMapper(repo);
+ var sut = new ScadaBridgeGroupRoleMapper(roleMapper);
+
+ var groups = new[] { "SCADA-Admins", "SiteDeployers" };
+
+ // The wrapped RoleMapper result is the oracle the seam must preserve.
+ var expected = await roleMapper.MapGroupsToRolesAsync(groups, CancellationToken.None);
+ var mapping = await sut.MapAsync(groups, CancellationToken.None);
+
+ // Roles: same set as RoleMapper.
+ Assert.Equal(expected.Roles.OrderBy(r => r), mapping.Roles.OrderBy(r => r));
+ Assert.Contains(Roles.Admin, mapping.Roles);
+ Assert.Contains(Roles.Deployment, mapping.Roles);
+
+ // Scope: carries the full RoleMappingResult (no site-scope info lost).
+ var scope = Assert.IsType(mapping.Scope);
+ Assert.False(scope.IsSystemWideDeployment);
+ Assert.Contains("11", scope.PermittedSiteIds);
+ Assert.Contains("22", scope.PermittedSiteIds);
+ Assert.Equal(expected.PermittedSiteIds.OrderBy(s => s), scope.PermittedSiteIds.OrderBy(s => s));
+ Assert.Equal(expected.IsSystemWideDeployment, scope.IsSystemWideDeployment);
+ }
+
+ [Fact]
+ public async Task MapAsync_PreservesSystemWideDeploymentFlagInScope()
+ {
+ // Unscoped Deployment mapping -> system-wide, empty PermittedSiteIds.
+ var mappings = new List
+ {
+ Mapping(1, "GlobalDeployers", Roles.Deployment),
+ };
+ var repo = new FakeSecurityRepository(mappings, new Dictionary>());
+ var roleMapper = new RoleMapper(repo);
+ var sut = new ScadaBridgeGroupRoleMapper(roleMapper);
+
+ var mapping = await sut.MapAsync(new[] { "GlobalDeployers" }, CancellationToken.None);
+
+ Assert.Contains(Roles.Deployment, mapping.Roles);
+ var scope = Assert.IsType(mapping.Scope);
+ Assert.True(scope.IsSystemWideDeployment);
+ Assert.Empty(scope.PermittedSiteIds);
+ }
+}
+
+#endregion