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.
Related
I am working with an Identity server 4 system. We are using the exact code from the MvcHybridAutomaticRefresh sample
The issue is with this code here. AutomaticTokenManagementCookieEvents.cs#L73
var response = await _service.RefreshTokenAsync(refreshToken.Value);
if (response.IsError)
{
_logger.LogWarning("Error refreshing token: {error}", response.Error);
return;
}
Currently if a refesh token was revoked by the admins, or the refresh token has expired ( we do not have sliding refresh tokens enabled) Then the application will crash. I would expect it to reroute the user to the login screen.
I am i missing something in this sample that it cant handle that?
I have also posted this as a question on the issue forum #3599
current attempt
is to add The following rather where it detects the error
await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
which i had hoped would log the user out. This just hangs and never goes anywhere. Its not even logging you out of the server.
Current Solution
The only thing i can find currently that remotely works is to add a catch in the api call. This is not ideal as in our actual application we have a lot of api calls this would mean making a lot of changes to our application. Isnt there a way to force a login directly from the middle wear itself?
[Authorize]
public async Task<IActionResult> CallApi()
{
try
{
var token = await HttpContext.GetTokenAsync("access_token");
var client = _httpClientFactory.CreateClient();
client.SetBearerToken(token);
var response = await client.GetStringAsync(Constants.SampleApi + "identity");
ViewBag.Json = JArray.Parse(response).ToString();
return View();
}
catch (Exception)
{
return new SignOutResult(new[] { "Cookies", "oidc" });
}
}
You can add just one row to force the middleware to perform the challenge again:
if (response.IsError)
{
_logger.LogWarning("Error refreshing token: {error}", response.Error);
context.RejectPrincipal();
return;
}
I have an identity server 4 application and I have added to the email scope [IdentityResources] table in the database. I have also added the email scope to the client that i am using with my client application.
The client application is now prompting the user for email scope consent after login.
I can also see that its there in the UserClaimsPrincipalFactory
protected override async Task
GenerateClaimsAsync(ApplicationUser user)
{
var identity = await base.GenerateClaimsAsync(user);
if (user.IsXenaSupporter)
identity.AddClaim(new Claim("Supporter", user.Id.ToString()));
return identity;
}
Identity does contain email. Yet when the Id token and access token are returned to the application neither contain an email. Nor is there an email when i reqeust it from the user info end point.
What do I need to do to populate email address in the claims when the application requests the email scope? Also my custom supporter claim is also not being added
The simple fact that the client application is prompting you for the email scope only means, that the scope was allowed in IdentityServer and requested on the client end but not necessarily that this information is being retrieved.
The magic is in the GetProfileDataAsync method of your IProfileService implementation.
This profile service is where you retrieve whatever claims you'd like and add them to the ProfileDataRequestContext.
public Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var subjectId = context.Subject.GetSubjectId();
Guid.TryParse(subjectId, out Guid g);
//whatever way or wherever you retrieve the claims from
var claimsForUser = idRepo.GetUserClaimsBySubjectId(g);
context.IssuedClaims = claimsForUser.Select(c =>
new Claim(c.ClaimType, c.ClaimValue)).ToList();
return Task.FromResult(0);
}
As explained here, an id token should pretty much only have a sub claim - that's what the userinfo endpoint is for.
The problem was that i had only added it to the [IdentityResources] table.
This simply defines the different scopes. But it doesn't actually assign any data.
To do that i needed to add it to the [IdentityClaims] table.
As soon as i did this the data started being returned in the claims.
I'm using Microsoft Graph C#.NET SDK to access user's mail inbox. The problem is that when I do authentication the token that Microsoft sends me back is valid just for 1 hour or so and it expires so early. But it's so annoying for user to login every 1 hours just to see the outlook mail inbox. I need to make this login PERSISTENT.
Here is the code that I use:
public static async Task Run()
{
string secret = "MyDamnPrivateSecret";
PublicClientApplication clientApp = new PublicClientApplication(secret);
GraphServiceClient graphClient = new GraphServiceClient("https://graph.microsoft.com/v1.0", new DelegateAuthenticationProvider(async (requestMessage) =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", await GetTokenAsync(clientApp));
}));
//Processing mailbox ...
}
private static async Task<string> GetTokenAsync(PublicClientApplication clientApp)
{
if (string.IsNullOrEmpty(Properties.Settings.Default.token) || string.IsNullOrWhiteSpace(Properties.Settings.Default.token))
{
//need to pass scope of activity to get token
string[] Scopes = { "User.Read", "Mail.ReadWrite" };
string token = null;
AuthenticationResult authResult = await clientApp.AcquireTokenAsync(Scopes);
token = authResult.AccessToken;
Properties.Settings.Default.token = token;
Properties.Settings.Default.Save();
return token;
}
else
{
return Properties.Settings.Default.token;
}
}
Is there any way to make expiration time last longer? Or make a refresh token or something to persist login?
You'll need to request the offline_access scope to get a refresh token. If you're using an older version of MSAL, you'll need to implement and pass a token cache in the PublicClientApplication constructor which I think that MSAL will use to automatically refresh the access token. I think the newer version handles the tokenCache for you.
From the docs, this is the recommended call pattern: first try to call AcquireTokenSilentAsync, and if it fails with a MsalUiRequiredException, call AcquireTokenAsync.
private static async Task<string> GetTokenAsync(PublicClientApplication clientApp)
{
AuthenticationResult result = null;
try
{
string[] scopes = { "User.Read", "Mail.ReadWrite", "offline_access" };
// Get the token from the cache.
result = await app.AcquireTokenSilentAsync(scopes, clientApp.Users.FirstOrDefault());
return result.AccessToken;
}
catch (MsalUiRequiredException ex)
{
// A MsalUiRequiredException happened on AcquireTokenSilentAsync.
// This indicates you need to call AcquireTokenAsync to acquire a token
System.Diagnostics.Debug.WriteLine($"MsalUiRequiredException: {ex.Message}");
try
{
// Dialog opens for user.
result = await app.AcquireTokenAsync(scopes);
return result.AccessToken;
}
catch (MsalException msalex)
{
ResultText.Text = $"Error Acquiring Token:{System.Environment.NewLine}{msalex}";
}
}
catch (Exception ex)
{
ResultText.Text = $"Error Acquiring Token Silently:{System.Environment.NewLine}{ex}";
return;
}
}
Here's a sample for reference. https://github.com/Azure-Samples/active-directory-dotnet-desktop-msgraph-v2
I will try to clarify the issues here:
MSAL .net is build for different platforms - .net desktop, .net core, UWP, xamarin android and xamarin iOS. On some of these platforms (UWP and xamarin) we persist the token cache for you. On the others, we expect you to persist the cache. The reason is that we cannot provide token serialization logic that works well for all scenarios (e.g. ASP.NET server farms), so we expect you to do it. We provide samples and guidance around it this. Details and some reference implementations on the MSAL wiki:
The sample code provided by #Michael is ok for MSAL v1. In MSAL v2 the things are a bit different and you can find the pattern of calling is also on the MSAL wiki:
We request and store the refresh token (RT). If the auth token (AT) is expired, we will request a new one based on the RT - this will happen without user interaction. This should all be transparent to you, i.e. it should just work :). Make sure that your token cache serialization works, i.e. you get an account when performing
// perform an interactive login first
// otherwise there will be no AT / RT in the store
var accounts = await app.GetAccountsAsync();
// there should be an account that you can use
Most of our samples show how to call the Graph. See all the samples by scenario here. For your use case I recommend you check out Calling the Graph from a WPF app
Also check out #Daniel Dobalian's answer for default expiration of AT and RT:
MSAL token expires after 1 hour
In your code, AcquireTokenAsync does always trigger login.
Instead, you need to implement a token cache and use AcquireTokenSilentAsync.
For more information, please review the following link:
Microsoft Graph SDK - Login
Using Owin + Oauth2 + Identity2.
I have a web Api with default basic authentication setup that i have modified.
my startup.cs partial class
public void ConfigureAuth(IAppBuilder app)
{
// 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
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);//TODO: prob wont need this
// Configure the application for OAuth based flow
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),//TODO: prob wont need this
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
// In production mode set AllowInsecureHttp = false
AllowInsecureHttp = true //TODO: set debug mode
};
// Token Generation
app.UseOAuthBearerTokens(OAuthOptions);
}
my startup.cs class partial at the root
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
ConfigureAuth(app);
WebApiConfig.Register(config);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
}
my applicationOAuthProvider.cs
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
//get user
var service = new CarrierApi.CarrierManagementClient();
var result = service.LoginAsync(context.UserName, context.Password);
var user = result.Result.Identity;
//TODO: log stuff here? i.e lastlogged etc?
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
ClaimsIdentity oAuthIdentity = user;
ClaimsIdentity cookiesIdentity = user;
AuthenticationProperties properties = CreateProperties(user.GetUserName());
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
As you can see i actually go and get the identity via a wcf call to our existing DB. when using postman, i get the /token url and obtain my bearer token, on the next request i pass it into the header and call my controller method.
[Authorize(Roles = "Templates_Access")]
public string Post([FromBody]string value)
{
return "woo";
}
This works great, if the user has the permission it wont allow access, if they do it does.
However if i go to our website that uses the same wcf and DB and change a users permission, if i send the same request on postman it still allows access even though ive removed that permission on the role the user is assigned too.
How do i make sure that permissions are "refreshed" or checked again on each request?
Every roles of a user logged in are stored in the bearer token at login time as claim, in the GrantResourceOwnerCredentials method. If a request have to be authorized, the role is searched on the list stored in the bearer token by the default implementation of AuthorizationFilter; so if you change the user's permissions, you need a new login.
This behavior respects the Stateless contraint of a Restfull architecture, as Fielding wrote in his dissertation, and also this is a good balance between performance and security
If you need a different behavior there is more than one possibility.
Refresh Token
You Can use Refresh Token, implementing the GrantRefreshToken method of applicationOAuthProvider class; you can retrieves the refreshed user's permissions and create a new access token; this is a good article to learn how.
Keep in mind:
more complexity on client
no real time effect; you have to wait the access Token expiring
If the Access Token has a short life, you have to update it often (even when the user permissions are not changed) otherwise a long life does not solve the problem
Check Permissions each request
You can implement a custom AuthorizationFilter and check in the database the permissions of the user, but it is a slow solution.
Cache and Login Session
You can generate a user session's key (like a guid) for each login in the GrantResourceOwnerCredentials method, and store it in the bearer token as a claim. You have to store it also in a cache system (like Redis), using two index: the user session's key and the userId. The official documentation of Redis explains how.
When the permessions of a user are changed, you can invalidate every sessions of that user in the cache system, searching by userId
You can implement a custom AuthorizationFilter and check for each request in cache if the session is valid, searching by user session's key.
Be careful: this will violate the stateless constraint and your architecture will not restfull
Here you can find the standard implementation of AuthorizaAttribute filter.
You can create your custom filter extending AuthorizeAttribute and overriding the IsAuthorized method.
Most likely there are other ways, but how often are changed the permissions of a user? In many systems, also in systems where the security is the first requirement, if the permissions's configuration of a user is changed during an active session, it is necessary a new login to active the new one.
Are you sure you needs to modify this standard behavior?
If you are, I suggest the solution with a cache system.
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.