Как безопасно хранить токен доступа пользователя Discord (OAuth2)? - PullRequest
0 голосов
/ 20 января 2019

Я изо всех сил пытаюсь найти способ безопасного сохранения токена доступа, который мое веб-приложение получило из DiscordAPI после того, как пользователь авторизовал приложение.

Я создаю веб-интерфейс дляDiscord Bot.Здесь важно, что не каждый может его использовать.Только сервер-модераторы и тому подобное на конкретном сервере Discord должны иметь доступ к большинству частей сайта.Для этого я использую материал OAuth2 из Discord для получения токена доступа, с помощью которого я могу получить информацию о пользователе, например, его уникальный идентификатор.Идентификатор затем используется для проверки того, какие роли они выполняют на сервере Discord.

Теперь часть получения Access-Token уже записана в сценарии и, похоже, работает нормально.Я могу использовать токен для запроса данных из Discord API и т. Д.

Моя главная проблема - это безопасность.Я прочитал несколько разных постов, но у каждого, похоже, есть разные взгляды на это.

Одно «решение», о котором я часто читаю (даже на сайте Auth2), - это сохранение токена в cookie.

Здесь я не уверен, что это вообще безопасно.Я не могу просто сохранить токен только на сервере, потому что пользователь должен оставаться в журнале в течение срока действия токенов.Но хранение его в виде cookie-файла открывает его для атак (с которыми я недостаточно знаком для защиты).

У меня есть эта строка при получении токена:

res.cookie('authToken', response.access_token);

Я также настраиваю строку следующим образом, так как это указано для удаления всех способов считывания файлов cookie с помощью сценариев (этого достаточно?):

res.cookie('authToken', response.access_token, { httpOnly: true });

И при доступе к другим частям веб-интерфейса я проверяю, существует ли cookie, и пытаюсь спросить Discord о пользовательской информации с ним.Если информация о пользователе верна правильно, я предполагаю, что пользователь правильно аутентифицирован:

router.get('/', catchAsync(async (req, res, next) => {  
    if(req.cookies.authToken === undefined) {

       // Render index view
       res.render('index', { 
           authenticated: false
       });
   }
   else {
        // Grab the token
        const localToken = req.cookies.authToken;
        // Use the token to query user information
        const response = await discordapi.GetDiscordUserDetails(localToken);
        if(response.id) {
            // Open the Index page and pass the username over
            res.render('index', {
                authenticated: true,
                username: response.username,
                userid: response.id,
                avatarid: response.avatar
            });
        } else {
            res.render('index', { 
                authenticated: false
            });
        }
   }
}));

(«Идентифицированный» логический параметр, который я передаю, изменяет только видимость кнопки входа в документ html (экспресс-руль). Вы можетебольше ничего с этим не делаю.)

Теперь, когда мои ограниченные знания об этом, я в значительной степени предполагаю, что это худший способ сделать это, и я хочу улучшить (это).

Другим решением будет хранение токена доступа в базе данных на самом веб-сервере.Так что, по сути, никогда даже не показываю это пользователю.Однако тогда мне нужен способ сопоставления этого токена с пользователем, поэтому им все еще нужен какой-то файл cookie с информацией, которую я могу использовать для получения токена.Я предполагаю, что было бы оптимальным, если бы токен был зашифрован в базе данных, а у пользователя был ключ для его расшифровки.Однако тогда я не уверен, что это снова безопасно, потому что, если вы прочитаете куки, вы все равно сможете получить доступ к токену или, по крайней мере, действовать так, как будто вы именно этот пользователь.

куки на 100% безопаснее всего?Я очень новичок в этом, и хотя это, скорее всего, небольшой веб-интерфейс для одного простого сервера Discord, я все же хочу сделать это правильно.

Я также прочитал, что обработка токена как своего рода парольи завернуть его в другой слой «сессий» - это способ сделать это, но это звучит слишком сложно, и я бы даже не знал, с чего начать.

Я надеюсь, что кто-то может пролить свет на это,поскольку безопасность - это то, чего я действительно боюсь.

Спасибо за ваше время!

1 Ответ

0 голосов
/ 20 января 2019
  • BORING STORY:

Я хотел бы начать с предпосылки, что я видел, как приложения онлайн-банкинга отправляли токены аутентификации в виде простого текста, добавляемого в виде строки запроса в POST.

