ServiceStack: Accessing the HttpRequest in a selfhosted application - session

I currently have an IIS hosted application that I would like to switch over to use the self-hosted method.
But I'm having difficulty accessing the session so I can retrieve the current users username.
This is the code I used when hosting under IIS which worked perfectly:
/// <summary>
/// A basic wrapper for the service stack session, to allow access to it lower down in the DAL layer without tying us to servicestack.
/// </summary>
public class ServiceStackAuthTokenService : IAuthTokenService
{
/// <summary>
/// GetCurrentAuthToken.
/// </summary>
/// <returns>A string representing the users auth name.</returns>
public string GetCurrentAuthToken()
{
// Grab the current request.
var req = HttpContext.Current.Request.ToRequest();
var res = HttpContext.Current.Response.ToResponse();
// Fetch the authentication service.
var authService = EndpointHost.AppHost.TryResolve<AuthService>();
authService.RequestContext = new HttpRequestContext(req, res, null);
// Grab the session.
var session = authService.GetSession(false);
// Return the username.
return session.UserName;
}
public string UserPropertyName
{
get { return "UserName"; }
}
}
This is added to the app host with the following code::
container.RegisterAutoWiredAs<ServiceStackAuthTokenService, IAuthTokenService>()
When running self-hosted the HttpContext.Current is null, how do I access the request under a self-hosted application?
Thanks!
Update
Additional things I have tried:
as per an post here: https://groups.google.com/forum/#!msg/servicestack/jnX8UwRWN8A/_XWzTGbnuHgJ
It was suggested to use:
container.Register>(c => AuthService.CurrentSessionFactory);
This just returns a newed IAuthSession.
What the user in that post is doing is exactly what I'm trying to achieve.
In the last post Mythz says:
Just to be clear, in order to form the Session Key that references the Users session you need either the ss-id or ss-pid cookies (as determined by ss-opts).
You can get cookies off the IHttpRequest object or otherwise in ASP.NET the HttpContext.Current.Request singleton, so whatever IAuthUserSession factory you inject needs to take something that can give it the cookies, i.e. either an IRequestContext, IHttpRequest, IService, etc.
But I still cant see a way to access the IHttpRequest.

For ServiceStack 3, you can share request data via the HostContext.Instance.Items Dictionary. For ServiceStack 4, you should use the HostContext.RequestContext.Items Dictionary.
For example, add a request filter in your app host configuration to save the value:
// Put the session into the hostcontext.
RequestFilters.Add((req, res, requestDto) =>
{
HostContext.Instance.Items.Add("Session", req.GetSession());
});
Then in your authentication token class pull it back out:
public string GetCurrentAuthToken()
{
var session = HostContext.Instance.Items["Session"] as AuthUserSession;
if (session != null)
{
return session.UserName;
}
throw new Exception("No attached session found.");
}

Related

Unable to fix veracode cwe id 918 flaw (SSRF) when using API gateway pattern in a Microservices architecture

