.Net and Azure

Home App - User account control and better let's encrypt automation


I implemented the user account control system and users can now register automatically. I decided against storing password and instead opted for using oauth authentication. That way the account security is delegated to bigger players and the user not only doesn’t have to remember another password but gets two factor authentication for free (if he chooses to use it).

For now users can use their google/microsoft accounts to create accounts with the system (but Asp.Net Core makes it trivial to add any known oauth provider).

The user has the ability to link accounts (incase he registers one account with one oauth provider and another account with another oauth provider).

After linking accounts the user can use any one of his logins to access his data.

And I also implemented an automated account termination feature (with email confirmation) that users can use to terminate their account.

For now, the account is instantly (and completely) deleted when the termination email is confirmed but in the future I intent to prevent termination when the user is still admin/user of a home (esp. important if he is on a paying plan).

Policy will be that the user has to remove himself from all homes first before he can delete his account.

On an semi-unrelated note:

Last week I mentioned using the Let’s Encrypt site extension for the public website and its issues.

I’ve since found a better solution that runs as a background job in one web app but can renew Let’s Encrypt for any number of websites.

On top, it doesn’t require the storage account that the Let’s Encrypt site extension needs.

I’ve also managed to relay all traffic from my old homeapp implementation to homeapplegacy.marcstan.net (all old traffic hits only homeapp.marcstan.net/api/* endpoints).

So now I can use the homeapp.marcstan.net domain for the new implementation as well.

The implementation required a bit of manual lifting as just setting 301 redirect is not enough.

The middleware logic is thus:

if (isLegacyCall)
    var path = req.Path.ToString();
    var uri = $"https://homeapplegacy.marcstan.net{path}{req.QueryString}";
    if ("GET".Equals(req.Method, StringComparison.OrdinalIgnoreCase))
        context.Response.StatusCode = 301;
        context.Response.Headers.Add("Location", uri);
        // redirects would end up sending empty GET requests even when originally a body with content was sent
        var httpClient = new HttpClient();
        // skip system keys (that throw for some reason even as you call get on them)
        foreach (var h in req.Headers.Keys.Where(header => !header.StartsWith("content", StringComparison.OrdinalIgnoreCase) &&
                                                           header != "Host" &&
            httpClient.DefaultRequestHeaders.Add(h, req.Headers[h].ToString());
        switch (req.Method.ToUpperInvariant())
            case "POST":
                // relay post, we only do json so no need to verify
                var content = new StreamContent(req.Body);
                content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
                var resp = await httpClient.PostAsync(uri, content);
                context.Response.Body = await resp.Content.ReadAsStreamAsync();
                throw new NotSupportedException("old solution didn't use PUT or DELETE, path: " + req.Path);