С учетом вышесказанного, большинство соображений безопасности, которые должен сделать разработчик, должны быть направлены на защиту серверной структуры, а не на предотвращение взлома пользователей на стороне клиента.(Очевидно, вы сделаете все возможное, чтобы сделать эту последнюю гипотезу менее правдоподобной, но есть моральный и технологический предел. Я имею в виду, если я подключусь к незащищенной сети, и кто-то перехватит мое сообщение и удастся его расшифровать,Я думаю, что это будет больше моя вина, чем другие.)

В любом случае, я перестану быть философским, но последнее, уже известное наблюдение, никогда не будет 100% безопасного решения для чего-либо.

  • НАСТОЯЩИЙ ОТВЕТ:

Использование файлов cookie httpOnly - это самый простой и безопасный способ передачи и хранения токенов аутентификации, но если этого недостаточно, существуют некоторые другие уровни безопасности, которыеможет быть реализовано.Это только некоторые идеи, их может быть гораздо больше!

  1. Сокращение времени жизни токенов и закрытие серверной части сеанса после некоторого времени бездействия.Вам нужно будет вести учет активных сеансов, каждый из которых имеет время начала и активный токен и т. Д.

  2. Проверка IP.Если сеанс начался с IP-адреса из США, а через пять минут IP-адрес, кажется, пришел из Филиппин, возможно, вам нужно предпринять какие-то действия.

  3. Использовать внешние службы аутентификации, такие как AWSCognito.Но он не будет делать ничего, что вы не можете сделать самостоятельно.

  4. Реализация многофакторной аутентификации.

  5. Аналогично проверке IP,Вы можете вычислить хеш, используя строку агента пользователя в качестве начального числа, и сохранить его.Проверьте это, если у вас есть сомнения относительно личности клиента.

  6. Говоря о хешах, вы можете сохранить токен и отправить хеш пользователю.Единственное улучшение заключается в том, что он не позволяет кому-либо напрямую вызывать API-интерфейс Discord (если у API нет какого-либо фильтра).Например, пароли всегда хранятся в виде хэшей.

  7. Любая комбинация предыдущих.

Список может продолжаться бесконечно и через некоторое времяТочка, на мой взгляд, вы будете только терять время и ресурсы без необходимости.Просто запомните банки, отправляющие токены в URL, и вы почувствуете себя намного лучше.

  • EXTRA:

Я недавно написал эту функцию JavaScript для генерации идентификаторов сеансов.Не стесняйтесь изменять его по своему усмотрению, если считаете, что это может быть полезно.

Используйте размер для определения длины вывода и введите следующее:

  1. 'alpha'
  2. 'num'
  3. 'alphanum'
  4. 'special'
  5. 'unique'
function randomID(size, type) {

    // SEEDS
    const special = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz~`!@#$%^&*()_-+={[}]|:;<,>.?/0123456789";
    const alphaNum = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    const alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    const num = "0123456789";

    let x, y, z;
    let currentIndex;
    let randomIndex;
    let temporaryValue;

    let i;

    if (type === 'special') {
        x = special;
    } else if (type === 'alphaNum' || type === 'unique') {
        x = alphaNum;
    } else if (type === 'alpha') {
        x = alpha;
    } else {
        x = num;
    }

    x = x.split();
    currentIndex = x.length;

    // WHILE THERE ARE ELEMENTS TO SHUFFLE
    while (currentIndex !== 0) {
        // PICK A REMAINING ELEMENT
        randomIndex = Math.floor(Math.random() * currentIndex);
        currentIndex--;
        // AND SWAP IT WITH THE CURRENT ELEMENT
        temporaryValue = x[currentIndex];
        x[currentIndex] = x[randomIndex];
        x[randomIndex] = temporaryValue;
    }
    x = x.toString();

    y = '';
    if (type === 'unique') {
        z = new Date();
        y += x.charAt(x.length - 1 - z.getDate());
        y += x.charAt(x.length - 1 - z.getHours());
        y += z.getFullYear().toString();
        y += x.charAt(x.length - 1 - z.getMonth());
        y += x.charAt(x.length - 1 - z.getMinutes());
        y += x.charAt(x.length - 1 - z.getSeconds());
        y = y.split('').reverse().join('');
        y += '-';
    }

    for (i = 0; i < size; i++) {
        y += x.charAt(Math.floor(Math.random() * x.length));
    }

    return y;

}
...