I am using API Gateway Pattern in a Micro services architecture in which the Front End Angular app makes an HTTP request to my API Gateway project which is simply a ASP.net Core 3.1 Web API project. Currently I only have 2 micro services and an API Gateway and all of them are of type ASP.net Core 3.1 Web API project. The API Gateway project has all the controllers of my micro services. The purpose of the API Gateway is just to receive the request from Front end and make an HTTP Request to the appropriate Micro service.
Now in the AccountController.cs of my API Gateway project, I have the following code
/// <summary>
/// Gets the detail of an account by its id
/// </summary>
/// <param name="organizationId">Id of the Organization of which the account belongs to</param>
/// <param name="accountId">Id of Account of which information is being requested</param>
/// <returns>Account's Details</returns>
[HttpGet("{organizationId}/{accountId}")]
public async Task<IActionResult> GetAccountAsync(Guid organizationId, Guid accountId)
{
_uri = new Uri(uriString: $"{_configurationService.AccountAPI}GetAccount/{organizationId}/{accountId}");
using var result = await _client.GetAsync(_uri);
var content = await result.Content.ReadAsStringAsync();
return Ok(content.AsObject<MessageResponse<AccountDetailVM>>());
}
After searching about the SSRF issue on stackoverflow I found the following recommendation at Veracode community.
Veracode Static Analysis will report a flaw with CWE 918 if it can
detect that data from outside of the application (like an HTTP Request
from a user, but also a file that may have been uploaded by a user,
database data, webservice data, etc) is able to change the nature of a
network request.
On Stackoverflow I found the following fix
For CWE ID 918 it is hard to make Veracode recognize your fix unless you have static URL. You need to validate all your inputs that become parts of your request URL.
That means I had to sanitize my input parameters OrganizationId and AccountId before appending them to the request URL.
Also another question on the veracode community suggested
The only thing that Veracode Static Analysis will automatically detect as a remediation for this flaw category is to change the input to be hardcoded
and they proposed a solution for the query string
The given example appears to take a model identifier and put it in the
URL used in an internal request. We would recommend validating the ID
per the rules you have for this datatype (typically this should only
be alphanumeric and less than 255 characters) and URLencode it before
appending it to a URL.
After all those stuff, I have made the following changes to my code
Made sure OrganizationId and AccountId Guid are not empty
URL Encoded the string
Here is the code after changes
/// <summary>
/// Gets the detail of an account by its id
/// </summary>
/// <param name="organizationId">Id of the Organization of which the account belongs to</param>
/// <param name="accountId">Id of Account of which information is being requested</param>
/// <returns>Account's Details</returns>
[HttpGet("{organizationId}/{accountId}")]
public async Task<IActionResult> GetAccountAsync(Guid organizationId, Guid accountId)
{
if (organizationId != Guid.Empty && accountId != Guid.Empty)
{
string url = HttpUtility.UrlEncode($"{_configurationService.AccountAPI}GetAccount/{organizationId}/{accountId}");
using var result = await _client.GetAsync(url);
var content = await result.Content.ReadAsStringAsync();
return Ok(content.AsObject<MessageResponse<AccountDetailVM>>());
}
return BadRequest();
}
Thats All I could do to sanitize my input parameters OrganizationId and AccountId but after all those changes veracode still identifies a SSRF flaw on line
using var result = await _client.GetAsync(url);
I found a hack to fix this issue, I just appended the query string parameters to the Base Address of httpClient and veracode stopped giving me error.
Here is how the solution looks like
/// <summary>
/// Gets the detail of an account by its id
/// </summary>
/// <param name="organizationId">Id of the Organization of which the account belongs to</param>
/// <param name="accountId">Id of Account of which information is being requested</param>
/// <returns>Account's Details</returns>
[HttpGet("{organizationId}/{accountId}")]
public async Task<IActionResult> GetAccountAsync(Guid organizationId, Guid accountId)
{
if (organizationId != Guid.Empty && accountId != Guid.Empty)
{
var httpClient = new HttpClient();
//Appended the parameters in base address to
//to fix veracode flaw issue
httpClient.BaseAddress = new Uri($"{_configurationService.AccountAPI}GetAccount/{organizationId}/{accountId}");
//passing empty string in GetStringAsync to make sure
//veracode doesn't treat it like modifying url
var content = await httpClient.GetStringAsync("");
return Ok(content.AsObject<MessageResponse<AccountDetailVM>>());
}
return BadRequest();
}

OAuth 2.0 token based Authentication Question on storing token values

