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.
Related
I have SPA developed application on which I used to implement Oidc-Client for OAUTH authentication and below are the clarifications.
How to configure silent-refresh page with web pack config file in angular structure based project since silent-refresh.html is not invoked on token expiration.
Even if silent token generated then how to get/set expiration time of silently generated token?
Kindly help and suggest.
SILENT REFRESH
Rather than a separate HTML page, my personal preference is to handle this by a silent token renewal response to the index.html page. Then write code like this:
if (window.top === window.self) {
// Run the main app
const app = new App();
app.execute();
} else {
// If our SPA is running on an iframe, handle token renewal responses
const app = new IFrameApp();
app.execute();
}
I find that this approach avoids adding complexity to the WebPack / build system. The code for the iframe app does very little other than receiving the silent token renewal response.
EXPIRY
Interesting why you want to use access token expiry times directly. You can get the value like this:
const user = await this._userManager.getUser();
if (user) {
console.log(user.expires_at);
}
The real requirement here is to ensure that you avoid errors for end users when an API call fails due to an expired access token. This is best handled via the following actions:
If an API call fails with a 401 status code
Then try to get a new access token, generally via userManager.signInSilent()
Then retry the API call with the new access token
Therefore the way you call APIs should have a helper class with some retry logic, as in my example here.
To get notified after silent refresh, add an event handler for userLoaded: UserManager.events.addUserLoaded. This will pass the new User with a new expire time
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.
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
I have a MVC 5 asp.net website where I need to expose a number of REST APIs to a stand-alone mobile client. The rest of the site is using Forms based security where it sets the ASP.NET_SessionId as a cookie, and that is used to authenticate the user with the request after they log in. With my mobile application, I am not able to use the cookie method because of the cross-doman issue. What I would like to do is add a header "X-SessionId" with the value of the ASP.NET_SessionId, then on the server side, have a filter that looks for that field, and if it is present, associates the request with the given session. (Client will log in with an AJAX POST call which will return the ASP.NET_SessionId upon successful login).
Is this possible?
Something like this?
public sealed class CustomSecurityAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext == null)
throw new ArgumentNullException("filterContext");
if (string.IsNullOrEmpty(filterContext.HttpContext.Request.Headers["X-SessionId"]) && IsAuthenticated(ilterContext.HttpContext.Request.Headers["X-SessionId"]))
filterContext.Result = new HttpNotFoundResult();
}
private bool IsAuthenticated(string sessionId)
{
// get your user details from your database (or whatever)
var user = new UserRepository().Get(sessionId);
if (user == null)
return false;
// build up an identity, use your own or out of the box.
FormsIdentity itentity = new MyIdentity(user);
// Set the user
filterContext.HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(itentity , user.Roles);
return true;
}
}
You are going to have to store current sessions in your database, so for example when a user logs in grab the sessionid and stick it in the db, so you know they have 1..n current sessions.
Then you can look it up as part of your authentication.
Edit:
Let's take a step back, never mind cookies and sessions for the moment.
You have a website and a restful api, they both servce different purposes and clients and have different security requirements.
So what are the most common options for securing your Api?
Basic authentication.
Most restful APIs require a username/password to be sent through with each request, as part of the headers or in the request itself.
An authentication token
You can provide a token associated with a user account (a guid could suffice) when requests are made you check for the token.
Using an existing protocal like OAuth
I would recommend using these common scenarios to be sure you don't miss something and open your self up to security vulnerabilities.
Is there a reason you can't use any of these?
I am writing a wrapper around a SOAP API. I have a service reference set up in VS2010 to point to the WSDL. When I make a call to login, the API returns a session variable in a cookie.
I've set allowCookies="true" on my binding in config.
I've implemented two API calls in my wrapper so far: login and logout.
I have a test harness that is a simple ASP.NET application that has a page for login and a page for logout.
When login is submitted:
using (var ApiClient = new ApiClient())
{
ApiClient.Login(txtUsername.Text, txtPassword.Text, txtOrganization.Text, txtIPAddress.Text);
}
And now in my ApiClient.Login method:
using (var soapService = new WSDLInterfaceClient())
{
var loginCredentials = new loginRequest
{
username = username,
password = password,
organization = organization
};
if (!string.IsNullOrWhiteSpace(ipAddress))
loginCredentials.ipAddress = ipAddress;
var loginResponse = soapService.Login(loginCredentials);
}
So this all goes off without a hitch. I was thinking I would need to remove the usings and have a class level WSDLInterfaceClient that I would use within my wrapper because I figured the cookies would be wiped out each time I constructed a new client. But that is simply not the case.
The logout method is implemented similarly. The logout API call will throw an exception if you try to logout without being logged in. Oddly enough, when I go to my logout page and submit (which in turn is constructing a new wrapper client and therefore a new service client) it recognizes that I am logged in. If I try to logout again it throws the exception as expected.
The cookies seem to be working in that even when constructing both a new interface client and a new wrapper client on each page, the cookies persist.
While this isn't a bad thing, I am perplexed as to how it is working. Is there somewhere that ASP.NET/WCF is saving these cookies for the session? How would this work in a console app?