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?
Related
So I have an application that's calling the graph API.
See the following snippet:
When the try (AquireTokenSilent) is called, the Web Request completes successfully no problem.
However, when make the same web request with the token I get from AcquireTokenAsync, I'm getting a 404 error and an exception thrown.
1) Can you recommend some good tools to analyze the HTTP request (so I can compare the difference and identify a problem). The Visual Studio debugger is helpful, but I can't see the whole picture, which obfuscates a possible problem here.
2) Can you help me identify why exactly one is successful and one fails? Both tokens seem to be acquired successfully, so I'm not sure what the problem is.
So I have figured out what was the underlying cause here.
In our authentication scenario, we have multiple products using azure AD SSO in the ecosystem. Since 'OnAuthorizationCodeReceived' is only called on login, and NOT when a valid login cookie is already held, the token cache will not be populated with the authorization code. So in this case, the Microsoft code samples for this scenario are dead wrong. Issuing an authentication challenge wont cause 'OnAuthorizationCodeReceived' to be called, as you already hold a valid login token.
So, while it's a litte ugly, the fix is dead simple. Force a logout, so that the token cache can be populated.
catch (AdalSilentTokenAcquisitionException e)
{
//in this case, it's possible there's no authorization code because the login cookie is from another session in
//the ecosystem. So in this scenario, force a logout so we can get a token into the tokencache
context.GetOwinContext().Authentication.SignOut(OpenIdConnectAuthenticationDefaults.AuthenticationType,
CookieAuthenticationDefaults.AuthenticationType);
sessionState.Abandon();
}
Now, because we're using this code outside the Controllers, and we call await, HttpContext will be null. Some serious voodoo going on in HttpContext, but I digress. We can use this little workaround to hang onto the context:
var context = HttpContext.Current;
var sessionState = context.Session;
EDIT: Came across one more problem when deploying the application to an azure app-service. You want to make sure Azure AD Authentication is toggle on in the 'Authentication' panel on Azure. We had some infinite login loop problems until I toggled this.
EDIT:
So, forcing a logout in this scenario really didn't sit well with me. BUT, I came across this question:
Azure Active Directory Graph API - access token for signed in user
And what we can do is follow the answer, and call AcquireTokenByAuthorizationCodeAsync(...), and make sure to use the 4 parameter method overload, where the last param is "https://graph.windows.net/"
Now, as long as we store the authorization code somewhere (in my case stored in a DB table). We should be able to get the authorization code for a given user, and get a new GraphAPI token, in the case where AcquireTokenSilentAsync(...) fails.
Now your statefull tokencache can be backed up by a stateless database call!
catch (AdalSilentTokenAcquisitionException e)
{
//in this case, the stateful cache is empty, so lets get the codeId from the DB
PersistentTokenCache pt = db.PersistentTokenCaches.Find(userObjectId);
if (pt != null && pt.token != null)
{
try
{
result = await ath.AcquireTokenByAuthorizationCodeAsync(pt.token,
new Uri(Startup.hostUri),
cc,
"https://graph.windows.net");
}
catch (AdalException ex)
{
Debug.WriteLine(ex.StackTrace);
//both authentication types have failed
pt.token = null;
await db.SaveChangesAsync();
context.GetOwinContext().Authentication.SignOut(OpenIdConnectAuthenticationDefaults.AuthenticationType,
CookieAuthenticationDefaults.AuthenticationType);
sessionState.Abandon();
return -1;
}
}
}
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.
What is the different between the following 3 methods to retrieve the claim?
Called in a ApiController:
((ClaimsIdentity) HttpContext.Current.User.Identity).Claims
((ClaimsIdentity) Thread.CurrentPrincipal.Identity).Claims
((ClaimsIdentity) User.Identity).Claims
The first two attributes have stored the same data but the last one has stored the data from the previous session.
This is done in the logout method:
UserCache.Instance.Clear();
FederatedAuthentication.SessionAuthenticationModule.SignOut();
HttpContext.Current.User = new GenericPrincipal(new GenericIdentity(string.Empty), null);
Update
Mixed WebForms, WebApi, MVC Application
Most of the application is build using WebForms.
If you are working with WebApi, then HttpContext.Current should not be available directly (see this answer). So I'm guessing you are using MVC as well and you see MVC context there.
Thread.CurrentPrincipal is dangerous to use because it contains thread principle which can be something you never expect, like user that actually runs IIS (AppPool user). Most of the time it is what you think, but sometimes it is not. And this will cause you endless bug-chasing that you can never recreate yourself.
User.Identity as ClaimsIdentity is the correct way to get what you need and it is used in the default template from VS. However if you see the data from "previous session" - means your cookies are not cleared properly. And the way you sign-out user looks suspicious:
What is UserCache.Instance?
SignOut method does not actually sign out user until the request is complete. So if you call this and then check for user identity within the same request, you'll see the same identity intact.
Assigning HttpContext.Current.User will not give you much within the request. See very first point if we are talking about pure WebAPI.
Default sign-out is done via IAuthenticationManager
private IAuthenticationManager Authentication
{
get { return Request.GetOwinContext().Authentication; }
}
[Route("Logout")]
public IHttpActionResult Logout()
{
Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationType);
return Ok();
}
Try this and then adjust for your needs.
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'm coming across a peculiar request: I have a website that uses Forms Authentication, but it now needs to grab the WindowsPrincipal (Windows Authentication) at some point in order to reuse it.
My first instinct was to create a new page, and to disable Anonymous Access on IIS for that precise page. When I'm on that page, Request.ServerVariables["LOGON_USER"] gives me the current Windows login name as expected.
Unfortunately, Context.User still gives me a GenericPrincipal.
Any idea on how I could get the current WindowsPrincipal in a FormsAuthentication Application? (recreating it by asking the user for his password is not an option)
Found it: Context.Request.LogonUserIdentity is what should be used in this scenario.
It will return the windows user that made the request if Anonymous Access is disabled on IIS (otherwise it'll return the IIS anonymous user).
For those interested on how to reuse it:
lblUserName.Text = WindowsIdentity.GetCurrent().Name;
// will return the ASP.Net user (that's useless to us)
WindowsIdentity id = Context.Request.LogonUserIdentity;
WindowsImpersonationContext ctx = id.Impersonate();
try
{
lblUserName.Text = WindowsIdentity.GetCurrent().Name;
// will return the correct user
// (...) do your stuff
}
finally
{
ctx.Undo();
}