Venky Venky - 1 month ago 12
C# Question

Authenticating a particular user to a particular client using IdentityServer

I am using

IdentityServerV3
for authenticating users for few clients. I am having problems configuring
IdentityServer
in such a way that a specific
user
must be able to login to a specific `client.

Let's take the below scenario :

I have 2 clients ->
client1
,
client2
.
I have 3 users ->
user1
,
user2
,
user3
.

user1
&
user2
have access to
client1
only. Whereas
user3
has access to
client2
alone.

When i try to login to
client1
with
user3
identity server is authenticating successfully. Which i don't want.

public static class Clients
{
public static IEnumerable<Client> Get()
{
return new[]
{
new Client
{
Enabled = true,
ClientName = "MVC Client",
ClientId = "mvc",
Flow = Flows.Hybrid,

RedirectUris = new List<string>
{
"https://localhost:44319/"
}
},
new Client
{
Enabled = true,
ClientName = "MVC Client 2",
ClientId = "mvc2",
Flow = Flows.Hybrid,

RedirectUris = new List<string>
{
"https://localhost:44319/"
}
}
};
}
}


Users are :

public static class Users
{
public static List<InMemoryUser> Get()
{
return new List<InMemoryUser>
{
new InMemoryUser
{
Username = "bob",
Password = "secret",
Subject = "1",

Claims = new[]
{
new Claim(Constants.ClaimTypes.GivenName, "Bob"),
new Claim(Constants.ClaimTypes.FamilyName, "Smith")
}
},
new InMemoryUser
{
Username = "rob",
Password = "secret",
Subject = "2",

Claims = new[]
{
new Claim(Constants.ClaimTypes.GivenName, "Rob"),
new Claim(Constants.ClaimTypes.FamilyName, "Thompson")
}
},
new InMemoryUser
{
Username = "Jerry",
Password = "secret",
Subject = "3",

Claims = new[]
{
new Claim(Constants.ClaimTypes.GivenName, "Jerry"),
new Claim(Constants.ClaimTypes.FamilyName, "Smith")
}
}
};
}
}


Now OWIN Startup class is :

public class Startup
{
public void Configuration(IAppBuilder app)
{
app.Map("/identity", idsrvApp =>
{
idsrvApp.UseIdentityServer(new IdentityServerOptions
{
SiteName = "Embedded IdentityServer",
SigningCertificate = LoadCertificate(),

Factory = InMemoryFactory.Create(
users : Users.Get(),
clients: Clients.Get(),
scopes : StandardScopes.All)
});
});
}

X509Certificate2 LoadCertificate()
{
return new X509Certificate2(
string.Format(@"{0}\bin\identityServer\idsrv3test.pfx", AppDomain.CurrentDomain.BaseDirectory), "idsrv3test");
}
}


I don't see how
IdentityServer
knows which user bleongs to which client. Is my scenario not supported by
IdentityServer
or am i missing anything.

UPDATE

public class LocalRegistrationUserService : UserServiceBase
{
public class CustomUser
{
public string Subject { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public List<Claim> Claims { get; set; }
}

public static List<CustomUser> Users = new List<CustomUser>();

public string ClientId { get; set; }

public override Task AuthenticateLocalAsync(LocalAuthenticationContext context)
{
var user = Users.SingleOrDefault(x => x.Username == context.UserName && x.Password == context.Password);
if (user != null)
{
context.AuthenticateResult = new AuthenticateResult(user.Subject, user.Username);
}

return Task.FromResult(0);
}
}


While registering the user i am redirecting him to a controller in Identity Server Host
.

public class LocalRegistrationController : Controller
{
[Route("core/localregistration")]
[HttpGet]
public ActionResult Index(string signin)
{
return View();
}

[Route("core/localregistration")]
[HttpPost]
public ActionResult Index(string signin, LocalRegistrationModel model)
{
var ctx = Request.GetOwinContext();
if (ModelState.IsValid)
{
var user = new LocalRegistrationUserService.CustomUser
{
Username = model.Username,
Password = model.Password,
Subject = Guid.NewGuid().ToString(),
Claims = new List<Claim>()
};
LocalRegistrationUserService.Users.Add(user);
user.Claims.Add(new Claim(Constants.ClaimTypes.GivenName, model.First));
user.Claims.Add(new Claim(Constants.ClaimTypes.FamilyName, model.Last));

return Redirect("~/core/" + Constants.RoutePaths.Login + "?signin=" + signin);
}

return View();
}
}


startup.cs

app.Map("/core", coreApp =>
{
var factory = new IdentityServerServiceFactory()
.UseInMemoryClients(Clients.Get())
.UseInMemoryScopes(Scopes.Get());

// different examples of custom user services
//var userService = new RegisterFirstExternalRegistrationUserService();
//var userService = new ExternalRegistrationUserService();

var userService = new LocalRegistrationUserService();

// note: for the sample this registration is a singletone (not what you want in production probably)
factory.UserService = new Registration<IUserService>(resolver => userService);
factory.ViewService = new Registration<IViewService, CustomViewService>();
var options = new IdentityServerOptions
{
SiteName = "Identity Server 3",

SigningCertificate = Certificate.Get(),
Factory = factory,

AuthenticationOptions = new AuthenticationOptions
{
IdentityProviders = ConfigureAdditionalIdentityProviders,
LoginPageLinks = new LoginPageLink[] {
new LoginPageLink{
Text = "Register",
// Href = "~/externalregistration",
Href = "~/localregistration",
//Href = "localregistration"
},
new LoginPageLink{
Text = "Forgot Password?",
Href = "~/forgotpassword",
//Href = "localregistration"
}
}
},

EventsOptions = new EventsOptions
{
RaiseSuccessEvents = true,
RaiseErrorEvents = true,
RaiseFailureEvents = true,
RaiseInformationEvents = true
}
};

coreApp.UseIdentityServer(options);
});


I need to understand the best way to map a
user
to
client
while registering and make sure that user can only login to that client and not any other clients even though the
username
and
password
are same for all the clients for that user.

Answer

What if you provide a custom claim to go in your id_token, named application_access with a value of an application/client the user has access to; which of there could be many. You could add this custom claim in your IUserService implementation and map this the same way you map other user specific claims.

Then, in your client, do a check for that specific claim and check if the logged in user has a claim with a specific value for that specific client.

https://github.com/IdentityModel/Thinktecture.IdentityModel.45/blob/master/IdentityModel/Thinktecture.IdentityModel/Authorization/WebApi/ScopeAttribute.cs

(mark: RequireScope is for access tokens and APIs, but you get the general idea)

Comments