Mvc OAuth2 how to store data for the current Autentication in a OAuthAuthorizationServerProvider - c#

im sorry for my bad English, im french.
I will try to explain my question the best i can.
i have a OAuthAuthorizationServerProvider wish work fine.
This is to allow other application to connect with my Asp.Net Identity 2.0 Authentication Server.
I wish to store data for the current authentication. If the user is connected twice, they will not necessary have the same stored data. I don't think Session is the right thing for this becose i dont use cookie. I use Bearer, an access_token and a refresh_token.
I can simply store the refresh_token in a table, then refer it on each request but i don't like to store sensible data like that, especially if the framework provide a way to do what i want.
I need to store the data relative to each external authentication, not to the user. Something like Claims but only for the current authentication session.
tanks to point me on the right path.

In your OAuthAuthorizationServerProvider, you will have overridden the GrantResourceOwnerCredentials method. This is where you will have validated the user, and it's the place where you can add additional claims for the user.
Here is an example that validates the user against ASPNet Identity, and adds an additional claim to the identity that is returned.
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
var mgr = context.OwinContext.GetUserManager<ApplicationUserManager>();
var user = await mgr.FindAsync(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);
var usrIdentity = await mgr.CreateIdentityAsync(user, context.Options.AuthenticationType);
foreach (var c in usrIdentity.Claims)
{
identity.AddClaim(c);
}
//
// Add additional claims to your identity
//
identity.AddClaim(new Claim("your_custom_claim", "your_custom_claim_value"));
context.Validated(identity);
}
That said, in your comments you seem to be using Cookie and Token in the same sentence, and possibly confusing the two. Check out this blog post which should give you a good example.
Also check out the ASP.NET Identity Recommended Resources page too.

Related

Authentication/Authorization in ASP.NET Core using Enterprise SingleSignon page and Microsoft SignInManager

Presently I am working on an authentication issue in one of my ASP.NET Core(3.0) application.
To give some background, we have to use an Enterprise Single sign on page to authenticate the users. Once the user got authenticated it redirects back to our application along with user name in a HTTP header called "SM_USER". Then using that information we load corresponding Claim information from DB using Microsoft SignInManger. The application is working fine if the user is accessing the root but it couldn't able to access if they are trying to navigate a specific page directly, like http://website/Controller/Index.
I am suspecting that we may have implemented it wrongly so would like to know how should the below scenario to be implemented?
Our users first get authenticated using an enterprise Login Page(Single sign on) then redirected to our application. user information available on HTTP Headers and the corresponding claims information is available in our DB which we need to use for authorization purpose(we wanted to use Microrosoft SignInManger to load them).
I found the issue after researching it throughly so sharing here in case it useful for someone.
we observed whenever a user try to access a page if the user is not authenticated then it is redirecting Login page(Observed that decorating them with [Authorize] attribute is causing this), where We are using this Login page for local development purpose.
so when the user get redirected to login page and if the environment is not development then below code is executed on the Get method, which takes care of signing the user and creating UserPrincipal. Then after that we redirecting to the page the user requested.
if (!_signInManager.IsSignedIn(User))
{
string userName = HttpContext.Request.Headers["SM_USER"].ToString();
if (userName.Length > 0)
{
var user = await _userManager.FindByNameAsync(userName);
if (user != null)
{
var claimsPrincipal = await _signInManager.CreateUserPrincipalAsync(user);
await _signInManager.Context.SignInAsync(IdentityConstants.ApplicationScheme,
claimsPrincipal,
new AuthenticationProperties { IsPersistent = true });
if (string.IsNullOrEmpty(returnUrl)) //returnUrl is a parameter get passed by the system.
{
return RedirectToAction("<Action>", "<Controller>");
}
else
{
return Redirect(returnUrl);
}
}
}
}

Prompt user for additional information during an Open Id Connect event?

