I am using Java servlet to return cookies with JWT information to client.
I return 2 cookies.
1 => Token_id, this cookie store the id_token
2 => refresh_token, this token store the refresh token to be send when the token is expired.
The problem is, these informations are too big, so the browser send this error:
431 (Request Header Fields Too Large)
To resolve it, I would like to store the refresh_token compressed, and when I will use this information I will decompress.
I read about the compress and tried to use a GZIPCompress String, like this:
public static String compress(final String str) throws IOException {
if ((str == null) || (str.length() == 0)) {
return null;
}
ByteArrayOutputStream obj = new ByteArrayOutputStream();
GZIPOutputStream gzip = new GZIPOutputStream(obj);
gzip.write(str.getBytes("UTF-8"));
gzip.flush();
gzip.close();
return obj.toString();
}
When I add the compress String to cookie, I have this exception:
Illegal character in cookie value
I can not store the refresh_token in my server.
How can I compress a big String to store in a cookie? I need to decrease the size of value cookie.
A cookie can only store strings, but you are storing binary data.
The result of the compression process is binary and is not representable using a string, even if you call obj.toString();. You would need to encode the result to base64 to get a string that could be stored in a cookie, but base64 increases the size about 30%, so I do not know if it makes sense in your case.
//storage
cookie = base64(gzip(str))
//retrieval
str = gunzip(base64decode(cookie))
The refresh token usually consists in a random string. May be you are including too much data?
Related
First up, I'm not using the Xero api. This is more an OAuth2 questions than Xero specifically I think.
Not quite sure if the issue is a general OAuth2 problem or a Xero implementation of OAuth2. I can successfully authenticate, get my tokens etc from Xero. I can even make successful Get requests to their endpoints for Invoices and contacts. My problem is trying to POST anything, i.e. create an invoice.
The server responds with, 400 Bad request. I've confirmed by actual post data is correct by putting the XML into their API tester and all is good there.
Shouldn't a post request be a standard httpwebequest, (POST) with the query string ?oauth_signature=[sig here], and the actual post data URL encoded and sent via stream? Is my implementation correct and should I be looking elsewhere for the problem? Is the data sent in the form supposed to be included in the signature?
{
byte[] reqData = encode.GetBytes(postData);
HttpWebRequest request = WebRequest.CreateHttp(url + querystring) as HttpWebRequest;
request.Method = "POST";
try {
using (Stream stream = request.GetRequestStream) {
stream.Write(reqData, 0, reqData.Length);
}
using (HttpWebResponse response = request.GetResponse) {
Stream dataStream = response.GetResponseStream();
StreamReader reader = new StreamReader(dataStream);
dynamic responseFromServer = reader.ReadToEnd();
return responseFromServer;
}
} catch (Exception ex) {
}
}
Xero uses OAuth1.0a, rather than OAuth2. The OAuth signature needs to be supplied as a header rather than a query string. I believe it should be exactly the same as the successful GET requests you're making.
https://oauth.net/core/1.0a/#rfc.section.5.4.1:
The OAuth Protocol Parameters are sent in the Authorization header the following way:
Parameter names and values are encoded per Parameter Encoding.
For each parameter, the name is immediately followed by an '=' character (ASCII code 61), a '"' character (ASCII code 34), the parameter value (MAY be empty), and another '"' character (ASCII code 34).
Parameters are separated by a comma character (ASCII code 44) and OPTIONAL linear whitespace per [RFC2617].
The OPTIONAL realm parameter is added and interpreted per [RFC2617], section 1.2.
Update for those finding this now, Xero API now has OAuth2 in public Beta
https://developer.xero.com/documentation/oauth2/overview
I have created a wesite in asp.net and use ms-sql database to save the records. Now want to convert it in node.js application. And want to use same sql database. In asp.net application I have encrypt the password for registered user. Below is code.
public static string CreateHash(string unHashed)
{
System.Security.Cryptography.MD5CryptoServiceProvider x = new System.Security.Cryptography.MD5CryptoServiceProvider();
byte[] data = System.Text.Encoding.ASCII.GetBytes(unHashed);
data = x.ComputeHash(data);
return System.Text.Encoding.ASCII.GetString(data);
}
public static bool MatchHash(string HashData, string HashUser)
{
HashUser = CreateHash(HashUser);
if (HashUser == HashData)
return true;
else
return false;
}
Now problem is that how I use same encryption in node.js. So when node application is ready old user can also make login. It only possible if node app also use same encryption that I have use in asp.net.
For node I have created all environment and use mssql module for database communication. Please help me fix that. Thanks!!
First of all MD5 shall no longer be used if you are serious about security.
Based on your comment and code, I fear there is a 'data loss' in the initial ASP.net code.
Let us have a look at CreateHash function again, I've added comments:
public static string CreateHash(string unHashed)
{
System.Security.Cryptography.MD5CryptoServiceProvider x = new System.Security.Cryptography.MD5CryptoServiceProvider();
// Convert unHashed string to bytes using ASCII coding
byte[] data = System.Text.Encoding.ASCII.GetBytes(unHashed);
// Compute MD5 hash from bytes
data = x.ComputeHash(data);
// Decode MD5 resulting bytes as ASCII
return System.Text.Encoding.ASCII.GetString(data);
}
The last line confuses me, it is decoding bytes received from MD5 function as if they were ASCII, but that is incorrect assumption. And the resulting encoded string as you gave in comment contains lots of "?'s".
Next node.js code will do similar except encode the string using hex rather than ascii:
var crypto = require('crypto')
function createHash(data) {
return crypto.createHash('md5').update(data, 'ascii').digest('hex')
}
To emulate "bytes to ascii" you could try .digest('binary') instead of hex. If it does not give what you expect, then you have to make a separate 'conversion' step from hex to ascii. (I am not experienced enough to give you elegant solution to the later one)
I use ASP.NET
I need to give user temporary link for downloading file from server.
It should be a temporary link (page), which is available for a short time (12 hours for example). How can I generate this link (or temporary web page with link)?
Here's a reasonably complete example.
First a function to create a short hex string using a secret salt plus an expiry time:
public static string MakeExpiryHash(DateTime expiry)
{
const string salt = "some random bytes";
byte[] bytes = Encoding.UTF8.GetBytes(salt + expiry.ToString("s"));
using (var sha = System.Security.Cryptography.SHA1.Create())
return string.Concat(sha.ComputeHash(bytes).Select(b => b.ToString("x2"))).Substring(8);
}
Then a snippet that generates a link with a one week expiry:
DateTime expires = DateTime.Now + TimeSpan.FromDays(7);
string hash = MakeExpiryHash(expires);
string link = string.Format("http://myhost/Download?exp={0}&k={1}", expires.ToString("s"), hash);
Finally the download page for sending a file if a valid link was given:
DateTime expires = DateTime.Parse(Request.Params["exp"]);
string hash = MakeExpiryHash(expires);
if (Request.Params["k"] == hash)
{
if (expires < DateTime.UtcNow)
{
// Link has expired
}
else
{
string filename = "<Path to file>";
FileInfo fi = new FileInfo(Server.MapPath(filename));
Response.ContentType = "application/octet-stream";
Response.AddHeader("Content-Disposition", "attachment;filename=" + filename);
Response.AddHeader("Content-Length", fi.Length.ToString());
Response.WriteFile(fi.FullName);
Response.Flush();
}
}
else
{
// Invalid link
}
Which you should certainly wrap in some exception handling to catch mangled requests.
http://example.com/download/document.pdf?token=<token>
The <token> part is key here. If you don't want to involve a database, encrypt link creation time, convert it to URL-safe Base64 representation and give user that URL. When it's requested, decrypt token and compare date stored in there with current date and time.
Alternatively, you can have a separate DownloadTokens table wich will map said tokens (which can be GUIDs) to expiration dates.
Append a timestamp to the URL, in the querystring:
page.aspx?time=2011-06-22T22:12
Check the timestamp against the current time.
To avoid the user changing the timestamp by himself, also compute some secret hash over the timestamp, and also append that to the querystring:
page.aspx?time=2011-06-22T22:12&timehash=4503285032
As hash you can do something like the sum of all fields in the DateTime modulo some prime number, or the SHA1 sum of the string representation of the time. Now the user will not be able to change the timestamp without knowing the correct hash. In your page.aspx, you check the given hash against the hash of the timestamp.
There's a million ways to do it.
The way I did once for a project was to generate a unique key and use a dynamic downloader script to stream the file. when the file request was made the key was generated and stored in db with a creation time and file requested. you build a link to the download script and passed in the key. from there it was easy enough to keep track of expiration.
llya
I'll assume you're not requiring any authentication and security isn't an issue - that is if anyone gets the URL they will also beable to download the file.
Personally I'd create a HttpHandler and then create some unique string that you can append to the URL.
Then within the ProcessRequest void test the encoded param to see if it's still viable (with in your specified time-frame) if so use BinaryWrite to render the File or if not you can render some HTML using Response.Write("Expired")
Something like :
public class TimeHandler : IHttpHandler, IRequiresSessionState
{
public void ProcessRequest ( HttpContext context )
{
if( this.my_check_has_expired( this.Context.Request.Params["my_token"] ) )
{
// Has Expired
context.Response.Write( "URL Has Expired" );
return;
}
// Render the File
Stream stream = new FileStream( File_Name , FileMode.Open );
/* read the bytes from the file */
byte[] aBytes = new byte[(int)oStream.Length];
stream.Read( aBytes, 0, (int)oStream.Length );
stream.Close( );
// Set Headers
context.Response.AddHeader( "Content-Length", aBytes.Length.ToString( ) );
// ContentType needs to be set also you can force Save As if you require
// Send the buffer
context.Response.BinaryWrite( aBytes );
}
}
You need to then setup the Handler in IIS, but that a bit different depending on the version you're using.
I have an Silverlight 4 RIA Services application with custom Forms Authentication. The custom authentication service works like a charm.
The problems is I want to serialize the user object in a cookie which is then sent with each subsequent request.
I create the cookie and add it to the response cookie collection but on the next request the only cookies in the cookie collection are ASPXAUT and ASPX_SESSIONId, of the custom cookie not a trace.
This is the cookie management class:
public class CookieManager:ISessionManager
{
public object this[string key]
{
get
{
var context = getCurrentContext();
var cookie = context.Request.Cookies[key];
if (cookie == null) return null;
return deserialize(cookie.Value);
}
set
{
var context = getCurrentContext();
string cookieValue = serialize(value);
HttpCookie cookie = new HttpCookie(key, cookieValue);
cookie.Expires = DateTime.Now.AddDays(10000);
cookie.HttpOnly = false;
context.Response.Cookies.Remove(key);
context.Response.Cookies.Add(cookie);
}
}
public void Abandon()
{
var context = getCurrentContext();
context.Response.Cookies.Clear();
}
public void Clear()
{
Abandon();
}
private HttpContext getCurrentContext()
{
return HttpContext.Current;
}
private string serialize(object value)
{
MemoryStream stream = new MemoryStream();
BinaryFormatter formatter = new BinaryFormatter();
formatter.Context = new StreamingContext(StreamingContextStates.Clone);
formatter.Serialize(stream, value);
StreamReader reader = new StreamReader(stream);
stream.Position = 0;
string result = reader.ReadToEnd();
reader.Close();
stream.Close();
return HttpUtility.UrlEncodeUnicode(result);
}
public object deserialize(string value)
{
value = HttpUtility.UrlDecode(value);
MemoryStream stream = new MemoryStream();
StreamWriter writer = new StreamWriter(stream);
writer.Write(value);
BinaryFormatter formatter = new BinaryFormatter();
return formatter.Deserialize(stream);
}
}
It reads and saves cookies.
Now my problem is this:
What I need to enable in silverlight or in the ASP.NET (WCF) application in order for extra cookies to be sent with each request along side the authentication cookie.
EDIT:
I've inspected the HTTP request/response stack and those extra cookies are sent from the server with the WCF RIA Services response but not returned by the next service call from the client.
If I understand your edit above correctly, you've already inspected the HTTP requests and found the desired cookie present in the HTTP Set-Cookie header of the response, but missing in the Cookie header of the next request. Is this correct? If not, please clarify.
If so, the problem sounds like one of three things:
the client is not successfully saving the cookie, due to many possible reasons including:
cookie not properly formatted (unlikley)
cookie is too long
there's a client- or server-side policy (e.g. P3P) preventing saving persistent cookies.
The client is saving the cookie OK, but is not sending it back, even without Silverlight. This could be caused by, for example, a security issue where the hostname of the first request is different from the second.
The client is saving the cookie and can send it back over regular HTML pages, but not via HTTP requests sent by Silverlight.
To see if #1 is the problem, look (using your browser's ability to view cookies) at the cookies saved by your browser for that site. Is the expected cookie saved? If it is, then you can eliminate #1 as the problem. If it's not saved, start looking
To see if #2 is the problem, try creating a server-side page with no silverlight on it-- just a simple HTML page. When you visit that page with your browser, is the cookie sent as expected? If yes, then #2 is not your problem.
If #1 and #2 are not the problem, that leaves #3. Silverlight's HTTP handling is complicated, not least because you have to choose between having HTTP client requests handled by the browser or by Silverlight. Read the Silverlight cookies documentation carefully and see if any of the info therein will help you figure out the problem. Consider trying to use the "Client HTTP" setting, or if you're already using this, consider switching back to the "browser HTTP" setting and see if your problem goes away. Note that the Client HTTP setting apparently has a problem with losing new cookies after an HTTP redirect. See this thread for more info. There's a workaround discussed in that thread: using CookieContainer.
BTW, could you edit your question to include all the HTTP headers of the request and the subsequent request? This may help diagnosis.
I don't know if I'm just being overly hopeful, but is there a way to hide the query string returned in the URL?
The scenario I am in is where I have page1.aspx redirecting a command to an outside server via a post, and it returns it to page2.aspx. The only problem I have with this, is that the querystring of the returned variables are still left in the URL.
I just want to hide the ugly string/information from the common user. So is there a way to edit and reload that in the pageload method or do I just have to save the variables on a middleman page and then hit page 2.
What is the origin of these querystring variables? Can you not submit all data as POST data, so that there is no querystring?
You could possibly also use
Context.RewritePath("/foo.aspx")
Here's a link to a ScottGu blog post about URL rewriting.
http://weblogs.asp.net/scottgu/archive/2007/02/26/tip-trick-url-rewriting-with-asp-net.aspx
Awhile back I made some http encoding encrypt/decrypt methods for this purpose. Sometimes in asp.net you need to use the query string, but you also need the end user to not know the value. What I do is base 64 encode, encrypt the value, hash the value based on my private key, and stick them together with a -. On the other side I check the left side hash to verify authenticity, and decrypt the right side. One really nice gotcha is that + (which is a valid base64 string value) is equal to space in html encoding, so I take that into account in the decrypt.
The way I use this is add the encrypted value to the query string, and then decrypt it on the other side
private const string KEY = "<random value goes here>";
public static string EncryptAndHash(this string value)
{
MACTripleDES des = new MACTripleDES();
MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
des.Key = md5.ComputeHash(Encoding.UTF8.GetBytes(KEY));
string encrypted = Convert.ToBase64String(des.ComputeHash(Encoding.UTF8.GetBytes(value))) + '-' + Convert.ToBase64String(Encoding.UTF8.GetBytes(value));
return HttpUtility.UrlEncode(encrypted);
}
/// <summary>
/// Returns null if string has been modified since encryption
/// </summary>
/// <param name="encoded"></param>
/// <returns></returns>
public static string DecryptWithHash(this string encoded)
{
MACTripleDES des = new MACTripleDES();
MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
des.Key = md5.ComputeHash(Encoding.UTF8.GetBytes(KEY));
string decoded = HttpUtility.UrlDecode(encoded);
// in the act of url encoding and decoding, plus (valid base64 value) gets replaced with space (invalid base64 value). this reverses that.
decoded = decoded.Replace(" ", "+");
string value = Encoding.UTF8.GetString(Convert.FromBase64String(decoded.Split('-')[1]));
string savedHash = Encoding.UTF8.GetString(Convert.FromBase64String(decoded.Split('-')[0]));
string calculatedHash = Encoding.UTF8.GetString(des.ComputeHash(Encoding.UTF8.GetBytes(value)));
if (savedHash != calculatedHash) return null;
return value;
}
I don't like this approach, but it will work.
Once you know you are where you need to be you can Response.Redirect to the same page and they will be gone.
It preserves Query String and Form Variables (optionally). It doesn’t show the real URL where it redirects the request in the users web browser. Server.Transfer happens without the browser knowing anything. The browser requests a page, but the server returns the content of another.
protected void btnServer_Click(object sender, EventArgs e)
{
Server.Transfer("~/About.aspx?UserId=2");
}