Reputation: 405
I would like to create a clearer picture in my mind as to what the differences are between roles and scopes in .NET Web API projects. This is more of a best-approach question than anything else, and I am finding myself to be a little confused as to how best authorize users that want to access my API. I come from a .NET MVC background, so I am familiar with roles and I am wondering if the same approaches apply to the web API framework. I am having difficulties putting scopes in the picture and how I should use them to allow access for a user using a particular client ID. Are scopes similar to access permissions? To illustrate my confusion, let's use this example:
Client A
Native app: displays event calendar
Role: Event
User login required? No
Allowed scopes: Read events
Client B
Web app: shows next upcoming event, displays registrant names
Role: Event
User login required? Yes
Allowed scopes: Read events, read registrants
Client C
Native app: registers a person for an event
Role: Registrant
User login required? Yes
Allowed scopes: Read events, read registrants, write registrants
Basically I would like to know if my above use of scopes is correct and what the best approach would be to grant resource owner credentials. I am using the token based authentication as outlined in Taiseers tutorial. Below is my current incomplete code snippet that will take care of validating requested client and scope:
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
ApiClient client = null;
string clientId = string.Empty;
string clientSecret = string.Empty;
if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
context.TryGetFormCredentials(out clientId, out clientSecret);
if (context.ClientId == null)
{
context.Validated();
context.SetError("invalid_clientId", "ClientId should be sent.");
return Task.FromResult<object>(null);
}
using (ApiClientRepo _clientRepo = context.OwinContext.GetUserManager<ApiClientRepo>())
{
client = _clientRepo.FindClient(context.ClientId);
}
if (client == null)
{
context.SetError("invalid_clientId", string.Format("Client '{0}' is not registered in the system.", context.ClientId));
return Task.FromResult<object>(null);
}
// Validate client secret
if (string.IsNullOrWhiteSpace(clientSecret))
{
context.SetError("invalid_secret", "Client secret should be sent.");
return Task.FromResult<object>(null);
}
else
{
WPasswordHasher passwordHasher = new WPasswordHasher();
PasswordVerificationResult passwordResult = passwordHasher.VerifyHashedPassword(client.SecretHash, clientSecret);
if (passwordResult == PasswordVerificationResult.Failed)
{
context.SetError("invalid_secret", "Client secret is invalid.");
return Task.FromResult<object>(null);
}
}
if (!client.Active)
{
context.SetError("invalid_clientId", "Client is inactive.");
return Task.FromResult<object>(null);
}
context.OwinContext.Set<int>("as:clientRepoId", client.Id);
context.OwinContext.Set<string>("as:clientAllowedOrigin", client.AllowedOrigin);
context.OwinContext.Set<string>("as:clientRefreshTokenLifeTime", client.RefreshTokenLifeTime.ToString());
context.Validated();
return Task.FromResult<object>(null);
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
IApiUser user = null;
string scope = null;
// Get parameters sent in body
Dictionary<string, string> body = context.Request.GetBodyParameters();
// Get API scope
body.TryGetValue("scope", out scope);
if (scope == null)
{
context.Validated();
context.SetError("invalid_scope", "Invalid requested scope.");
return;
}
var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin");
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });
// At this point I got the requested scope.
// What should I do with it?
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
// create claims identity based on user info
ClaimsIdentity identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, user.FirstName + " " + user.LastName));
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Username));
identity.AddClaim(new Claim(ClaimTypes.Role, scope));
var props = new AuthenticationProperties(new Dictionary<string, string>
{
{
"as:client_id", (context.ClientId == null) ? string.Empty : context.ClientId
},
{
"userName", context.UserName
}
});
var ticket = new AuthenticationTicket(identity, props);
context.Validated(ticket);
}
Thanks ahead for all thoughts, suggestions and ideas!
Upvotes: 2
Views: 3001
Reputation: 1066
In my perspective scopes define the resources. Basically the request challenge is "may client (=application) access resource x on your behalf"?
Where x is a any resource your API serves. I've used a conveniention in a project where a scope can be specific for a CRUD action on a resource. For example scope = tweets.read or tweets.create.
Having a token for a scope doesn't give a client the permission. The permission is based on the fact that the user has permission to preform the action and has the client the correct resource scope in its token. Of course a users permission can be based on a role like guest or admin etc.
So in theory the user can grant access to scopes (resources) it has no permissions at.
A token has a lifetime of let's say 20 min, if you base a permission on any value in the access token, the permission cannot be revoked or changed within the token lifetime.
Upvotes: 4