1

I have managed to setup my .Net core 2 API to require the user to login when he/she wants to consume it. The code for this is in Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        // This lambda determines whether user consent for non-essential cookies is needed for a given request.
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });

    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));
    services.AddDefaultIdentity<IdentityUser>()
        .AddEntityFrameworkStores<ApplicationDbContext>();

    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

    services.Configure<IdentityOptions>(options =>
    {
        // Password settings
        options.Password.RequireDigit = true;
        options.Password.RequiredLength = 8;
        options.Password.RequireNonAlphanumeric = false;
        options.Password.RequireUppercase = true;
        options.Password.RequireLowercase = false;
        options.Password.RequiredUniqueChars = 6;

        // Lockout settings
        options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(60);
        options.Lockout.MaxFailedAccessAttempts = 10;
        options.Lockout.AllowedForNewUsers = true;

        // User settings
        options.User.RequireUniqueEmail = true;
    });

    services.ConfigureApplicationCookie(options =>
    {
        options.Cookie.HttpOnly = true;
        options.ExpireTimeSpan = TimeSpan.FromMinutes(60);
        options.LoginPath = "/Account/Login";
        options.AccessDeniedPath = "/Account/AccessDenied";
        options.SlidingExpiration = true;
    });

    services.AddApplicationServices(Configuration);
    services.AddRouting();
    services.Configure<RouteOptions>(options => options.LowercaseUrls = true);
    services.AddSwagger(Configuration);
}

Now I'm hosting this on Azure and it seems to work. However, I need to ask how I would implement this if I want my Arduino to consume this API? Should I just create a "Arduino user" account and let the arduino post the credentials of this account and save the cookie? Is this a secure approach?

gre_gor
  • 6,669
  • 9
  • 47
  • 52
polly
  • 11
  • 3
  • 1
    Set up a service account for the Arduino to use. Any non-interactive programmatic access to the API should log in using this account. – Kevin Aug 27 '18 at 11:47
  • Saving a cookie won't be secure, and the cookie would expire also I would think. For a secure solution, use JWT e.g. https://learn.microsoft.com/en-us/azure/app-service/app-service-web-tutorial-auth-aad#enable-authentication-and-authorization-for-back-end-app – user326608 Aug 27 '18 at 12:08
  • I've looked into JWT and that guide but it mainly describes how to connect two applications already on Azure. I have managed to set up loggin in via Active Directory to access the API. The question is more how I actually setup things like Tenant, Domain, ClientID and stuff on the Arduino. – polly Aug 27 '18 at 18:17

1 Answers1

0

Instead of JWT or Cookies, use a pre-shared secret and some basic crypto. Use these to set custom headers on your http requests to your Azure API from the Arduino.

Make sure to store your pre-shared secret in your database and/or environment variables on your build machine via Azure Key Vault - don't check them into source code.

Use a decorator like this to secure your API controllers:

[UserKeyAuthorize]
public class MyController : Controller
{
    // ...
}

Here's the implementation of the attribute+filter:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Interface | AttributeTargets.Delegate)]
public class UserKeyAuthorizeAttribute : TypeFilterAttribute
{
    public UserKeyAuthorizeAttribute() : base(typeof(UserKeyFilter))
    {
    }

    public class UserKeyFilter : IAsyncActionFilter
    {
        public static string CalculateHash(string stringToHash)
        {
            using (var sha = SHA256.Create())
            {
                var computedHash = sha.ComputeHash(Encoding.Unicode.GetBytes(stringToHash));
                return Convert.ToBase64String(computedHash);
            }
        }


        private const string Hash = "signature";

        public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
        {
            // check key
            if (!IsUserKeyValid(context.HttpContext.Request))
            {
                context.Result = new UnauthorizedResult();
            }
            else
            {
                await next();
            }
        }

        public static bool IsUserKeyValid(HttpRequest request)
        {
            var map = new Dictionary<string, string>();
            foreach (var header in request.Headers)
            {
                if (header.Key.StartsWith("x-"))
                {
                    map.Add(header.Key, header.Value);
                }
            }

            var headerSig = "";

            foreach (var key in map.OrderBy(x=> x.Key))
            {
                headerSig = headerSig + "x-" + map[key.Key] + "-";
            }
            var nonce = request.Headers["nonce"].FirstOrDefault();   
            headerSig = headerSig + nonce + "-" + Environment.GetEnvironmentVariable("ApiKey");
            var hash = CalculateHash(headerSig);
            var signature = request.Headers[Hash].FirstOrDefault<string>();                                

            return hash == signature;
        }
    }       
}

On your Arduino, you can then set headers on your api requests, and hash them with the pre-shared key and send the hash as a further header so the server can verify you. https://github.com/simonratner/Arduino-SHA-256 and https://techtutorialsx.com/2016/07/21/esp8266-post-requests/ should help you with the client code, here's what works for me in C#:

    public static async Task<HttpResponseMessage> SignedGetRequest(this HttpClient client, string url, Dictionary<string, string> data)
    {   
        var request = new HttpRequestMessage()
        {
            RequestUri = new Uri(url),
            Method = HttpMethod.Get
        };

        var keys = data.Keys.OrderBy(x => x);
        var headerSig = "";
        foreach (var key in keys)
        {
            headerSig = headerSig + "x-" + data[key] + "-";
            request.Headers.Add("x-" + key, data[key]);
        }
        // TODO: Time based nonce hash
        var nonce = String.Format("{0:r}", DateTime.Now);
        headerSig = headerSig + nonce + "-" + Environment.GetEnvironmentVariable("ApiKey");
        var hash = Security.CalculateHash(headerSig);
        request.Headers.Add("signature", hash);
        request.Headers.Add("nonce", nonce);

        request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        return  await client.SendAsync(request);
    }

You can set any user or app level info in headers like e.g. "x-user-id" to identify individual clients to the server.

For setting the key on Azure so that Environment.GetEnvironmentVariable("ApiKey") resolves, see https://stackoverflow.com/a/34622196

user326608
  • 2,210
  • 1
  • 26
  • 33
  • Huh, I might try this, thanks! As the App and Arduino is mostly for personal use, this should probably be fine for me! I just tried implementing this but I'm not sure about the implementation. Were does the UserKeyAuthorizeAttribute read my shared key? Could you please elaborate your example? – polly Aug 28 '18 at 08:37
  • Added some info, note the Azure UI has changed by the looks of it. Do make sure to roll your keys regularly, and use something long and random - I use uuid's – user326608 Aug 29 '18 at 02:06
  • Alright, well I'm still a bit lost on how this works. Let says this shared secret is "XYZ". Should I then take XYZ, hash it and then send it with the header key of x-? – polly Aug 29 '18 at 10:58
  • Alright, so I managed to get it working by providing two headers: "-x: secret" and "signature: secretHashed". Is this a safe approach? Should it not be enough to just send the hash? – polly Aug 29 '18 at 11:13
  • sending the secret is a definite no-no. the secret should be the e.g. `ApiKey` that both systems have been inited with separately. you can also generate your own key pair and verify it that way. its just about having private information that the server can use to verify the client. adding an x-client-id or other header values is for app-level behaviour, and also makes the hash harder to reverse engineer by padding out the size of the hashed value. – user326608 Sep 03 '18 at 04:54
  • I'm not familiar with the Arduino, but normally you can pass e.g. -DAPI_SECRET=xxx to the compiler and then have the preprocessor stripe that into the compiled output somehow – user326608 Sep 03 '18 at 05:02