Tamturk.AspNetCore.RequestSigning 2.1.1

There is a newer version of this package available.
See the version list below for details.
dotnet add package Tamturk.AspNetCore.RequestSigning --version 2.1.1                
NuGet\Install-Package Tamturk.AspNetCore.RequestSigning -Version 2.1.1                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Tamturk.AspNetCore.RequestSigning" Version="2.1.1" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Tamturk.AspNetCore.RequestSigning --version 2.1.1                
#r "nuget: Tamturk.AspNetCore.RequestSigning, 2.1.1"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Tamturk.AspNetCore.RequestSigning as a Cake Addin
#addin nuget:?package=Tamturk.AspNetCore.RequestSigning&version=2.1.1

// Install Tamturk.AspNetCore.RequestSigning as a Cake Tool
#tool nuget:?package=Tamturk.AspNetCore.RequestSigning&version=2.1.1                

RequestSigning

The missing .NET Core & ASP.NET Core request signing library. Generate links that is valid with specific parameters for specific duration and optinally that are valid for only once!

The generated URL contains at least a signature as query parameter, but it may optinally contain expiration time and allowed HTTP method(s).

For example,

/reset?email=buraktamturk@gmail.com&code=8295&method=GET,POST&exp=1545888427&sig=c23752ab1f08385285d85ce8224604e477b17f7cf3356ec0e4bf4db5b9a7415a

This URL is generated by SignRequest function and the user must submit the same path and parameters (parameter order does not matter, as ordering made internally before signature checking!). Good thing is request body can have different content, so you can send signed e-mail reset links that is valid for 10 minutes without storing anything on your database.

This library provides a way to generate URLs with hidden query parameters. This means you can send a random code using SMS to verify the users phone number. And you can return this URL in the AJAX call.

/reset?email=buraktamturk@gmail.com&method=GET,POST&exp=1545888427&sig=c23752ab1f08385285d85ce8224604e477b17f7cf3356ec0e4bf4db5b9a7415a

Since the signature generated with "code=8295", the returned url will not work by default. The client has to send the correct code parameter in order to call this function, which verifies the user got the valid code 😉

Tamturk.RequestSigning

This assembly provides basic functionality that is used to sign and validate requests. Also, it comes with InMemory hash-table for revoking URLs even before their expiration time.

Tamturk.AspNetCore.RequestSigning

This assembly provides extension methods for ASP.NET Core classes to the Tamturk.RequestSigning assembly.

Usage

Sample web project is included in the samples folder.

  1. Call AddRequestSigning and optionally AddInMemoryRevokedHashTable on your startup class.