I have implemented the OAuth token-based Authentication in our WebAPI Application and validating the username and password against the database. But we are not syncing the access tokens and refresh tokens to any type of database. Here is the code, however, I have one question where the token values are stored.
Below code for generating the Token
/// <summary>
/// Grant resource owner credentials overload method.
/// </summary>
/// <param name="context">Context parameter</param>
/// <returns>Returns when task is completed</returns>
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
// Initialization.
var usernameVal = context.UserName;
var passwordVal = context.Password;
var user = _securityLogic.AuthenticateApiUser(usernameVal, passwordVal);
// Verification.
if (!user)
{
// Settings.
context.SetError("invalid_grant", "The user name or password is incorrect.");
// Return info.
return;
}
// Initialization.
var claims = new List<Claim>
{
//var userInfo = user.FirstOrDefault();
// Setting
new Claim(ClaimTypes.Name, usernameVal)
};
// Setting Claim Identities for OAUTH 2 protocol.
ClaimsIdentity oAuthClaimIdentity = new ClaimsIdentity(claims, OAuthDefaults.AuthenticationType);
ClaimsIdentity cookiesClaimIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationType);
// Setting user authentication.
AuthenticationProperties properties = CreateProperties(usernameVal);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthClaimIdentity, properties);
// Grant access to authorize user.
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesClaimIdentity);
}
#endregion
#region Token endpoint override method.
/// <summary>
/// Token endpoint override method
/// </summary>
/// <param name="context">Context parameter</param>
/// <returns>Returns when task is completed</returns>
public override Task TokenEndpoint(OAuthTokenEndpointContext context)
{
foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
{
// Adding.
context.AdditionalResponseParameters.Add(property.Key, property.Value);
}
// Return info.
return Task.FromResult<object>(null);
}
#endregion
This is code is for generating the refresh Token
#region GrantRefreshToken
private static readonly ConcurrentDictionary<string, AuthenticationTicket> RefreshTokens =
new ConcurrentDictionary<string, AuthenticationTicket>();
/// <summary>
/// Grants Refresh Token
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public override Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
{
// Change authentication ticket for refresh token requests
var newIdentity = new ClaimsIdentity(context.Ticket.Identity);
// newIdentity.AddClaim(new Claim("newClaim", "newValue"));
var newTicket = new AuthenticationTicket(newIdentity, context.Ticket.Properties);
context.Validated(newTicket);
return Task.FromResult<object>(null);
}
public async Task CreateAsync(AuthenticationTokenCreateContext context)
{
var guid = Guid.NewGuid().ToString();
// Copy claims from the previous token
var refreshTokenProperties = new AuthenticationProperties(context.Ticket.Properties.Dictionary)
{
IssuedUtc = context.Ticket.Properties.IssuedUtc,
ExpiresUtc = DateTime.UtcNow.AddMinutes(30)
};
var refreshTokenTicket = await Task.Run(() =>
new AuthenticationTicket(context.Ticket.Identity, refreshTokenProperties));
RefreshTokens.TryAdd(guid, refreshTokenTicket);
// Consider storing only the hash of the handle
context.SetToken(guid);
}
#endregion
So, my question is .NET/Owin/IdentityServer3 writing them to some in-memory database? If so, can they be accessed for things like viewing and deleting? And what happens if the app server is restarted, are all the tokens wiped out? Or are they persistent?
And do you recommend storing in the Database and retrieving it from the database? Any help is appreciated, by the way, this code is working perfectly fine.
From the documentation:
If not specifically configured we will always provide an in-memory
version stores for authorization codes, consent, reference and refresh
tokens.
Please note that they talk about reference tokens and refresh tokens. JWT access tokens and identity tokens are not stored.
In order to use a refresh token in IdentityServer3 (and also IdentityServer4) it has to match a stored token.
The major benefit of that is that you can control the token. You can revoke it (removes it from the store), and define how to use it: OneTime or ReUse.
I'm not familiar with IdentityServer3, but you can take a look at github and search the code where the RevocationEndpoint is implemented, the place where the refresh token is removed from the store. That may give a clue on how to access and use the store.
With in-memory stores, the tokens are lost on restart of the IdentityServer. So persisting them in a persistent store, like a database, is a good thing for production servers. For IdentityServer4 you can implement an operational store.
Please note that JWT tokens remain valid regardless a restart of the server, unless the private key is also not persisted. In that case IdentityServer can't validate the token and has no choice but to consider the JWT tokens as invalid.
So for production environments you should persist keys and data, and using a database is fine. As you can see in IdentityServer4 there is support for this.
Speaking of IdentityServer4, since (free) support of IdentityServer3 has ended I would recommend to switch to IdentityServer4, if you are in the position to do so. Since both versions implement oidc/auth2 you should be able to keep using the clients with the upgraded IdentityServer. On stackoverflow there are questions that can help you with that. And take a look at the IdentityServer4 documentation, it's very informative.

