How do I set a response cookie on HttpReponseMessage? - asp.net-web-api

I would like to create a demo login service in web api and need to set a cookie on the response. How do I do that? Or are there any better way to do authorization?

Add a reference to System.Net.Http.Formatting.dll and use the AddCookies extension method defined in the HttpResponseHeadersExtensions class.
Here is a blog post describing this approach, and the MSDN topic.
If that assembly isn't an option for you, here's my older answer from before this was an option:
Older answer follows
I prefer an approach that stays within the realm of HttpResponseMessage without bleeding into the HttpContext which isn't as unit testable and does not always apply depending on the host:
/// <summary>
/// Adds a Set-Cookie HTTP header for the specified cookie.
/// WARNING: support for cookie properties is currently VERY LIMITED.
/// </summary>
internal static void SetCookie(this HttpResponseHeaders headers, Cookie cookie) {
Requires.NotNull(headers, "headers");
Requires.NotNull(cookie, "cookie");
var cookieBuilder = new StringBuilder(HttpUtility.UrlEncode(cookie.Name) + "=" + HttpUtility.UrlEncode(cookie.Value));
if (cookie.HttpOnly) {
cookieBuilder.Append("; HttpOnly");
}
if (cookie.Secure) {
cookieBuilder.Append("; Secure");
}
headers.Add("Set-Cookie", cookieBuilder.ToString());
}
Then you can include a cookie in the response like this:
HttpResponseMessage response;
response.Headers.SetCookie(new Cookie("name", "value"));

You can add the cookie to the HttpContext.Current.Response.Cookies collection.
var cookie = new HttpCookie("MyCookie", DateTime.Now.ToLongTimeString());
HttpContext.Current.Response.Cookies.Add(cookie);

Related

How to modify Response Cookie in GraphQL using Hot Chocolate in .Net 5

I am building a GraphQL API using Hot Chocolate(.net 5) and need to add authentication using the JWT token.
In REST API, I have used http only cookie to add the refresh token.
var cookieOption = new CookieOptions
{
HttpOnly = true,
Expires = DateTime.UtcNow.AddDays(7)
};
Response.Cookies.Append("refreshToken", <refreshToken.Token>, cookieOption);
In my login mutation, I do not have access to HttpResponse as in REST API.
Even Hot Chocolate's documentation does not have an example or instruction on how to access the Http Response.
I highly appreciate any help on this.
Thanks
You can use the IHttpContextAccessor to access the HttpContext and in turn modify the cookies.
public string Foo(string id, [Service] IHttpContextAccessor httpContextAccessor)
{
if (httpContextAccessor.HttpContext is not null)
{
httpContextAccessor.HttpContext.Response.Cookies...
}
}
https://chillicream.com/docs/hotchocolate/fetching-data/resolvers/#ihttpcontextaccessor

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());
}

Sitecore and caching control

I am working on this Sitecore project and am using WebApi to perform some service calls. My methods are decorated with CacheOutput information like this:
[HttpGet]
[CacheOutput(ClientTimeSpan = 3600, ServerTimeSpan = 3600)]
I am testing these calls using DHC app on Google Chrome. I am sure that the ClientTimespan is set correctly but the response headers that i am getting back are not what i am expecting. I would expect that Cache-Control would have a max-age of 1hour as set by the ClientTimespan attribute but instead it is set to private.
I have been debugging everything possible and t turns out that Sitecore may be intercepting the response and setting this header value to private. I have also added the service url to the sitecore ignored url prefixes configuration but no help .
Does anyone have an idea how I can make Sitecore NOT change my Cache-Control headers?
This is default MVC behaviour and not directly related to Sitecore / Web API.
You can create a custom attribute that sets the Cache-Control header:
public class CacheControl : System.Web.Http.Filters.ActionFilterAttribute
{
public int MaxAge { get; set; }
public CacheControl()
{
MaxAge = 3600;
}
public override void OnActionExecuted(HttpActionExecutedContext context)
{
context.Response.Headers.CacheControl = new CacheControlHeaderValue()
{
Public = true,
MaxAge = TimeSpan.FromSeconds(MaxAge)
};
base.OnActionExecuted(context);
}
}
Which enables you to add the [CacheControl(MaxAge = n)] attribute to your methods.
Code taken from: Setting HTTP cache control headers in WebAPI (answer #2)
Or you can apply it globally throughout the application, as explained here: http://juristr.com/blog/2012/10/output-caching-in-aspnet-mvc/

ServiceStack: Accessing the HttpRequest in a selfhosted application

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.");
}

WebApi authorization filter with token in json payload

I've been looking into Authorization with AspNetWebApi and information is a little sparse on the subject.
I've got the following options:
Pass API token on query string
Pass API token as header
Pass API token using Basic Auth
Pass API token onto the request payload in json.
Which is generally the recommended method?
I'm also wondering for point 4), how would I go about inspecting the json payload in the OnAuthorization method on the AuthorizationFilterAttribute to check whether the API token is correct?
If you want a truly secure option for authorization, something like OAuth is the way to go. This blog post provides a pretty thorough sample using the now obsolete WCF Web API but a lot of the code is salvageable. Or at least, go with using HTTP basic authentication as shown in this blog post. As Aliostad notes, make sure you're using HTTPS if you go the Basic authentication route so the token stays secure.
If you decide you want to roll your own (which almost always will be much less secure than either option above) then below is a code sample of what you'll need for the AuthorizationHanlder if you go HTTP header route. Be aware there's a good chance the way the UserPrinicipal is handled in Web API classes may change so this code is only good for the first preview release. You would need to wire-in the AuthorizationHandler like this:
GlobalConfiguration.Configuration.MessageHandlers.Add(new AuthenticationHandler());
Code for header token:
public class AuthenticationHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
var requestAuthTokenList = GetRequestAuthTokens(request);
if (ValidAuthorization(requestAuthTokenList))
{
//TODO: implement a Prinicipal generator that works for you
var principalHelper = GlobalConfiguration.Configuration
.ServiceResolver
.GetService(typeof(IPrincipalHelper)) as IPrincipalHelper;
request.Properties[HttpPropertyKeys.UserPrincipalKey] =
principalHelper.GetPrinicipal(request);
return base.SendAsync(request, cancellationToken);
}
/*
** This will make the whole API protected by the API token.
** To only protect parts of the API then mark controllers/methods
** with the Authorize attribute and always return this:
**
** return base.SendAsync(request, cancellationToken);
*/
return Task<HttpResponseMessage>.Factory.StartNew(
() => new HttpResponseMessage(HttpStatusCode.Unauthorized)
{
Content = new StringContent("Authorization failed")
});
}
private static bool ValidAuthorization(IEnumerable<string> requestAuthTokens)
{
//TODO: get your API from config or however makes sense for you
var apiAuthorizationToken = "good token";
var authorized = requestAuthTokens.Contains(apiAuthorizationToken);
return authorized;
}
private static IEnumerable<string> GetRequestAuthTokens(HttpRequestMessage request)
{
IEnumerable<string> requestAuthTokens;
if (!request.Headers.TryGetValues("SomeHeaderApiKey", out requestAuthTokens))
{
//Initialize list to contain a single not found token:
requestAuthTokens = new[] {"No API token found"};
}
return requestAuthTokens;
}
}

Resources