Aps .net IdentityServer4 authorize - asp.net

I'm using IdentityServer4 with asp .net identity as authentication point. My APIs/WebApps call identity server to get access token.
Now, how to authorize uses before some action or inside action in my api/app controller?
I can add roles to access token and then in controller (in web api/web app) use AuthorizeAttribute and check if user IsInRole.
But it means that if I will change user roles, he will see it after logout-login (because roles are part of access token) or token has to expire.
I would like to ask identity server about user role(s) each time I need to authorize him to some action (especially to action like modify/delete some data).
Question how?
Or What I have to looking for?

So there's a few possible solutions here:
Make a call to the OIDC UserInfo Endpoint to obtain updated user claims on every request
Lower the cookie lifetime to refresh user info automatically more often
Implement a custom endpoint on IdentityServer for it to post profile change information to a list of subscribed clients (such as your webapp).
Have IdentityServer force single sign out when user profile data is changed
In terms of difficulty to implement, lowering cookie lifetime is the easiest (just change cookie expiration), but it doesn't guarantee up-to-date claims, and it is visible to the user (frequent redirects to IdentityServer, although no login is required if the access token lifetime is still valid)
Having the webapp call the UserInfo Endpoint on each request is the next easiest (see sample below) but has the worst performance implications. Every request will produce a round trip to IdentityServer.
The endpoint / subscriber model would have the lowest performance overhead. UserInfo requests to IdentityServer would ONLY occur when user profile information has actually changed. This would be a bit more complicated to implement:
On your IdentityServer project, you would need to modify changes to profile data, and post an http message to your webapp. The message could simply contain the user ID of the modified user. This message would need to be authenticated somehow to prevent malicious users from voiding legitimate user sessions. You could include a ClientCredentials bearer token for this.
Your webapp would need to receive and authenticate the message. It would need to store the changed user's ID somewhere accessible to the OnValidatePrincipal delegate (through a service in the DI container most likely)
The Cookie OnValidatePrincipal delegate would then inject this local service to check if user information has changed before validating the principal
Code Samples
Get updated UserInfo from endpoint on each call
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "NameOfYourCookieAuthSchemeHere",
Events = new CookieAuthenticationEvents()
{
OnValidatePrincipal = async context =>
{
// Get updated UserInfo from IdentityServer
var accessToken = context.Principal.Claims.FirstOrDefault(c => c.Type == "access_token").Value;
var userInfoClient = new UserInfoClient("https://{IdentityServerUrlGoesHere}");
var userInfoResponse = await userInfoClient.GetAsync(accessToken);
// Invalidate Principal if Error Response
if (userInfoResponse.IsError)
{
context.RejectPrincipal();
await context.HttpContext.Authentication.SignOutAsync("NameOfYourCookieAuthSchemeHere");
}
else
{
// Check if claims changed
var claimsChanged = userInfoResponse.Claims.Except(context.Principal.Claims).Any();
if (claimsChanged)
{
// Update claims and replace principal
var newIdentity = context.Principal.Identity as ClaimsIdentity;
newIdentity.AddClaims(userInfoResponse.Claims);
var updatedPrincipal = new ClaimsPrincipal();
context.ReplacePrincipal(updatedPrincipal);
context.ShouldRenew = true;
}
}
}
}
});
Update On Subscribed Change Message from IdentityServer. This example supposes you've created a service (ex IUserChangedService) which stores userIds received at the endpoint from IdentityServer. I don't have samples of the webapp's receiving endpoint or a service.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "NameOfYourCookieAuthSchemeHere",
Events = new CookieAuthenticationEvents()
{
OnValidatePrincipal = async context =>
{
// Get User ID
var userId = context.Principal.Claims.FirstOrDefault(c => c.Type == "UserIdClaimTypeHere");
var userChangedService = context.HttpContext.RequestServices.GetRequiredService<IUserChangedService>();
var userChanged = await userChangedService.HasUserChanged(userId);
if (userChanged)
{
// Make call to UserInfoEndpoint and update ClaimsPrincipal here. See example above for details
}
}
}
});
The asp.net core docs have an example of this as well, except working with a local database. The approach of wiring to the OnValidatePrincipal method is the same:
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie#reacting-to-back-end-changes
Hope this helps!

Related

asp.net core identity extract and save external login tokens and add claims to local identity

