it-roy-ru.com

Как хранить токен доступа? (Oauth 2, поток кода аутентификации)

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

Как сервер страниц должен хранить токен доступа после его получения? Я учился на примере Pluralsight, в котором есть эта часть кода:

    public static HttpClient GetClient()
    {
        HttpClient client = new HttpClient();
        var accessToken = RequestAccessTokenAuthorizationCode();
        client.SetBearerToken(accessToken);

        client.BaseAddress = new Uri(IdentityConstants.API);
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Accept.Add(
            new MediaTypeWithQualityHeaderValue("application/json"));

        return client;
    }

    private static string RequestAccessTokenAuthorizationCode()
    {
        // did we store the token before?
        var cookie = HttpContext.Current.Request.Cookies.Get("ClientMVCCookie.AuthCode");
        if (cookie != null && cookie["access_token"] != null && !string.IsNullOrEmpty(cookie["access_token"]))
        {
            return cookie["access_token"];
        }

        // no token found - request one

        // we'll pass through the URI we want to return to as state
        var state = HttpContext.Current.Request.Url.OriginalString;

        var authorizeRequest = new IdentityModel.Client.AuthorizeRequest(
            IdentityConstants.AuthEndoint);

        var url = authorizeRequest.CreateAuthorizeUrl(IdentityConstants.MVCClientSecret, "code", "management secret",
            IdentityConstants.MVCAuthCodeCallback, state);

        HttpContext.Current.Response.Redirect(url);

        return null;
    }
}

Это приведет к тому, что каждый запрос будет проверять наличие маркера доступа в cookie. Если нет, то поток будет инициирован. Обратный вызов выглядит так:

public class CallbackController : Controller
{
    // GET: STSCallback
    public async Task<ActionResult> Index()
    {
        // get the authorization code from the query string
        var authCode = Request.QueryString["code"];

        // with the auth code, we can request an access token.
        var client = new TokenClient(
            IdentityConstants.TokenEndoint,
            "mvc_client_auth_code",
             IdentityConstants.MVCClientSecretAuthCode);

        var tokenResponse = await client.RequestAuthorizationCodeAsync(
            authCode,
            IdentityConstants.MVCAuthCodeCallback);

        // we save the token in a cookie for use later on
        var cookie = Response.Cookies["ClientMVCCookie.AuthCode"];
        cookie.Expires = DateTime.Now.AddMinutes(1);
        cookie["access_token"] = tokenResponse.AccessToken;

        // get the state (uri to return to)
        var state = Request.QueryString["state"];

        // redirect to the URI saved in state
        return Redirect(state);
    }
}

Разве хранение токена доступа в cookie не противоречит всей цели потока кода авторизации? Файл cookie будет передан в браузер клиента, таким образом предоставляя его клиенту? Я что-то пропустил? Если это неправильный способ хранения токена, как его хранить?

7
BodzioSamolot

Клиент, в терминологии OAuth, является компонентом, который отправляет запросы на сервер ресурсов, в вашем случае клиент - это сервер веб-приложения (НЕ браузер).

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

То, как браузер аутентифицирует себя на сервере веб-приложений, не имеет ничего общего с OAuth 2.0. Например, это может быть обычный файл cookie сеанса, и сервер веб-приложений может связать каждый сеанс или каждого пользователя с токеном доступа.

Запрос токена, который обменивает код аутентификации для токена доступа, выполняется сервером веб-приложений, и сервер веб-приложений должен аутентифицироваться на сервере авторизации (например, с использованием общего client_secret).

Поток кода авторизации гарантирует, что клиент может быть аутентифицирован, что защищает от злонамеренных клиентов, выдающих себя за законных клиентов. Не все клиенты веб-приложений имеют серверный компонент, и в некоторых случаях запросы к серверу ресурсов выполняются непосредственно с помощью кода JavaScript в браузере. В таких ситуациях браузер является клиентом, а маркер доступа должен храниться браузером (в переменной JavaScript, в локальном хранилище или в файле cookie). В этом случае клиент не может быть аутентифицирован (но разумный уровень безопасности может быть достигнут с помощью TLS и перенаправления сервера только на зарегистрированные URL-адреса конечных точек).

Рекомендуемое чтение относительно безопасности OAuth 2.0: https://tools.ietf.org/html/rfc6819#section-4.3.3 (RFC 6819)

14
Florian Winter

Файл cookie никогда не открывается браузеру. Это часть ответа, возвращаемого сервером авторизации клиенту, который сам является сервером, а не браузером. CallbackController, который реализует конечную точку перенаправления, извлекает cookie из ответа.

Файл cookie никогда не передается в браузер. То, как браузер аутентифицирует себя на сервере приложений клиента, не показано в вашем примере кода и не является частью OAuth.

Сервер авторизации может хранить токен в теле запроса (например, в формате JSON), а не в файле cookie. Однако это не имеет значения, потому что клиент все равно может видеть и обрабатывать весь ответ.

Подробности см. В моем другом ответе: https://stackoverflow.com/a/44655679/2279059

Примечание: CallbackController использует state для сохранения окончательного URL-адреса для перенаправления браузера. Это нестандартно, но работает. Однако state на самом деле предназначен для защиты конечных точек перенаправления от атак CSRF. CallbackController не проверяет state, но слепо перенаправляет на любой заданный URL. Вероятно, эта деталь была опущена, потому что код был задуман как пример. Однако это показывает, что этот код, вероятно, не полностью готов к работе.

2
Florian Winter