public void ConfigureServices(IServiceCollection services) {
  services

      .AddRequestSigning(Configuration["signingKey"]) // use HMACSHA256 with this key that is taken from appconfig
      .AddInMemoryRevokedHashTable() // use in memory table to store revoked tokens (optional!)

      // 
      .AddMvc()
      .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
  1. Inject RequestSigning class on your controller.
public class MyController : Controller {
  private RequestSigning requestSigning;

  public MyController(Tamturk.RequestSigning requestSigning) {
    this.requestSigning = requestSigning;
  }

  // methods
}
  1. Generate URLs with expiration and send them over email, and generate URLs with hidden content that user needs to supply. (this example refers forget password action)
[HttpGet("forgotpw")]
public dynamic get(string email) {
    if (email == null) {
        throw new Exception("Please enter an e-mail");
    }
    
    int randomNumber = new Random().Next(1000, 9999);

    // sign request for 10 minutes hash it with hidden query string values and make
    // client estimate correct code in 10 minutes

    var expire_date = DateTimeOffset.UtcNow.AddMinutes(10);
    
    var linkWithoutCode = requestSigning.SignRequest(
        "GET,POST",
        "/reset",
        new Dictionary<string, string>() {
            { "email", email }
        },
        expire_date,
        new Dictionary<string, string>() {
            { "code", randomNumber.ToString() }
        }
    );

    // example return /reset?email=buraktamturk@gmail.com&method=GET,POST&exp=1545888427&sig=c23752ab1f08385285d85ce8224604e477b17f7cf3356ec0e4bf4db5b9a7415a
    
    // sign request for 10 minutes, include the code here also, and sent to
    // user e-mail so he can go there and reset
    var linkWithCode = requestSigning.SignRequest(
        "GET,POST",
        "/reset",
        new Dictionary<string, string>() {
            { "email", email },
            { "code", randomNumber.ToString() }
        },
        expire_date
    );


    // example return /reset?email=buraktamturk@gmail.com&code=8295&method=GET,POST&exp=1545888427&sig=c23752ab1f08385285d85ce8224604e477b17f7cf3356ec0e4bf4db5b9a7415a
    
    // we do not have to store code, or this request, anywhere else!
    
    return new {
        message = "Assume 'has_sent' was sent to e-mail address. And has_returned returned to the client. These links valid only for 10 minutes!",
        has_sent = new {
            code = randomNumber,
            link = linkWithCode
        },
        has_returned = linkWithoutCode
    };
}
  1. Right now, you have a signed URL that you can send to the email, along with an optional code. If all of the query strings that is included in linkWithCode variable is not passed, validation fails.
[HttpGet("reset")]
public bool reset(string email) {
    // is any of the query parameters (incl. code) invalid?
    if (!Request.TryValidateRequest()) {
        return false;
    }
    
    // or you can use this instead, which will throw exception
    // Request.ValidateRequest();

    // this link used before?
    if (Request.IsRevoked()) {
        return false;
    }
    
    // or use Request.ThrowIfRevoked();
    
    return true; // congrats, code is correct and not used, so redirect user to ask new password page.
}
  1. Reset user password and Revoke token, Revoking invalidates the url and makes it expire before the expiration date, by using in-memory hash table.
// we can also return void because we can return 2xx on success 4xx or 5xx on failure.
[HttpPost("reset")]
public async Task<string> reset(
    // we can get email as plain text from query string, because of the hash, the user
    // can not alter the email address that we set it from forgetpw endpoint
    [FromQuery] string email,
    
    [FromBody] ResetPasswordModel model) {
    Request.ValidateRequest(); // if code is incorrect, or link is timeout, this will throw exception
    
    // you may check your password strength here
    // if you check it later, 
    // use will not be able to submit with a new password

    await Request.RevokeAsync(); 
    // this will throw exception if the same link used for password reset twice
    // you may also use Sync version of this (Revoke), 
    // some backends may take advantage of async functions but all of them can be called synchronously also
    
    /*
      * If you omit calling Revoke function (which is optional anyway), the same link
      * may be used to reset password as many times
      * till the expiration date that is set in
      * forgotpw endpoint (10 minutes)
      */
    
    // update user in db and save hashed & salted password to db.
    
    return "YOUR PASSWORD OF " + email + " IS SUCCESSFULLY RESET.";
}
  1. So you can basically send secure URLs without storing tokens anywhere. Revoking is complately optional and it makes user not be able to reset password twice during the lifespan on the URL. Expiration time is put on the URL before the signature is generated. So you can make it expire in 10 minutes and forget about revoking it.

Contributing

Pull requests and issues are welcome!

License

© 2018 Burak Tamturk

Released under the MIT LICENSE

Product Compatible and additional computed target framework versions.
.NET net5.0 is compatible.  net5.0-windows was computed.  net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 is compatible.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
2.2.2 85 11/18/2024
2.2.1 329 11/17/2023
2.1.2 1,018 11/8/2022
2.1.1 423 9/18/2022
2.1.0 360 11/10/2021
2.0.0 575 10/20/2019
1.0.3 658 12/28/2018
1.0.2 619 12/27/2018
1.0.1 636 12/27/2018
1.0.0 659 12/27/2018