I am a stackoverflow noob so please go easy if I am doing this wrong.
I am using asp.net core with the default core identity template (local accounts).
I have accertained how to add claims to user principal when they login locally like so
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginInputModel model)
{
if (ModelState.IsValid)
{
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
var user = await _userManager.FindByNameAsync(model.Email);
await _userManager.AddClaimAsync(user, new Claim("your-claim", "your-value"));
And I have figured out how to get claims returned from the external login but I cannot figure out how I would add these before the user principal gets created in the ExternalLoginCallback function
public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
{
if (remoteError != null)
{
ModelState.AddModelError(string.Empty, $"Error from external provider: {remoteError}");
return View(nameof(Login));
}
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
return RedirectToAction(nameof(Login));
}
else {
// extract claims from external token here
}
// assume add claims to user here before cookie gets created??
// Sign in the user with this external login provider if the user already has a login.
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false);
if (result.Succeeded)
I am assuming the the _signInManager.ExternalLoginSignInAsync function works similar to the local login _signInManager.PasswordSignInAsync in the sense that once it is called, the cookie will be created. But I am just not sure.
Essentially what I am hoping to achieve, is understanding of how to add custom claims into the cookie that gets created regardless of how to user logins in (local or external), and how to persist these claims to the database if required.
I am planning on doing some work where if I have a user login using say google auth, I need to save that access_token from google, because I wish to call into the Google APIs later with it. So I need to be able to include this access_token in with the User Principal that gets created, and I would hope the cookie would have a claim on it I could use at the front end as well.
This might be out of scope on this question but I would also like when the google token expires, for some-how it to use the refresh token and go get a new one, or force the user to relogin.
Any help on this would be super appreciated, I have really tried hard to understand this without posting this question to stackoverflow. I have read many articles with lots of useful info, but does not provide the answers this specific question is asking. So Thank you very much in advance.
cheers
When you use await _userManager.AddClaimAsync(user, new Claim("your-claim", "your-value")); that actually updates the Identity's aspnetuserclaims table.
Whenever you sign in (by using _signInManager.PasswordSignIn or _signInManager.ExternalLoginSignInAsync) the claims from that table are read and added to the cookie that on every request becomes the Principal.
So you probably don't want to be calling the AddClaimAsync method from UserManager on every login.
Regarding external login providers, you have access to the claims when you call (in ExternalCallback and ExternalCallbackConfirmation if you are using the default templates) here:
var info = await _signInManager.GetExternalLoginInfoAsync();
The claims are in info.Principal.Claims.
The access token is not included by default. When it is, it will be here (along with the type and expiry date):
var accessToken = info.AuthenticationTokens.Single(f => f.Name == "access_token").Value;
var tokenType = info.AuthenticationTokens.Single(f => f.Name == "token_type").Value;
var expiryDate = info.AuthenticationTokens.Single(f => f.Name == "expires_at").Value;
To have the access token be included in the AuthenticationTokens collection, when you are configuring the GoogleAuthentication middleware set the SaveTokens flag to true:
app.UseGoogleAuthentication(new GoogleOptions{
ClientId = "...",
ClientSecret = "...",
SaveTokens = true
Now, if you want to have control over which claims go in the cookie you have to "take over" the process of creating the claims principal.
This is done for you when you use _signInManager.PasswordSignIn/ExternalLoginSignInAsync.
So, for example, for ExternalLoginSignInAsync replace:
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false);
With:
var user = await this._userManager.FindByLoginAsync(info.LoginProvider, info.ProviderKey);
var claimsPrincipal = await this._signInManager.CreateUserPrincipalAsync(user);
((ClaimsIdentity)claimsPrincipal.Identity).AddClaim(new Claim("accessToken", info.AuthenticationTokens.Single(t => t.Name == "access_token").Value));
await HttpContext.Authentication.SignInAsync("Identity.Application", claimsPrincipal);
"Identity.Application" is the default cookie name. You can change it in Startup's ConfigureServices method, for example to MainCookie:
services.Configure<IdentityOptions>(options => {
options.Cookies.ApplicationCookie.AuthenticationScheme = "MainCookie";
});
You still need to handle the ExternalCallbackConfirmation action in the AccountController. It will be similar to the example above.

Replacing Cookie by Token based authentication in ASP.NET OWIN OpenIdConnect code authorization flow

We have a web application written in ASP.NET that uses MVC for serving our Single Page Applications and Web API for ajax calls.
The authentication uses Microsoft.Owin and OpenIdConnect with Azure AD for Authority. The OAUTH flow is server side code authorization.
Then in Startup.Auth.cs we have
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
var cookieAuthenticationOptions = new CookieAuthenticationOptions()
{
CookieName = CookieName,
ExpireTimeSpan = TimeSpan.FromDays(30),
AuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
SlidingExpiration = true,
};
app.UseCookieAuthentication(cookieAuthenticationOptions);
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
AuthorizationCodeReceived = (context) =>
{
/*exchange authorization code for a token
stored on database to access API registered on AzureAD (using ADAL.NET) */
},
RedirectToIdentityProvider = (RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context) =>
{
/* Set the redirects URI here*/
},
});
}
When clicking on signin we navigate to an url whose routes map to the methods of the following MVC controller
public class AccountController : Controller
{
public void SignIn(string signalrRef)
{
var authenticationProperties = /* Proper auth properties, redirect etc.*/
HttpContext.GetOwinContext()
.Authentication.Challenge(authenticationProperties, OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType);
}
public void SignOut(string signalrRef)
{
var authenticationProperties = /* Proper auth properties, redirect etc.*/
HttpContext.GetOwinContext().Authentication.SignOut(authenticationProperties,
OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType);
}
Then the end-user connected to our application is authenticated between our client apps and the ASP.net server using an ASP.NET cookie. We would like to use Token Based approach instead. If you are interested this is the reason.
I tried to replace
the Nuget package Microsoft.Owin.Security.Cookies by Microsoft.Owin.Security.OAuth and in Startup.cs
replacing
app.UseCookieAuthentication(cookieAuthenticationOptions); by app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
and in my AccountController we changed the challenge from HttpContext.GetOwinContext().Authentication.SignOut(authenticationProperties,
OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType); to HttpContext.GetOwinContext().Authentication.SignOut(authenticationProperties,
OpenIdConnectAuthenticationDefaults.AuthenticationType, OAuthDefaults.AuthenticationType);
The problem is that with Cookie the set-cookie was automatically sent in web request respond when the flow completes while redirecting to the url we specified.
Where can I find the Bearer generated by OWIN with UseOAuthBearerAuthentication (if there is any) **, **Where and When should I send it back to my client SPAs
Note: an open source sample of what we are trying to do can be found in this github repository.
I think there are two approaches for you to consider.
Use javascript libraries to perform sign-in & token acquisition within your single page app. Then your backend is purely an web API, and can just use OAuth bearer middleware to authenticate requests. The backend doesn't know anything about signing the user in. We have a good sample that takes this approach here. If your backend needs to make API calls as well, you could consider the OnBehalfOf flow as well. I usually recommend this approach.
Use the OpenIDConnect middleware in your server to perform user sign-in and token acquisition. You might even be able to omit the usage of the CookieAuthenticationMiddleware entirely (although I'm not 100% sure). You can capture the token in the AuthorizationCodeReceived notification as you mention, and you could redirect back to your SPA with the token in the fragment of the URL. You could also have some route which delivers the tokens (which are cached on your server) down to your javascript. In either case, you'll need to ensure that an outside caller can't get access to your tokens.
The thing to keep in mind will be how you refresh tokens when they expire. If you use #1, most of it will be handled for you by libraries. If you use #2, you'll have to manage it more yourself.

ASP.NET Identity 2 execute code after cookie authentication

I'm using ASP.NET Identity 2 authentication via OWIN middlewear. I've created a new project using the template so initially started with the default generated code but have changed it a bit (taken out entity framework and wired in my own existing authentication). This is all working.
What I'd now like to do is execute code after a user logs in via a saved cookie. I've had a look at ConfigureAuth in the Startup.Auth.cs file which I've configured as follows:
public void ConfigureAuth(IAppBuilder app) {
// Configure the user manager and signin manager to use a single instance
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
// Configure the sign in cookie
app.UseCookieAuthentication(new CookieAuthenticationOptions {
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider {
OnResponseSignIn = ctx => {
Log.Trace("On Response Sign In.");
},
OnResponseSignedIn = ctx => {
Log.Trace("On Response Signed In.");
},
OnValidateIdentity = async ctx => {
Log.Trace("On Validate Identity.");
}
}
});
}
From this I can see that OnResponseSignIn and OnResponseSignedIn are hit only during actual logins when the user enters their username and password. They are not hit when the user is authenticated via saved cookie.
OnValidateIdentity is hit regardless of whether the user authenticated via username/password or saved cookie and it's hit for every request they make.
What I'd like is to execute code just once after a login via cookie. Does anyone know how to do this? If not, I guess another option is to put code in OnValidateIdentity but in an if statement that will prevent it being run unless its the first call after the cookie authentication. Can anyone think of how to achieve that? All I can think of is to set a variable in Session after the code is first run and check for it's presence to prevent it being re-run?
It can probably be done by using a session variable as a flag, and only do your thing when it is not set.
OnValidateIdentity = async context => {
if (HttpContext.Current.Session["Refreshed"] == null)
{
/** do your thing **/
...
HttpContext.Current.Session["Refreshed"] = new object();
}
}

Using Facebook access tokens obtained in mobile app to access ASP.Net WebAPI2 controller actions

Setup
Client: mobile app built on Cordova
Backend: ASP.net WebAPI2 (based on the standard template) configured with facebook login provider.
Problem
Having authenticated the user in the mobile app and received a Facebook access token, I pass this in subsequent requests as an HTTP header ("Authorization: Bearer "). This returns status 401 Unauthorized.
Questions
What am i missing here? How can i access the WebAPI controller actions based on the Facebook access token obtained on the mobile device?
On a high level, what i'm trying to achieve is this:
User opens mobile app and authenticates with Facebook
If user is not registered as a local user, he must choose a username to complete the registration
User is registered and can access API
I was facing the same problem and I found a really good solution here: http://codetrixstudio.com/mvc-web-api-facebook-sdk/
The WebApi web site can't understand the access token provided by Facebook. I guess it's because it hasn't been issued by itself (LOCAL AUTHORITY) but by an external provider. The approach explained in the link above is based on validating the token given by Facebook using it's API and recreating the access token.
So, you'll need some additional steps to achieve your goal.
The external providers have API so you can get information. For example, the https://graph.facebook.com/me?access_token={0} can be used to check if the token is valid. On the server side, you'll need to make a https web request to this URL passing the token (and the secret app as a proof, if the app is configured to ask it in Facebook).
Given the token is ok, you'll create an identity (ClaimsIdentity) using the information you've got at the API (id and username, for example). This identity will be needed to make an instance of the AuthenticationTicket class so you'll be able to issue a new access token for your Cordova app. Use this new bearer access token in the Authorization header of your https calls and your WebApi will recognized it as valid calls.
Ps. The good thing here is that you can set the token's expiration.
Since API's are stateless, there are multiple ways to secure it. In this case the mobile app has authenticated the user, but the API has not.
You can register the user's email and facebook ID into the database using a anonymous route. this can serve as both the login and register technically. (you could secure it with a clientid via OAuth if you don't want it fully open) along with thier current token. You verify the user against the facebook API on the server before registering of course just in case.
Create a custom route handler to secure the account controller or any other routes. The custom route handler would check the database for the current FB token and fb ID combo as well as token expire time, since you don't want to keep authenticating if it's expired.
Facebook has two types of tokens.
A Short lived token, initially created when you login and a long term token that lasts up to 60 days vs 1-2 hours.
for the api side, i suggest sticking to the short lived token and re-authenticate once expired but thats up to you.
You should grab a facebook SDK of your choice to verify and pull account info.
Example Secured Route:
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "routename",
routeTemplate: "account",
constraints: null,
handler: new CustomFBHandler()
{
InnerHandler = new HttpControllerDispatcher(config)
},
defaults: new {controller = "default"}
);
}
Custom Handler: (note that you should pass any needed dependancies in the construtor)
public class CustomHandler : DelegatingHandler
{
public CustomHandler()
{
}
protected async Task<bool> IsAuthenticated(HttpRequestMessage requestMessage)
{
//Authenticate FB User Info HERE Against the Registered/logged in user....
}
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
bool isAuthenticated = false;
try
{
isAuthenticated = await IsAuthenticated(request);
}
catch (Exception e)
{
var response = request
.CreateResponse(HttpStatusCode.InternalServerError, new { status = new { code = 333, error = true, message = e.Message } }, new JsonMediaTypeFormatter());
response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue(Configuration.AuthenticationScheme));
return response;
}
if (!isAuthenticated)
{
var response = request
.CreateResponse(HttpStatusCode.Unauthorized,new {status=new{code=1,error=true,message="Authorization Failed"}},new JsonMediaTypeFormatter());
response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue(Configuration.AuthenticationScheme));
return response;
}
return await base.SendAsync(request, cancellationToken);
}
}
You should send the FB Token and Facebook user id in the Headers. Once authenticated you can use the token/id to pull the user info you need from the database.

How do you consume extra parameters in OAuth2 Token request within .net WebApi2 application

I have an api specific project in a large .net MVC 5 web solution. I am utilizing the WebApi2 templates out of the box to authenticate a user through the api. Using individual accounts to authenticate, the request body required to get an access token is:
grant_type=password&username={someuser}&password={somepassword}
This works as expected.
However, I need to add a 3rd dimension to the scaffolded method "GrantResourceOwnerCredentials". In addition to checking the username/password, i need to add a device id, which is meant to restrict access from a user account to a specific device. What's not clear is how to add these extra request parameters to the already defined "OAuthGrantResourceOwnerCredentialsContext". This context currently makes room for UserName and Password, but obviously i'll need to include more.
My question is simply, is there a standard way to extend the login requirements for the OWIN OAuth2 token request to include more data? And, how would you reliably do that in a .NET WebApi2 environment?
As it often is the case, I found the answer immediately after submitting the question...
ApplicationOAuthProvider.cs contains the following code out-of-the-box
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(context.UserName, data["udid"]);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
}
By simply adding
var data = await context.Request.ReadFormAsync();
within the method, you can access all posted variables in the request body and use them as you like. In my case, I placed it immediately after the null-check on the user to perform a more restrictive security check.

Resources