The Instructure Community will enter a read-only state on November 22, 2025 as we prepare to migrate to our new Community platform in early December.
Read our blog post for more info about this change.
Found this content helpful? Log in or sign up to leave a like!
I am struggling to properly verify an oauth signature. What I have done so far...
I'm using C#/.net-core and libraries for this seem to be non-existent. I've attempted to use https://github.com/davemateer/oauth_1_utility_netcore/blob/master/OAuth1Utilities.cs to verify the signature but not having any luck.
If anyone has examples of doing this in .net I'd greatly appreciate seeing them. I have seen implementations in Ruby but these use a Ruby library for verifying the signature.
There is a post here: https://community.canvaslms.com/t5/Developers-Group/Verifying-OAuth-Signature/td-p/102280 but I don't see any solutions, only that the author was able to find one.
Also noting that the example tool https://github.com/IMSGlobal/LTI-Sample-Tool-Provider-PHP cannot successfully verify signatures from the launch request.
You can see the process and check a signature using a tool like the one at https://lti.tools/oauth.
Here is some basic code I have used in the past (but note that this assumes there are no query parameters):
String sig = launchparams["oauth_signature"];
launchparams.Remove("oauth_signature");
string basestring = string.Empty;
basestring += "POST&";
basestring += EscapeUriDataStringRfc3986(endpoint) + "&";
foreach (KeyValuePair<string, string> entry in launchparams)
{
basestring += Uri.EscapeDataString(entry.Key + "=" + EscapeUriDataStringRfc3986(entry.Value) + "&");
}
basestring = basestring.Substring(0, basestring.Length - 3); // strip last encoded &
string signingkey = EscapeUriDataStringRfc3986(oauth_consumer_secret) + "&";
HMACSHA1 hasher = new HMACSHA1(new ASCIIEncoding().GetBytes(signingkey));
string sighash = Convert.ToBase64String(hasher.ComputeHash(new ASCIIEncoding().GetBytes(basestring)));
bool ok = sig.Equals(sighash);
Further checks should be made for the timestamp, nonce and required parameters.
Thank you for the response @svickers.. I have tried the tool you linked - I took an exact request that I received from Canvas, including copying all of the received form parameters (except for oauth signature) and it was not providing the same value that Canvas did as the signature. A quick test of the code you provided isn't providing the same thing as Canvas but I'm going to spend some more time looking into it and comparing to what I was trying to use before. I'm _assuming_ that your escape method is similar to this...
/// <summary>
/// The set of characters that are unreserved in RFC 2396 but are NOT unreserved in RFC 3986.
/// </summary>
private static readonly string[] UriRfc3986CharsToEscape = new[] { "!", "*", "'", "(", ")" };
/// <summary>
/// Escapes a string according to the URI data string rules given in RFC 3986.
/// </summary>
/// <param name="value">The value to escape.</param>
/// <returns>The escaped value.</returns>
/// <remarks>
/// The <see cref="Uri.EscapeDataString"/> method is <i>supposed</i> to take on
/// RFC 3986 behavior if certain elements are present in a .config file. Even if this
/// actually worked (which in my experiments it <i>doesn't</i>), we can't rely on every
/// host actually having this configuration element present.
/// </remarks>
internal static string EscapeUriDataStringRfc3986(string value)
{
// Start with RFC 2396 escaping by calling the .NET method to do the work.
// This MAY sometimes exhibit RFC 3986 behavior (according to the documentation).
// If it does, the escaping we do that follows it will be a no-op since the
// characters we search for to replace can't possibly exist in the string.
StringBuilder escaped = new StringBuilder(Uri.EscapeDataString(value));
// Upgrade the escaping to RFC 3986, if necessary.
for (int i = 0; i < UriRfc3986CharsToEscape.Length; i++)
{
escaped.Replace(UriRfc3986CharsToEscape[i], Uri.HexEscape(UriRfc3986CharsToEscape[i][0]));
}
// Return the fully-RFC3986-escaped string.
return escaped.ToString();
}
I'm wondering if I'm missing something that Canvas uses (or, more likely, excludes) in the signing process - and on my end I'm using them. As I understood it, Canvas is supposed to use all of the form values it is going to send (excluding oauth signature) to create the signature. There are no query parameters.
Thanks again for the response - I'm more than happy to provide any information that might help someone guide me here, it's been quite frustrating to try and get help from Canvas :).
If it helps, these are *all* of the params I'm using after removing the oauth_signature and sorting them.
Did your code generate the same signature as lti.tools/oauth? Did your URL contain any query parameters?
This is the EscapeUriDataStringRfc3986 method that my sample code used:
private static readonly string[] UriRfc3986CharsToEscape = new[] { "!", "*", "'", "(", ")" };
internal static string EscapeUriDataStringRfc3986(string value)
{
StringBuilder escaped = new StringBuilder(Uri.EscapeDataString(value));
for (int i = 0; i < UriRfc3986CharsToEscape.Length; i++)
{
escaped.Replace(UriRfc3986CharsToEscape[i], Uri.HexEscape(UriRfc3986CharsToEscape[i][0]));
}
return escaped.ToString();
}
Community helpTo interact with Panda Bot, our automated chatbot, you need to sign up or log in:
Sign inTo interact with Panda Bot, our automated chatbot, you need to sign up or log in:
Sign in