Accessing session outside of Service creates duplicate

In my request filter I'm setting some properties in a custom session which I later access from the service. This works as expected.
Request Filter:
public sealed class CustomAttribute:RequestFilterAttribute
{
public override void Execute(IRequest req, IResponse res, object requestDto)
{
var session = req.SessionAs<CustomSession>();
if (!session.FromToken)
{
throw new AuthenticationException("Please authenticate using bearer token or with flag 'UseTokenCookie' set to true.");
}
... do some work ...
session.X = tup.Item2;
session.Y = tup.Item1;
req.SaveSession(session);
}
}
In my service:
var session = this.SessionAs<CustomSession>();
var myX = session.X;
var myY = session.Y;
... do some work ...
var someObj = new MyOtherClass();
someObj.DoSomeWork();
Later in the same request, I tried to access these same properties and was returned a different session.
public class MyOtherClass
{
...stuff...
public void DoSomeWork()
{
...
var req = HostContext.AppHost.TryGetCurrentRequest();
var session = req.SessionAs<CustomSession>(); //-> this returns a new session?
var myX = session.X; //-> so this is null
var myY = session.Y; //-> and this is null
}
}
My question is why? It's the same request. What am I doing wrong?
For context - I'm using JWT (as of 4.5.6) and 'MyOtherClass' is actually a singleton error handling class which decides when a failure is significant enough to fail a transaction or trigger an email notification.
You're not accessing the same IRequest instance when you use HostContext.TryGetCurrentRequest(), it creates a new instance for the ASP.NET Request which needs to re-fetch the session from the cache.
You'll either need to pass the same base.Request instance in your Service (recommended) which will let you access the same locally-cached session instance or you can save the session after you make changes using IRequest.SaveSession() that way when the session is re-fetched it will load the modified session. If you're using the default MemoryCacheClient you'll incur no I/O costs.

ServiceStack user session not found when using sessionId in client Headers or Cookies

