How the Signed URL works?
Tags: csharp, security
Published: 2022-01-15 16:15:35

### What is a Signed URL?

A signed URL temporarily provides access to a resource. Signed URLs contain user/authentication information in their query string, allowing users without credentials to perform specific actions on a resource. You can add more information in the query string and these values cannot be changed on the client-side.

### Signed URL basic workflow:

1. Create the URL you want to sign. Mostly the URL will be in the below format.
> https://{your_domain}/{path}?{query_parameter}
2. Sign the query parameters with a secret key using the **HMACSHA256** hashing algorithm and you will get a hash code in this process.
3. Append the hash code in your URL like below,
> https://{your_domain}/{path}?{query_parameter}&signature={hash_code}
4. When you receive the request in the server with a signature, you need to do the same hashing with the same secret on the request URL query parameters without the signature in the query parameter.
5. Compare the output hash code with the hash code that you get in the request query parameter.
6. You will get a different hash code if the URL is altered on the client-side.

### How to sign a URL? (C#)
Here is the code snippet to sign the URL in C#. The sign URL method requires a message and a secret code to generate the hash code.

```c#
class Program
{
    static void Main(string[] args)
    {
        string secretCode = "9tGaq1zidmxJN8Pid6IMYhHFqAUwNbu";  // secret code
        var url = "https://example.com";

        // Variable declaration to form the signature URL 
        var query = "?page=4&access=rwd&"; // Add queries that you need.
        var nonce = Guid.NewGuid().ToString(); // random string - To prevent replay attacks.
        double timeStamp = DateTimeToUnixTimeStamp(DateTime.UtcNow); // current time as UNIX time stamp
        var expirationTime = "100"; // alive time of the token - This can be used to make the URL expire.
        var userId = 1; // User ID or user email


        string queryParameter = query + "nonce=" + nonce + "&user_id=" + userId + "&timestamp=" + timeStamp + "&expirationtime=" + expirationTime;

        string signature = SignURL(queryParameter.ToLower(), secretCode);
        string queryWithSignature = queryParameter.ToLower() + "&signature=" + signature;
        var signedUrl = url + queryWithSignature;
    }

    static string SignURL(string message, string secretcode)    
    {
        var encoding = new UTF8Encoding();
        var keyBytes = encoding.GetBytes(secretcode);
        var messageBytes = encoding.GetBytes(message);
        using (var hmacsha1 = new HMACSHA256(keyBytes))
        {
            var hashMessage = hmacsha1.ComputeHash(messageBytes);
            return Convert.ToBase64String(hashMessage);
        }
    }

    static double DateTimeToUnixTimeStamp(DateTime dateTime)
    {
        DateTime unixStart = new DateTime(1970, 1, 1, 0, 0, 0, 0, System.DateTimeKind.Utc);
        long unixTimeStampInTicks = (dateTime.ToUniversalTime() - unixStart).Ticks;
        return unixTimeStampInTicks / TimeSpan.TicksPerSecond;
    }
}
```

We should add some additional query parameters for security and for URL expiration.


- **nonce** - It's a random string to prevent the replay attack.
- **timeStamp** - It helps you to calculate the URL expiry. It's advisable to use a UNIX timestamp to avoid confusion in the date format.
- **expirationTime** - The lifespan of the URL.

### How to verify the Signed URL?

Here is the code snippet to verify the signed URL,

```c#
public string SignURL(string message, string secret)
{
    var encoding = new System.Text.UTF8Encoding();
    var keyBytes = encoding.GetBytes(secret);
    var messageBytes = encoding.GetBytes(message);
    using (var hmacsha1 = new HMACSHA256(keyBytes))
    {
        var hashMessage = hmacsha1.ComputeHash(messageBytes);
        return Convert.ToBase64String(hashMessage);
    }
}

public bool SignatureValidation(string query, string secret, string signature)
{
    return SignURL(HttpUtility.UrlDecode(query), secret).ToString().Equals(signature);
}
```

The above snippet has only the signature validation and it validates only whether the URL query parameter is altered or not.

To check the URL expiry, you could use the parameters (expirationTime, timeStamp) that we have added. 

```C#
var isValidSignatureKey = SignatureValidation(query, secretCode, signature);

var expireTime = Convert.ToInt32(filterContext.HttpContext.Request.Query["expiretime"]);
DateTime timeStampDateTime = DateTimeHelper.UnixTimeStampToDateTime(Convert.ToDouble(filterContext.HttpContext.Request.Query["timestamp"]));
bool urlExpiryTime = DateTime.UtcNow > timeStampDateTime ? DateTime.UtcNow - timeStampDateTime <= TimeSpan.FromSeconds(expireTime) : false;               
if (isValidSignatureKey && urlExpiryTime && !string.IsNullOrEmpty(nonce))
{
    return true;
}
```