I want to change the way the default bearer token system works.
I want to login to the webAPI providing the username, password, and mac address of the device. Like so.
Content-Type: application/x-www-form-urlencoded
username=test&password=P#ssword&grant_type=password&client_id=android&device_info=MAC_Address
I then want the API to provide me with a Refresh Token. This token will be valid for say 7 days and will allow for me to get a new access token. However in the refresh token I want to save / embed the security stamp of the users password in the token along with the extirpation date. This way I can check the security stamp when a new access token is requested. (solves password changing scenario)
My access token only needs to store the bare amount of information for it to work. I don't require that the access token store anything specific. I would like to keep it as small as possible. When it expires I will simply request a new access token using my refresh token.
Now I have tried to implement the above but have got my self heavily confused about what to implement where. Here's what i have got.
Step 1: The Startup.Auth.cs
//Configure the application for OAuth based flow
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/Token"),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
Provider = new SimpleAuthorizationServerProvider(),
RefreshTokenProvider = new SimpleRefreshTokenProvider(),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(20)
};
Now in here I already have some questions. I want to have two providers, one which handles Refresh Tokens and one that handles Access Tokens. Which providers do I need to set? because I see there is also one called AccessTokenProvider = then what is Provider = for?
Step 2: The RereshTokenProvider. This is what I have so far:
public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider
{
//Used to store all the refresh tokens
public static ConcurrentDictionary<string, AuthenticationTicket> RefreshTokens = new ConcurrentDictionary<string, AuthenticationTicket>();
public Task CreateAsync(AuthenticationTokenCreateContext context)
{
var guid = Guid.NewGuid().ToString("N");
//copy properties and set the desired lifetime of refresh token
var refreshTokenProperties = new AuthenticationProperties(context.Ticket.Properties.Dictionary)
{
IssuedUtc = context.Ticket.Properties.IssuedUtc,
ExpiresUtc = DateTime.UtcNow.AddDays(7)
};
//TODO: get mac address from the request headers??
//TODO: save the mac address to db along with user and date
var refreshTokenTicket = new AuthenticationTicket(context.Ticket.Identity, refreshTokenProperties);
RefreshTokens.TryAdd(guid, refreshTokenTicket);
context.SetToken(guid);
return Task.FromResult<object>(null);
}
public Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
AuthenticationTicket ticket;
if (RefreshTokens.TryRemove(context.Token, out ticket))
{
context.SetTicket(ticket);
}
return Task.FromResult<object>(null);
}
public void Receive(AuthenticationTokenReceiveContext context)
{
throw new NotImplementedException();
}
public void Create(AuthenticationTokenCreateContext context)
{
throw new NotImplementedException();
}
}
Now if i understand correctly. The purpose of the SimpleRefreshTokenProvider is to build up a RefreshToken and to the validate it when the api receives a request with one in it?
Step 3: SimpleAuthorizationServerProvider. This is what I have so far. but I have a feeling this is where I have gone wrong. Or im getting confused, What is the purpose of this class? Is it not to validate the AccessToken?
public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
// Not concerned about clients yet
context.Validated();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
// validate user credentials
var userManager = context.OwinContext.GetUserManager<FskUserManager>();
FskUser user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
// create identity
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager,
CookieAuthenticationDefaults.AuthenticationType);
//Set properties of the token
// create metadata to pass on to refresh token provider
AuthenticationProperties properties = new AuthenticationProperties(new Dictionary<string, string>
{
{"userName", user.UserName}
});
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
public override async Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
{
string originalClient;
context.Ticket.Properties.Dictionary.TryGetValue("as:client_id", out originalClient);
var currentClient = context.ClientId;
// chance to change authentication ticket for refresh token requests
var newId = new ClaimsIdentity(context.Ticket.Identity);
newId.AddClaim(new Claim("newClaim", "refreshToken"));
var newTicket = new AuthenticationTicket(newId, context.Ticket.Properties);
context.Validated(newTicket);
}
}
Please what am I missing here?
Related
I'm Using Token-Based Authentication in my webApi application. for each login OAuth generates an access token for user. if a user tries to do login more than once. it may own some more valid token.
is there a limitation on this process.
Here is my Startup class:
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
ConfigureOAuth(app);
WebApiConfig.Register(config);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
//Rest of code is here;
}
public void ConfigureOAuth(IAppBuilder app)
{
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new SimpleAuthorizationServerProvider()
};
// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
and here is "GrantResourceOwnerCredentials" Method:
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
using (AuthRepository _repo = new AuthRepository())
{
IdentityUser user = await _repo.FindUser(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
}
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim("sub", context.UserName));
identity.AddClaim(new Claim("role", "user"));
context.Validated(identity);
}
One of the main limitation of oauth token is it's expiry. So if you generate long living token then it is valid for long time. So some of common approach to handle such senerio is :
issue short living token with additional refresh token
store token in database and every time when new token is generated then make old one token status to expire. Then you can write your custom authorize attribute to check whether token is expire or not.
I am afraid the token is valid until it expires and it will contain all the info related to the user.
So to do what you want you have to create your own layer to validate if the user has or not a token, like creating a mapping table and then a custom filter to reject the request if the user is not using the last token generated for him.
I was able to generate a token by validating the incoming username and password.
In startup.cs I have this
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/api/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(100),
Provider = new MYAuthorizationServerProvider(),
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active
};
// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()
{
Provider = new OAuthBearerAuthenticationProvider()
});
HttpConfiguration config = new HttpConfiguration();
WebApiConfig.Register(config);
}
}
In MyAuthorizationsServiceProvider I have
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
var userServices = new UserService();
var user = await userServices.ValidateUser(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "Provided username and password is incorrect");
return;
}
else
{
identity.AddClaim(new Claim(ClaimTypes.Role, "Admin"));
identity.AddClaim(new Claim("username", user.UserName));
identity.AddClaim(new Claim(ClaimTypes.Name, user.UserName));
context.Validated(identity);
}
}
This is all good until now. I have a controller which is accessible only by Admin role and it works fine for the token generated.
Now let's assume that I have stripped off the user role in the backend for that specific user or deactivated the user. Now the token should not work for that specific controller or invalidate the authentication as the user is deactivated. How does the Oauth know the back end change and how does it validate?
If someone could provide an answer with some example that would be really helpful.
I also have public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) but for some reason this does not fire up.
How does the Oauth know the back end change and how does it validate?
It will only verify the username and password against the backend when the user signs in. After that the principal and claims are set from the token that the client passes along with the request.
One option is to create a custom authorized filter which validates the user against the backend in every request but that is not recommended as that would be very costly in request time.
A better option would be to set the valid time on the token to a lower number than 100 days AccessTokenExpireTimeSpan = TimeSpan.FromDays(1), and add an RefreshTokenProvider to the OAuthAuthorizationServer. Then in that provider revalidate the user against the backend. You could read here about how to implement a refresh provider
I have implemented an authentication service based on IdentityServer3 and a simple MVC client app and a Shopper API secured by the authentication service. I've implemented a IdentityServer custom UserService so that the authentication service authenticates against our existing user data store. My Shopper API expects a userid in the Shopper Get request. Currently the response from authentication service includes the identity token and the access token, but no user id. I tried adding a user_id claim in the AuthenticationResult from my custom UserService.AuthenticateLocalAsync method, but I'm not seeing it in my client app code.
UserService.AuthenticateLocalAsync looks like this:
try
{
var user = new shopper(_dbConnLib, context.UserName, context.Password);
var claims = new List<Claim> { new Claim("user_id", user.shopperid) };
context.AuthenticateResult = new AuthenticateResult(user.shopperid, user.MemberDetail.billToAddress.FirstName, claims);
}
catch(shopperInitFromException ex)
{
context.AuthenticateResult = null; // Indicates username/password failure
}
return Task.FromResult(0);
And my client app SecurityTokenValidated handler looks like this:
SecurityTokenValidated = async n =>
{
var nid = new ClaimsIdentity(
n.AuthenticationTicket.Identity.AuthenticationType,
Constants.ClaimTypes.GivenName,
Constants.ClaimTypes.Role);
var userInfoClient = new UserInfoClient(
new Uri(n.Options.Authority + "/connect/userinfo").ToString());
var userInfo = await userInfoClient.GetAsync(n.ProtocolMessage.AccessToken);
userInfo.Claims.ToList().ForEach(ui => nid.AddClaim(new Claim(ui.Type, ui.Value)));
nid.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
nid.AddClaim(new Claim("access_token", n.ProtocolMessage.AccessToken));
//nid.AddClaim(new Claim("user_id", n.ProtocolMessage.UserId));
nid.AddClaim(new Claim("expires_at", DateTimeOffset.Now.AddSeconds(int.Parse(n.ProtocolMessage.ExpiresIn)).ToString()));
n.AuthenticationTicket = new AuthenticationTicket(
nid,
n.AuthenticationTicket.Properties);
}
If I step through that in the debugger, userInfo.Claims always has a count of 0. How can I get back a claim with the unique identifier of the user? Or can I get it from the identity or access token? Or should I just pass the tokens to the Shopper API and let it determine the id from the tokens?
I think I may have the answer. So far, as far as I can tell, the claims I include in the AuthenticateResult constructor in my override of AuthenticateLocalAsync don't seem to go anywhere. But the claims I include in my override of GetProfileDataAsync appear in the token. My GetProfileDataAsync code, which appears to set the claims properly, looks like this:
public override Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var user = new shopper(_dbConnLib, context.Subject.FindFirst("sub").Value);
var claims = new List<Claim> { new Claim("sub", user.shopperid), new Claim("acr_level", "level 0"), new Claim("amr", "anonymous") };
context.IssuedClaims = claims;
return Task.FromResult(0);
}
My AuthenticateLocalAsync code that sets claims in the AuthenticateResult that I never see in my client app code looks like this:
public override Task AuthenticateLocalAsync(LocalAuthenticationContext context)
{
// TODO: Handle AddshopperToBasketException in UserService.AuthenticateLocalAsync
try
{
var user = new shopper(_dbConnLib, context.UserName, context.Password);
var claims = new List<Claim> { new Claim("acr_level", "level 0"), new Claim("amr", "anonymous") };
context.AuthenticateResult = new AuthenticateResult(user.shopperid, user.MemberDetail.billToAddress.FirstName, claims);
}
catch(shopperInitFromException ex)
{
context.AuthenticateResult = null; // Indicates username/password failure
}
return Task.FromResult(0);
}
How to use claims? For example, I want to set access to each page (resource) for each user. I understand, I can do it using roles, but as I understand, claim-based is more effectively. But when I try to create a claim, I see the following method:
userIdentity.AddClaim(new Claim(ClaimTypes.Role, "test role"));
first parameter of constructor of Claim class get ClaimTypes enum, which has many "strange" members like Email, Phone etc. I want to set that this claim and then check this claim to have access to certain resource. I'm on wrong way? How to do it?
From the code above, I am assuming you have already added the claim in startup class on authenticated of your provider as below.
context.Identity.AddClaim(new Claim("urn:google:name", context.Identity.FindFirstValue(ClaimTypes.Name))); // added claim for reading google name
context.Identity.AddClaim(new Claim("urn:google:email", context.Identity.FindFirstValue(ClaimTypes.Email))); // and email too
Once you have added the claims in startup, when the request is actually processed check if its a callback and if yes, read the claims as below(in IHttpHandler).
public void ProcessRequest(HttpContext context)
{
IAuthenticationManager authManager = context.GetOwinContext().Authentication;
if (string.IsNullOrEmpty(context.Request.QueryString[CallBackKey]))
{
string providerName = context.Request.QueryString["provider"] ?? "Google";//I have multiple providers so checking if its google
RedirectToProvider(context, authManager, providerName);
}
else
{
ExternalLoginCallback(context, authManager);
}
}
If its 1st call redirect to provider
private static void RedirectToProvider(HttpContext context, IAuthenticationManager authManager, string providerName)
{
var loginProviders = authManager.GetExternalAuthenticationTypes();
var LoginProvider = loginProviders.Single(x => x.Caption == providerName);
var properties = new AuthenticationProperties()
{
RedirectUri = String.Format("{0}&{1}=true", context.Request.Url, CallBackKey)
};
//string[] authTypes = { LoginProvider.AuthenticationType, DefaultAuthenticationTypes.ExternalCookie };
authManager.Challenge(properties, LoginProvider.AuthenticationType);
//without this it redirect to forms login page
context.Response.SuppressFormsAuthenticationRedirect = true;
}
And finally read the claims you get back
public void ExternalLoginCallback(HttpContext context, IAuthenticationManager authManager)
{
var loginInfo = authManager.GetExternalLoginInfo();
if (loginInfo == null)
{
throw new System.Security.SecurityException("Failed to login");
}
var LoginProvider = loginInfo.Login.LoginProvider;
var ExternalLoginConfirmation = loginInfo.DefaultUserName;
var externalIdentity = authManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
var emailClaim = externalIdentity.Result.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email);
var email = emailClaim.Value;
var pictureClaim = externalIdentity.Result.Claims.FirstOrDefault(c => c.Type.Equals("picture"));
var pictureUrl = pictureClaim.Value;
LogInByEmail(context, email, LoginProvider); //redirects to my method of adding claimed user as logged in, you will use yours.
}
Claim doesn't set permission. It's used to verify you that "you are who you claim to be you are". These claims are identified by issuer, usually a 3rd party. See for example this article for description.
So, you should define which claims are necessary (who user should be) in order to access a certain page. Otherwise, using claim-based authorization will be same as using identity based or role based.
I'm securing a Web API site, and I want to use tokens. But, I'm working with a legacy database, where there is a users table and each user already has a token created for them and stored in the table.
I'm trying to work out if I can use the Identity oAuth bearer token auth bits, but plug it all into my existing database, so that
Granting a token just returns the token for that user from the db
I can validate the token by looking it up in the db and creating an identity from the user (I am using ASP.NET Identity elsewhere in the site for the MVC side of things)
I can't work out if this is going to be possible, or if I should give up and use a standard HTTP handler approach. Here's my fairly standard code so far, which just issues standard tokens, not the existing ones I want to work with.
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new SimpleAuthorizationServerProvider()
};
// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
var bearerAuth = new OAuthBearerAuthenticationOptions()
{
Provider = new OAuthBearerAuthenticationProvider()
};
app.UseOAuthBearerAuthentication(bearerAuth);
public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
var manager = new UserManager<User, long>(new UserStore(new UserRepository()));
var user = await manager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
}
else
{
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim("name",user.Email));
context.Validated(identity);
}
}
}
Answering my own question ;)
Yes, it is possible. It mostly requires that you sort out a custom Token provider and implement your logic in there. A good sample of this:
https://github.com/eashi/Samples/blob/master/OAuthSample/OAuthSample/App_Start/Startup.Auth.cs