Using asp.net Core, Mvc and OpenIdConnect, is it possible to prompt an authenticated user for additional information during the ODIC authentication process, and then redirect back to the originally-desired page?
To give a concrete example: in our system one person, represented by an email address, can have multiple user ids that they may wish to operate under. Assume my email address is tregan#domain.com, and I have 3 user ids to choose from: treganCat, treganDog, treganMouse. When I hit a Controller action that is decorated with the [Authorize] attribute I first go through OpenIdConnect authentication, and one of the claims returned is an email address.
Using that email address, I want the application to prompt me to select the identity that I want to run under (treganDog, treganCat, or treganMouse).
From there, I want the application to take the user id that I selected, interrogate a database for the roles that go along with the selected user id, and load those roles as claims to my identity.
Finally, I want the application to send me on to my desired page (which is the protected Controller method that I originally attempted to visit).
Is this possible?
I'm using an Owin Startup class; the code below "works" except for the fictional line "var identityGuid = [return value from the prompt];" ("fictional" because it represents what I would like to occur, but in fact a series of redirects would be needed).
My example below uses the OnTicketReceived event, but that selection is arbitrary, I would be willing to do this in any event.
services.AddAuthentication(authenticationOptions =>
{
authenticationOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
authenticationOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(openIdConnectOptions =>
{
openIdConnectOptions.Authority = Configuration["PingOne:Authority"];
openIdConnectOptions.CallbackPath = "/Callback";
openIdConnectOptions.ClientId = Configuration["PingOne:ClientId"];
openIdConnectOptions.ClientSecret = Configuration["PingOne:ClientSecret"];
openIdConnectOptions.ResponseType = "code";
openIdConnectOptions.Events.OnTicketReceived = (ticketReceivedContext) =>
{
var emailClaim =
ticketReceivedContext.Principal.Claims.FirstOrDefault(o =>
o.Type == ClaimTypes.Email);
string emailAddress = emailClaim.Value;
//here is where I would like to prompt the user to select an identity based on the email address
//the selected identity is represented by a guid
var identityGuid = [return value from the prompt];
var roles = new MyRepository(myContext).GetRolesForUserId(identityGuid);
var claims = new List<Claim>();
foreach (string role in roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
ticketReceivedContext.Principal.AddIdentity(new ClaimsIdentity(claims));
return Task.CompletedTask;
};
});
This is impersonation where there is a real user and you need to identify the impersonated user after login.
You will need to complete the login first, return to the app and configure the principal. Then render a UI and receive the selected choice.
You then need your UI to call the back end and tell it to update claims in the auth cookie. Not sure if you'll get this to work though - the impersonated user may need separate storage - such as a second cookie.
This highlights that it can be useful to separate the token / credential the UI receives from the claims the back end works with.
I use the below design a lot for REST APIs that serve UIs directly - though it may be overkill for your solution:
https://authguidance.com/2017/10/03/api-tokens-claims/
I think what I want to do is simply not possible without either figuring out a way to do it inside PingOne or writing my own IdentityServer and taking care of the extra steps there.
I decided to instead write a custom middleware that fires after the Authentication middleware, as described in this SO question: In asp.net core, why is await context.ChallengeAsync() not working as expected?

Understanding storage of OAuth token

I have implemented ASP.NET Identity authentication and OAuth authorization according to this tutorial: http://bitoftech.net/2014/06/01/token-based-authentication-asp-net-web-api-2-owin-asp-net-identity/
It's currently working but i don't fully understand where the TOKEN and it's timer is stored.
This is the code that generates token:
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[] { "*" });
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);
}
}
I would guess that token is stored in the ASP.NET Identity DB or within the hosted WEB API Application, but i don't fully understand.
the token is only generated once by the provider and it is not stored anywhere. It contains the information the application needs to authenticate the request and nothing more.
Assuming you use the Json Web Tokens or JWT, then the token is nothing more than a Json object with some properties, such as when it expires, the actual duration in seconds etc.
The token last for a configurable duration, so assuming you want to reuse that token for multiple calls then the client application will need to store somewhere in a safe manner. It could be in session for example, you could store the whole token and when you need it simply check if it's still active by looking at the duration. If it's not active anymore, you either refresh the current one you have or simply request another.
You could encapsulate all this nicely with something like this :
private TokenModel GetToken()
{
TokenModel result = null;
if (this._systemState.HasValidToken(this._currentDateTime) )
{
result = this._systemState.RetrieveUserData().TokenData;
}
else
{
try
{
result = this._portalApiWrapperBase.RequestAccessTokenData();
}
catch(Exception ex)
{
this.LastErrorMessage = ex.Message;
}
finally
{
this._systemState.AddTokenData(result);
}
}
return result;
}
In my case all this user data is stored in Session and simply retrieved from there.
I am not showing all the code here, but I basically have a state provider where I store the token once I receive it the first time. The next time I need it, if it's still valid, I return it, or go and request another if it's not. All this is hidden from the app, you just call the GetToken method and it deals with everything else.
Now, the token is supposed to be application level, it's generated based on a ClientID and CLientSecret so you could easily request another one when you need to.
The token isn't stored. The user requesting the token needs to be able to pass the token on every request in order to make an authenticated call. So it's the responsibility of the client to store the token in order to do that. (that might be in-memory for short lived sessions or on disk/in a database for longer lived sessions.
There is no need for the server to store the token, since it is passed by the client on each request. One might store it in a db themselves on the server and check if the token is there. Using that kind of mechanism allows you to revoke a token by removing it from the db. There are other ways to do that though.
By timer I guess you mean the lifetime of the token. That is checked by the framework on every request. So there is no actual timer.

how to get claims of another user using ASP.NET Core

I'm still learning identities with asp.net core. I'm doing a claims-based token authorization.
Most examples are about "Current" logged in user. In my case my RPC service is receiving a username & password of some user in the identity DB. I need to
verify that a user with such credentials exist
get all the claims of that user
so to verify if the user exists, I'm using this:
ApplicationUser applicationUser = await _userManager.FindByNameAsync(username);
bool exist = await _userManager.CheckPasswordAsync(applicationUser, password);
if (!exist)
{
// log and return
}
I don't know how to do the 2nd step properly. I guess I could do a simple linq to collect all user's claims, but I'm sure there is a better way using the identity methods.
You need to use the GetClaimsAsync() method. For example:
var claims = await _userManager.GetClaimsAsync(applicationUser);
See MSDN

Asp.net Identity 2.0 custom login method

I'm developing ASP.NET 5 application using Identity 2.0. I have two types of users:
Normal - they authenticate using standard login method.
Temporary - they should login based on provided token.
I do not want to store temporary users, except from information required to authenticate user (some username and token). If the user provides username and valid password he should be logged in.
I'm not sure how to achieve this.
You could use Identity in both scenarios simultaneously as well. For first scenario use Identity just like you have done before without any change but for second scenario you a slight modify in login method.
public ActionResoult TempLogin(string username, string password)
{
// imaging you have own temp user manager, completely independent from identity
if(_tempUserManager.IsValid(username,password))
{
// user is valid, going to authenticate user for my App
var ident = new ClaimsIdentity(
new[]
{
// adding following 2 claim just for supporting default antiforgery provider
new Claim(ClaimTypes.NameIdentifier, username),
new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", "ASP.NET Identity", "http://www.w3.org/2001/XMLSchema#string"),
// an optional claim you could omit this
new Claim(ClaimTypes.Name, username),
// you could even add some role
new Claim(ClaimTypes.Role, "TempUser"),
new Claim(ClaimTypes.Role, "AnotherRole"),
// and so on
},
DefaultAuthenticationTypes.ApplicationCookie);
// Identity is sign in user based on claim don't matter
// how you generated it Identity
HttpContext.GetOwinContext().Authentication.SignIn(
new AuthenticationProperties { IsPersistent = false }, ident);
// auth is succeed,
return RedirectToAction("MyAction");
}
ModelState.AddModelError("", "We could not authorize you :(");
return View();
}
Since we injected our logic to Identity, we don't need to do extra thing at all.
[Authorize]
public ActionResult MySecretAction()
{
// all authorized users could use this method don't matter how has been authenticated
// we have access current user principal by calling also
// HttpContext.User
}
[Authorize(Roles="TempUser")]
public ActionResult MySecretAction()
{
// just temp users have accesses to this method
}
You'll need to extend the ASP.NET Identity Libraries, using your custom logic and/or storage.
Here you can find an example in my Github account with some useful links that I used to read when I was trying to understand the ASP.NET Identity stuff: https://github.com/hernandgr/AspNetIdentityDemo
Hope it helps!

Categories