I am working on an ASP.NET MVC application which uses ADFS authentication and have the following error in our log files in production and I'm trying to figure out what the cause of this issue is, as I believe it is preventing some users from accessing our application.
The error is as follows:
System.IdentityModel.Tokens.SecurityTokenExpiredException: IDX10223: Lifetime validation failed. The token is expired.
ValidTo: '08/13/2018 12:59:35'
Current time: '08/13/2018 13:15:34'.
While I can't be sure, since I don't have timestamps of when the error happened, I believe it is causing the classic ASP.NET Server Error in '/' Application and it's the only error I'm seeing in our logs that would correlate with that page appearing.
As I'm searching Stack Overflow, I see references to JWT authorization which is not what our application is using. Or at least we aren't using anything that explicitly uses JWT for authentication, it may be what's happening under the hood. I also see some posts which state that if the authentication server and application server times are not in sync this error can occur; I am working with my IT team to verify these server's clocks are in sync and will update accordingly.
Our application uses a singular MVC route to serve our Angular application, and only enforces authentication on that landing page; our API controllers do not have specific authorization requirements on them (I know, bad security practice, that's a whole other conversation I'm trying to have with my team's architect).
While I wait for the information on the clocks, are there any other possible options I can investigate?
OWIN Startup code
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
// Workaround for this bug: http://katanaproject.codeplex.com/workitem/197
app.UseKentorOwinCookieSaver();
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
CookieSecure = CookieSecureOption.Always,
CookieName = "Adfs Cookie Name",
});
app.UseWsFederationAuthentication(new WsFederationAuthenticationOptions
{
MetadataAddress = WebConfigurationManager.AppSettings["WSFederation:MetadataAddress"],
Wtrealm = WebConfigurationManager.AppSettings["WSFederation:Realm"],
SignOutWreply = WebConfigurationManager.AppSettings["WSFederation:Realm"],
Notifications = new WsFederationAuthenticationNotifications
{
RedirectToIdentityProvider = ctx =>
{
if (IsAjaxRequest(ctx.Request))
{
ctx.HandleResponse();
}
return Task.FromResult(0);
}
}
});
return app;
}
private static bool IsAjaxRequest(IOwinRequest request)
{
var query = request.Query;
if (query != null && query["X-Requested-With"] == "XMLHttpRequest")
{
return true;
}
var headers = request.Headers;
if (headers != null && headers["X-Requested-With"] == "XMLHttpRequest")
{
return true;
}
return false;
}
}
Finally figured out what the issue was! But first, some background. The web app I'm building integrates with a WPF application via a browser control in the application. The browser control is a tab that is not selected on the initial load of the application, but the does at least make a request and get redirected to ADFS for authentication. However, the browser wasn't completing the redirect from ADFS to my app until after the browser tab is activated.
Why does all of this matter? Well the ADFS token was configured with a 1hr lifetime. So what happened was users would open the WPF app, and automatically authenticate with ADFS and get a token generated. However, if they didn't activate the tab within that 1hr lifetime the token would expire before the redirect completed. I think this could also happen if I opened a tab in a browser, signed in to ADFS, and then immediately moved to a different tab before my app would have time to be served. Ultimately, it's a weird edge case for my application, but the root of the problem was a token getting issued but not validated by my app server until after it had already expired.
Related
I have created a small .NET Core 3.1 console application using the MSAL library which requests scope api://55a047a1-a0d1-4b6b-9896-751a848e1e06/testscope2
Custom API exposes two scopes
api://55a047a1-a0d1-4b6b-9896-751a848e1e06/testscope1
api://55a047a1-a0d1-4b6b-9896-751a848e1e06/testscope2
I have also configured another application named test-app in Azure Active Directory which represents my console application.
I have configured only one API permission (api://55a047a1-a0d1-4b6b-9896-751a848e1e06/testscope1) for this application. My understanding is with this configuration in place client app will only be able to request for scope test1 and it won't allow test-app to request for scope2
Below is screenshot
This is my code:
//<PackageReference Include="Microsoft.Identity.Client" Version="4.13.0" />
namespace console_client
{
class Program
{
static void Main(string[] args)
{
#region Azure AD parameters
var clientId = "dddeefa5-d95c-4931-a53d-2382deee27c3";
var tenant = "-- MY TENANT ID--";
var instance = "https://login.microsoftonline.com/";
#endregion
var client = PublicClientApplicationBuilder
.Create(clientId)
.WithDefaultRedirectUri()
.WithAuthority($"{instance}{tenant}")
.Build();
List<string> scopes = new List<string>();
try
{
// I was under impression that this call will throw as exception as
// this app is requesting 'testscope2' which is not included in API Permissions
// while configuring test-app in Azure Active Directory (dddeefa5-d95c-4931-a53d-2382deee27c3 )
// But I was able to retrieve token back with testscope2 in it.
scopes.Add("api://55a047a1-a0d1-4b6b-9896-751a848e1e06/testscope2");
var authenticationResult = client.AcquireTokenInteractive(scopes).ExecuteAsync().Result;
Console.WriteLine($"Interactive Access token is : {authenticationResult.AccessToken}");
}
catch (Exception ex)
{
System.Console.WriteLine($"******* {ex.Message}");
}
}
}
}
Question
Am I missing anything? Why am I getting the access token back even if the app doesn't have permission configured?
Thanks
TL;DR it is a feature.
With the v2 endpoint / MSAL, you can request for scopes that are not defined in your app manifest.
The ones in your app registration are the static permissions required by your application.
But your application can also request dynamic permissions at login time.
The user/admin would still need to consent to that of course, an app won't get a permission without consent.
Your app seems to be a single-tenant app so this doesn't really make a difference for you.
It is mainly for multi-tenant SaaS applications that can require the minimum needed permissions in the app registration/manifest, and then request more permissions for opt-in features as they are needed.
By the way, if you want to use the permissions defined in your app registration, you can request a special scope: api://55a047a1-a0d1-4b6b-9896-751a848e1e06/.default (your app ID URI or client id + "/.default").
This will make AAD look at your app registration to decide which permissions to check consent for.
I have a server sided Blazor app. Every user must be authenticated when he wants to visit a page. For this the user is redirected to an "identity server" login page. When the user logs in with the correct credentials, he is then redirected back to the Blazor app.
I have setup my Blazor app with CascadingAuthenticationState, so that I can access the User object and its claims inside my Blazor pages.
This goes something like this inside a component:
[CascadingParameter]
private Task<AuthenticationState> AuthenticationStateTask { get; set; }
...
...
var authState = await AuthenticationStateTask;
var claims = authState.User.Claims; // Works fine.
When I do it like this I have access to the users claims.
So far so good.
But I also have a middleware class where I need to access the user claims.
public async Task InvokeAsync(HttpContext context)
{
if (context?.User?.Claims != null && context.User.Claims.Any())
{
Console.WriteLine("aaa");
}
// Call the next delegate/middleware in the pipeline
await _next(context);
}
But for some reason the User claims are always empty.
Why is the User.Claims object from inside my Blazor component filled with all the claims, but are they empty when I access them through the HttpContext object?
The order of registering Middleware is critical. This is something I've been personally running into a lot lately in my own projects as well - And yet, there are no warnings if you configure them out-of-order.
Quote from Microsoft docs on Middleware order:
The order that middleware components are added in the Startup.Configure method defines the order in which the middleware components are invoked on requests and the reverse order for the response. The order is critical for security, performance, and functionality.
It seems you are calling your Middleware before the .NET Core authentication middleware, and therefor your User object is null - or lacks claims.
Place your app.UseMiddleware<T> after app.UseAuthentication() and app.UseAuthorization().
I have a test console app which I'm pointing at a local instance of Identity Server 3 to request an access token. The following code does this and returns my token fine (passing a single scope "scope.test.client").
static TokenResponse GetClientToken(string clientId, string clientSecret, string[] scopes)
{
var uri = new Uri(string.Concat(ID_BASE_URI, ID_URL_TOKEN));
var client = new TokenClient(
uri.AbsoluteUri,
clientId,
clientSecret);
return client.RequestClientCredentialsAsync(string.Join(" ", scopes)).Result;
I then use this token to call an API also running locally. This takes the TokenResponse obtained above and passed it to this method:
static void CallApi(string url, TokenResponse response)
{
try
{
using (var client = new HttpClient())
{
client.SetBearerToken(response.AccessToken);
Console.WriteLine(client.GetStringAsync(url).Result);
}
}
catch (Exception x)
{
Console.WriteLine(string.Format("Exception: {0}", x.Message));
}
}
The API (an ASP.NET WebApi project) uses an Owin Startup class to enforce bearer token authentication for all requests:
appBuilder.Map(baseApiUrl, inner =>
{
inner.UseWebApi(GlobalConfiguration.Configuration);
// Enforce bearer token authentication for all API requests
inner.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "https://identityserver/core",
ValidationMode = ValidationMode.ValidationEndpoint,
RequiredScopes = new[] { "scope.test.client" }
});
});
It also ensures all API requests are handled by a custom authorize attribute:
GlobalConfiguration.Configuration.Filters.Add(new DefaultApiAuthorizeAttribute());
Debugging this API, the first line in my overridden OnAuthorize method (in DefaultApiAuthorizeAttribute) is this:
var caller = actionContext.RequestContext.Principal as System.Security.Claims.ClaimsPrincipal;
If I break on this line I can see that actionContext.RequestContext.Principal is always null. However, I can see that ((System.Web.Http.Owin.OwinHttpRequestContext)actionContext.RequestContext).Request.Headers contains an Authorization header with the bearer token passed from my console app.
So it would seem that the API project is not authenticating the bearer token. Certainly the Identity Server logs suggest it isn't being hit at all after issuing the initial access token. So I'd appreciate your expert advice about why this might not be happening, or at least some pointers about where to look.
I suspect it might have something to do with SSL. Both sites are hosted locally under self-signed SSL certs, although Identity Server is configured to not require SSL and uses the idsrv3test.pfx development certificate for signing. I do have another test MVC web app which delegates authentication to the same IS3 instance which works fine locally, so I believe my IS3 instance is configured correctly.
You need to call UseIdentityServerBearerTokenAuthentication before you call UseWebApi. When you set up an OWIN Middleware Pipeline, the order is important.
In your case, Web API will be handling your requests before they get sent onto Identity Server (if they get sent on at all).
I imagine a range of possible issues could have the impact I described, but in my case I was able to find the cause by adding a diagnostics log to my consuming API. This led me to discover that the problem was an assembly conflict. The Owin middleware was looking for a Newtonsoft.JSON assembly with version 8.0.0.0 but my consuming API (actually running on top of a CMS intance) was using 7.0.0.0.
For anyone else who wants to find the answer fast, rather than spend hours tweaking configurations, here's the documentation that describes how to add this logging: https://identityserver.github.io/Documentation/docsv2/consuming/diagnostics.html
We are trying to set up Identity Server 3 in the right way.
We got authentication working fine and we manage to retrieve the refresh token.
The client application is using Angular.
Now when the acces_token expires any calls to the rest api fails (we managed to get it to return 401) but we are wondering how to re-authenticate the user.
In our tests, any api call made from Javascript is failing (401) but as soon as the page is refreshed the whole mechanism is kicking in. We do see that we are redirected to the identity server but it does not show up the login page, we are sent back to the client application with new tokens apparently.
What I would like to do is to refresh the access token without having to force the user to refresh the page.
What I'm not sure though is whose responsibility is it? Is that the client application (website) or the angular application? In other word, should the application handle this transparently for Angular or should angular do something when it receives a 401, in which case, I'm not too sure how the information will flow back to the web app.
Any clue?
Additional Information: We are using OpenId Connect
I got it working!
As I said in the comments I used this article. The writer is referencing a very nice lib that I am using as well.
Facts:
Identity Server 3 is requesting the client secret upon access token refresh
One should not store the refresh_token or the client_secret on the javascript application as they are considered unsafe (see the article)
So I chose to send the refresh_token as en encrypted cookie sith this class (found of ST BTW, just can't find the link anymore, sorry...)
public static class StringEncryptor
{
public static string Encrypt(string plaintextValue)
{
var plaintextBytes = plaintextValue.Select(c => (byte) c).ToArray();
var encryptedBytes = MachineKey.Protect(plaintextBytes);
return Convert.ToBase64String(encryptedBytes);
}
public static string Decrypt(string encryptedValue)
{
try
{
var encryptedBytes = Convert.FromBase64String(encryptedValue);
var decryptedBytes = MachineKey.Unprotect(encryptedBytes);
return new string(decryptedBytes.Select(b => (char)b).ToArray());
}
catch
{
return null;
}
}
}
The javascript application is getting the value from the cookie. It then deletes the cookie to avoid that thing to be sent over and over again, it is pointless.
When the access_token becomes invalid, I send an http request to the application server with the encrypted refresh_token. That is an anonymous call.
The server contacts the identity server and gets a new access_token that is sent back to Javascript. The awesome library queued all other requests so when I'm back with my new token, I can tell it to continue with authService.loginConfirmed();.
The refresh is actually pretty easy as all you have to do is to use the TokenClient from IdentityServer3. Full method code:
[HttpPost]
[AllowAnonymous]
public async Task<JsonResult> RefreshToken(string refreshToken)
{
var tokenClient = new TokenClient(IdentityServerConstants.IdentityServerUrl + "/connect/token", "my-application-id", "my-application-secret");
var response = await tokenClient.RequestRefreshTokenAsync(StringEncryptor.Decrypt(refreshToken));
return Json(new {response.AccessToken});
}
Comments are welcome, this is probably the best way to do that.
For future reference - using refresh tokens in an angular (or other JS) application is not the correct way as a refresh token is too sensitive to store in the browser. You should use silent renew based on the identityserver cookie to get a new access token. Also see the oidc-client-js javascript library, as this can manage silent renew for you.
I have tried many options and this is my last resort to see if any of the community members have any ideas.
I have .NET MVC 5 application in which I use a Filter to force HTTPS on each unsecured request.
Here is the scenario:
Access my application at say, http://portal.mywebsite.com
It is redirected to third party (auth0) SSO provider for authentication. If the user is not already authenticated, he is redirected to the SSO login page.
The user enters valid credentials, authenticated.
The above scenario works perfectly. But the issue is If I access the same application with https say https://portal.mywebsite.com, it fails. To be precise, it fails to retrieve a ExternalIdentity (ExternalCookie) on the server.
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var externalIdentity = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
if (externalIdentity == null)
{
throw(new Exception("Could not get the external identity. Please check your Auth0 configuration settings and ensure that " +
"you configured UseCookieAuthentication and UseExternalSignInCookie in the OWIN Startup class. " +
"Also make sure you are not calling setting the callbackOnLocationHash option on the JavaScript login widget."));
}
AuthenticationManager.SignIn(new AuthenticationProperties { IsPersistent = true }, CreateIdentity(externalIdentity));
return RedirectToLocal(returnUrl);
}
Also, accessing the application with https on my test environment works and not the production environment.
All my web applications are hosted as Azure WebRoles.
I tried Fiddler to watch the requests between working and non-working to see if I can find any useful information in identifying the issue but no success.
Any thoughts or ideas that I could try to help me narrow down the cause?
Thanks in advance!
There is a bug in Microsoft's Owin implementation for System.Web. The temporary fix is addressed here at github.com/KentorIT/owin-cookie-saver
Someone had the same issue .AspNetApplicationCookie and ASP.NET_SessionId not created