WebApi 2 Authentication - c#

I am a little confused with WebApi and MVC.
I created a blank WebApi project and selected Individual User Accounts as the authentication method.
This generated the AccountController : ApiController class. In here there are methods for Registering, Getting user info, etc, but there is no method for logging in.
How is the MVC user supposed to log in?
Cheers,
/r3plica

In default Web Api template is using OWIN middleware to authenticate user.
In Startup.Auth.cs you can find configuration info about urls for authentication.
static Startup()
{
PublicClientId = "self";
UserManagerFactory = () => new UserManager<IdentityUser>(new UserStore<IdentityUser>());
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token") - this url for get token for user,
Provider = new ApplicationOAuthProvider(PublicClientId, UserManagerFactory),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
}
After you send request to TokenEndPointPath with user name and password in parameters, OWIN middleware call method GrantResourceOwnerCredentials which implemented in ApplicationOAuthProvider in default template with user account.In this method you can check user name and password and grant access to user.
You can find default implementation of this method below.
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
using (UserManager<IdentityUser> userManager = _userManagerFactory())
{
IdentityUser user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
ClaimsIdentity oAuthIdentity = await userManager.CreateIdentityAsync(user,
context.Options.AuthenticationType);
ClaimsIdentity cookiesIdentity = await userManager.CreateIdentityAsync(user,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = CreateProperties(user.UserName);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
}

Related

Authenticate using Azure AD but Authorize using aspnetmembership roles and claims

I am trying to build an ASP.NET MVC 5 application that uses Azure AD for authentication. But once the user is authenticated, I need to use aspnet_membership Microsoft Identity set up to grab the claims for that authenticated logged in user. We don't want to maintain roles and claims within the Azure AD setup and we don't want to use MS Graph.
I have created one MVC 5.0 project using Individual User Accounts in VS 2017, which in turn created the aspnet_membership database in my SQL Server database for me.
I have also created a separate MVC 5.0 project and registered the app in Azure AD and I have the ClientID etc. and that project is also working fine. Now I am trying to merge the two and I am kind of getting lost and doubting if I am thinking it right.
Basically once the user logs in on that Microsoft Azure AD login page, I redirect to a local registration page where when the user registers with just bare minimum info including some roles, and I would then make an entry in the AspNetUsers/Claims tables and I have to attach those claims to the Principal. On subsequent logins for that user, I have to load the clams once authenticated.
Can you please help me in pointing to any samples for this kind of a scenario, as most of what I have read here advice to use Microsoft Graph. But our roles are way too complicated and we have decided to use the local identity aspnet_membership database only for authorization (Roles as Claims).
Thanks
This worked for me.
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
string clientId = System.Configuration.ConfigurationManager.AppSettings["ClientId"];
string redirectUri = System.Configuration.ConfigurationManager.AppSettings["RedirectUri"];
string tenant = System.Configuration.ConfigurationManager.AppSettings["Tenant"];
string authority = string.Format(System.Configuration.ConfigurationManager.AppSettings["Authority"], tenant);
var cookieExpiryHours = Int32.Parse(System.Configuration.ConfigurationManager.AppSettings["CookieExpiryHours"]);
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
CookieManager = new SystemWebCookieManager(),
ExpireTimeSpan = TimeSpan.FromMinutes(cookieExpiryHours),
SlidingExpiration=true,
});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
RedirectUri = redirectUri,
PostLogoutRedirectUri = redirectUri,
Scope = OpenIdConnectScope.OpenIdProfile,
UseTokenLifetime = false,
// ResponseType is set to request the code id_token - which contains basic information about the signed-in user
ResponseType = OpenIdConnectResponseType.CodeIdToken,
TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailed,
SecurityTokenValidated = OnSecurityTokenValidated,
}
}
);
}
private Task OnSecurityTokenValidated(SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context)
{
var OIDClaimDesc = "http://schemas.microsoft.com/identity/claims/objectidentifier";
var claims = context.AuthenticationTicket.Identity.Claims;
var cookieExpiryHours = Int32.Parse(System.Configuration.ConfigurationManager.AppSettings["CookieExpiryHours"]);
context.AuthenticationTicket.Properties.ExpiresUtc = DateTimeOffset.UtcNow.AddHours(cookieExpiryHours);
context.AuthenticationTicket.Properties.IsPersistent = false;
var owinContext = context.OwinContext;
var userIdentity = context.AuthenticationTicket.Identity;
var userClaims = userIdentity as ClaimsIdentity;
var firstName = userClaims?.FindFirst(ClaimTypes.GivenName)?.Value ?? string.Empty;
var lastName = userClaims?.FindFirst(ClaimTypes.Surname)?.Value ?? string.Empty;
var email = userClaims?.FindFirst(ClaimTypes.Email)?.Value ?? string.Empty;
var objID = Guid.Parse(userClaims.FindFirst(OIDClaimDesc).Value);
var user = new UserService().GetUser(objID, email);
if (user is null)//This user has just wandered in to the site or the admins have not added this user in the DB yet. Just redirect them back to log out
{
owinContext.Authentication.Challenge();
return Task.FromResult(0);
}
if (userIdentity.IsAuthenticated)
{
userIdentity.AddClaim(new Claim(ClaimTypes.GivenName, firstName));
userIdentity.AddClaim(new Claim(ClaimTypes.Surname, lastName));
userIdentity.AddClaim(new Claim(ClaimTypes.Email, email));
userIdentity.AddClaim(new Claim("AzureID", objID.ToString()));
userIdentity.AddClaim(new Claim(ClaimTypes.Role, "user"));
}
new UserService().UpdateUser(objID, firstName, lastName, email);
foreach (var claim in user.UserClaims)
{
if (!claim.ClaimType.Equals(ClaimTypes.GivenName, StringComparison.OrdinalIgnoreCase)
&& !claim.ClaimType.Equals(ClaimTypes.Surname, StringComparison.OrdinalIgnoreCase))
{
userIdentity.AddClaim(new Claim(ClaimTypes.Role, claim.ClaimValue));
}
}
return Task.FromResult(0);
}
/// <summary>
/// Handle failed authentication requests by redirecting the user to the home page with an error in the query string
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context)
{
var code = "IDX21323";
if (context.Exception.Message.Contains(code)) //I need this, as under certain conditions, the auth process was going on an infinite loop.
{
context.HandleResponse();
context.OwinContext.Authentication.Challenge();
}
return Task.FromResult(true);
}
}
public UserViewModel GetUser(Guid guid, string email)
{
var model = new UserViewModel();
using (var ctxt = new DBContext())
{
var user = ctxt.Users.Where(x => (x.Email == email || x.OID==guid) && x.IsActive).FirstOrDefault();
if (user == null)
return null;
var claims = ctxt.UserClaims.Where(x => x.UserId==user.ID).ToList();
model = Mapper.Map<UserViewModel>(user);
model.UserClaims = Mapper.Map<List<ViewModels.UserClaimViewModel>>(claims);
}
return model;
}

How to limit user to only one access token in ASP.NET Identity

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.

OAuthAuthorizationServerProvider: validate client bearer token

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

How to customize oAuth token providers in asp.net web api

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?

Web API Bearer tokens - can I use custom tokens?

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

Categories