private static void ReadApiKeys(JToken apiKeyToken)
        {
            IEnumerable <string> recurseApiKeys(JToken token)
            {
                switch (token)
                {
                case JObject apiKey:
                    var keyString = apiKey["Key"].Value <string>();
                    if (keyString == null || Regex.IsMatch(keyString, @"[\(\)]") || !Regex.IsMatch(keyString, RegEx.ApiKey))
                    {
                        throw new Exception("An API key contained invalid characters. Must be a non-empty string, not containing " +
                                            "whitespace or parentheses, and only containing ASCII characters 33 through 126");
                    }
                    var key = keyString.SHA256();

                    IEnumerable <AccessRight> recurseAllowAccess(JToken allowAccessToken)
                    {
                        switch (allowAccessToken)
                        {
                        case JObject allowAccess:

                            IEnumerable <IResource> recurseResources(JToken resourceToken)
                            {
                                switch (resourceToken)
                                {
                                case JValue value when value.Value is string resourceString:
                                    var iresources     = Resource.SafeFindMany(resourceString);
                                    var includingInner = iresources.Union(iresources
                                                                          .Cast <IResourceInternal>()
                                                                          .Where(r => r.InnerResources != null)
                                                                          .SelectMany(r => r.InnerResources));
                                    foreach (var resource in includingInner)
                                    {
                                        yield return(resource);
                                    }
                                    yield break;

                                case JArray resources:
                                    foreach (var resource in resources.SelectMany(recurseResources))
                                    {
                                        yield return(resource);
                                    }
                                    yield break;

                                default: throw new Exception("Invalid API key XML syntax in config file");
                                }
                            }

                            yield return(new AccessRight
                                         (
                                             resources: recurseResources(allowAccess["Resource"])
                                             .OrderBy(r => r.Name)
                                             .ToList(),
                                             allowedMethods: allowAccess["Methods"]
                                             .Value <string>()
                                             .ToUpper()
                                             .ToMethodsArray()
                                         ));

                            yield break;

                        case JArray allowAccesses:
                            foreach (var allowAccess in allowAccesses.SelectMany(recurseAllowAccess))
                            {
                                yield return(allowAccess);
                            }
                            yield break;

                        default: throw new Exception("Invalid API key XML syntax in config file");
                        }
                    }

                    var accessRights = AccessRights.ToAccessRights(recurseAllowAccess(token["AllowAccess"]), key);
                    foreach (var resource in Resources.Where(r => r.GETAvailableToAll))
                    {
                        if (accessRights.TryGetValue(resource, out var methods))
                        {
                            accessRights[resource] = methods
                                                     .Union(new[] { GET, REPORT, HEAD })
                                                     .OrderBy(i => i, MethodComparer.Instance)
                                                     .ToArray();
                        }
                        else
                        {
                            accessRights[resource] = new[] { GET, REPORT, HEAD }
                        };
                    }
                    if (Authenticator.ApiKeys.TryGetValue(key, out var existing))
                    {
                        existing.Clear();
                        accessRights.ForEach(pair => existing[pair.Key] = pair.Value);
                    }
                    else
                    {
                        Authenticator.ApiKeys[key] = accessRights;
                    }
                    yield return(key);

                    yield break;

                case JArray apiKeys:
                    foreach (var _key in apiKeys.SelectMany(recurseApiKeys))
                    {
                        yield return(_key);
                    }
                    yield break;

                default: throw new Exception("Invalid API key XML syntax in config file");
                }
            }

            var currentKeys = recurseApiKeys(apiKeyToken).ToList();

            Authenticator.ApiKeys.Keys.Except(currentKeys).ToList().ForEach(key =>
            {
                if (Authenticator.ApiKeys.TryGetValue(key, out var accessRights))
                {
                    WebSocketController.RevokeAllWithKey(key);
                    accessRights.Clear();
                }
                Authenticator.ApiKeys.Remove(key);
            });
        }