I am using ServiceStack v4 with custom Authentication. This is setup and working correctly. I can call the /auth service and get a returned AuthorizationResponse with unique SessionId.
I also have swagger-ui plugin setup. Using it, I can authenticate via /auth and then call one of my other services which require authentication without issue.
Now, from a secondary MVC application using the c# JsonServiceClient I can again successfully make a call to /auth and then secured services using the same client object. However, if I dispose of that client (after saving the unique sessionId to a cookie), then later create a new client, and either add the sessionId as a Cookie or via headers using x-ss-pid as documented, calling a services returns 401. If I call a non-secure service, but then try to access the unique user session, it returns a new session.
If I look at the request headers in that service, the cookie or Header is clearly set with the sessionId. The sessionId also exists in the sessionCache. The problem seems to be that the code which tries to get the session from the request isn't finding it.
To be more specific, it appears that ServiceExtensions.GetSessionId is looking at the HostContext and not the calling Request. I'm not sure why. Perhaps I misunderstand something along the way here.
If I directly try and fetch my expected session with the following code it's found without issue.
var req = base.Request;
var sessionId = req.GetHeader("X-" + SessionFeature.PermanentSessionId);
var sessionKey = SessionFeature.GetSessionKey(sessionId);
var session = (sessionKey != null ? Cache.Get<IAuthSession>(sessionKey) : null)?? SessionFeature.CreateNewSession(req, sessionId);
So, am I missing something obvious here? Or maybe not so obvious in creating my secondary client?
Sample code of client calls
Here is my authorization code. It's contained in a Controller class. This is just the relevant parts.
using (var client = new JsonServiceClient(WebHelper.BuildApiUrl(Request)))
{
try
{
loginResult = client.Post(new Authenticate()
{
UserName = model.Email,
Password = model.Password,
RememberMe = model.RememberMe
});
Response.SetCookie(new HttpCookie(SessionFeature.PermanentSessionId, loginResult.SessionId));
return true;
}
}
Here is my secondary client setup and service call, contained in it's own controller class in another area of the MVC application
using (var client = new JsonServiceClient(WebHelper.BuildApiUrl(Request)))
{
var cCookie = HttpContext.Request.Cookies.Get(SessionFeature.PermanentSessionId);
if (cCookie != null)
{
client.Headers.Add("X-" + SessionFeature.PermanentSessionId, cCookie.Value);
client.Headers.Add("X-" + SessionFeature.SessionOptionsKey, "perm");
}
response = client.Get(new SubscriptionStatusRequest());
}
Additional Update
During the Authenticate process the following function is called from HttpRequestExtensions with the name = SessionFeature.PermanentSessionId
public static class HttpRequestExtensions
{
/// <summary>
/// Gets string value from Items[name] then Cookies[name] if exists.
/// Useful when *first* setting the users response cookie in the request filter.
/// To access the value for this initial request you need to set it in Items[].
/// </summary>
/// <returns>string value or null if it doesn't exist</returns>
public static string GetItemOrCookie(this IRequest httpReq, string name)
{
object value;
if (httpReq.Items.TryGetValue(name, out value)) return value.ToString();
Cookie cookie;
if (httpReq.Cookies.TryGetValue(name, out cookie)) return cookie.Value;
return null;
}
Now what occurs is the httpReq.Items contains a SessionFeature.PermanentSessionId value, but I have no clue why and where this gets set. I don't even understand at this point what the Items container is on the IRequest. The code thus never gets to the functionality to check my cookies or headers
The Session wiki describes the different cookies used by ServiceStack Session.
If the client wants to use a Permanent SessionId (i.e. ss-pid), it also needs to send a ss-opt=perm Cookie to indicate it wants to use the permanent Session. This Cookie is automatically set when authenticating with the RememberMe=true option during Authentication.
There was an issue in the Session RequestFilter that was used to ensure Session Id's were attached to the current request weren't using the public IRequest.GetPermanentSessionId() API's which also looks for SessionIds in the HTTP Headers. This has been resolved with this commit which now lets you make Session requests using HTTP Headers, e.g:
//First Authenticate to setup an Authenticated Session with the Server
var client = new JsonServiceClient(BaseUrl);
var authResponse = client.Send(new Authenticate
{
provider = CredentialsAuthProvider.Name,
UserName = "user",
Password = "p#55word",
RememberMe = true,
});
//Use new Client instance without Session Cookies populated
var clientWithHeaders = new JsonServiceClient(BaseUrl);
clientWithHeaders.Headers["X-ss-pid"] = authResponse.SessionId;
clientWithHeaders.Headers["X-ss-opt"] = "perm";
var response = clientWithHeaders.Send(new AuthOnly()); //success
This fix is available from v4.0.37+ that's now available on MyGet.
However, if I dispose of that client (after saving the unique sessionId to a cookie)
If the client is disposed where is the cookie you are saving the sessionId located? This answer might provide some additional information.
then later create a new client, and either add the sessionId as a Cookie or via headers using x-ss-pid as documented, calling a services returns 401
If you store/save a valid sessionId as a string you should be able to supply it within a CookieContainer of a new client (given the sessionId is still authenticated). I know you said you tried adding the sessionId as a Cookie but I don't a see sample within your question using the CookieContainer so it should look something like...
using (var client = new JsonServiceClient(WebHelper.BuildApiUrl(Request)))
{
var cCookieId = savedCookieId; //a string that I believe you saved from a successfully authenticated client that is now disposed
if (cCookieId != null)
{
var cookie = new Cookie(SessionFeature.PermanentSessionId, cCookieId);
//cookie.Domian = "somedomain.com" //you will probably need to supply this as well
client.CookieContainer.Add(cookie)
}
response = client.Get(new SubscriptionStatusRequest());
}

How to maintain session information across authentication

I using ServiceStack authentication with a custom session object. I've got everything set up with different authentication providers and everything is working fine.
Now a want to store some information in the session before the user is authenticated (Think shopping cart). But we loose that information when the user logs in later. Looking at the code in the documentation this makes sense:
Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] {
new BasicAuthProvider(), //Sign-in with Basic Auth
new CredentialsAuthProvider(), //HTML Form post of UserName/Password credentials
}));
The authentication removes the existing session whenever a user logs in. This makes sense when the old login is a valid user, you want to make sure it's fully logged out. However when the current session isn't authenticated there doesn't seem to be much reason to do so.
I've been looking at a custom session factory, but that doesn't help me because as () => new AuthUserSession() shows, there isn't any context to use when creating the new session. Without a way to get the old session there I've got no way to copy any information.
I can work around it by overriding AuthProvider.Authenticate() and grab the required information before calling base. But that means doing so in every authentication provider we use and the ones we might use in the future. That doesn't really feel like the correct solution.
Is there a cleaner way to carry information across the authentication? Preferably something which works regardless of the AuthProvider used.
Whilst the Typed Sessions are re-created after authenticating, the Permanent and Temporary Session Ids themselves remain the same which lets you use ServiceStack's dynamic SessionBag to store information about a user which you can set in your Services with:
public class UnAuthInfo
{
public string CustomInfo { get; set; }
}
public class MyServices : Service
{
public object Any(Request request)
{
var unAuthInfo = SessionBag.Get<UnAuthInfo>(typeof(UnAuthInfo).Name)
?? new UnAuthInfo();
unAuthInfo.CustomInfo = request.CustomInfo;
SessionBag.Set(typeof(UnAuthInfo).Name, unAuthInfo);
}
}
You can then access the dynamic Session Bag in your Custom AuthUserSession Session Events with:
public class CustomUserSession : AuthUserSession
{
[DataMember]
public string CustomInfo { get; set; }
public override void OnAuthenticated(IServiceBase service, IAuthSession session,
IAuthTokens tokens, Dictionary<string, string> authInfo)
{
var sessionBag = new SessionFactory(service.GetCacheClient())
.GetOrCreateSession();
var unAuthInfo = sessionBag.Get<UnAuthInfo>(typeof(UnAuthInfo).Name);
if (unAuthInfo != null)
this.CustomInfo = unAuthInfo.CustomInfo;
}
}
New Session API's in v4.0.32+
Accessing the Session bag will be a little nicer in next v4.0.32+ of ServiceStack with the new GetSessionBag() and convenience ISession Get/Set extension methods which will let you rewrite the above like:
public object Any(Request request)
{
var unAuthInfo = SessionBag.Get<UnAuthInfo>() ?? new UnAuthInfo();
unAuthInfo.CustomInfo = request.CustomInfo;
SessionBag.Set(unAuthInfo);
}
//...
public override void OnAuthenticated(IServiceBase service, IAuthSession session,
IAuthTokens tokens, Dictionary<string, string> authInfo)
{
var unAuthInfo = service.GetSessionBag().Get<UnAuthInfo>();
if (unAuthInfo != null)
this.CustomInfo = unAuthInfo.CustomInfo;
}

Resources