Подтвердить токен идентификатора Google

Я использую ASP.NET Core для обслуживания API для клиента Android. Android подписывается как учетная запись Google и передает JWT, токен идентификатора, в API в качестве токена-носителя. У меня работает приложение, оно проходит проверку подлинности, но я не думаю, что он проверяет подпись маркера.

В документах Google я могу называть этот url для этого: https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=XYZ123, но я не могу найти соответствующие перехватчики на сервер для этого. Кроме того, согласно документам Google, я могу как-то использовать API клиентского доступа, чтобы делать это, не вызывая на сервер каждый раз.

Мой код конфигурации:

app.UseJwtBearerAuthentication( new JwtBearerOptions()
{

    Authority = "https://accounts.google.com",
    Audience = "hiddenfromyou.apps.googleusercontent.com",
    TokenValidationParameters = new TokenValidationParameters()
    {
        ValidateAudience = true,
        ValidIssuer = "accounts.google.com"
    },
    RequireHttpsMetadata = false,
    AutomaticAuthenticate = true,
    AutomaticChallenge = false,
});

Как мне получить промежуточное ПО JWTBearer для проверки подписи? Я близок к отказу от использования промежуточного программного обеспечения MS и откатыванию своих собственных.

Ответ 1

Есть несколько различных способов, в которых вы можете проверить целостность маркера ID на стороне сервера:

  • "Вручную" - постоянно загружать открытые ключи Google, проверять подпись, а затем каждое поле, включая iss one; основное преимущество (хотя и небольшое, на мой взгляд) я вижу здесь, что вы можете свести к минимуму количество запросов, отправленных в Google.
  • "Автоматически" - выполните GET на конечной точке Google, чтобы проверить этот токен https://www.googleapis.com/oauth2/v3/tokeninfo?id_token={0}
  • Использование клиентской библиотеки Google API - например, официальный .

Вот как мог выглядеть второй:

private const string GoogleApiTokenInfoUrl = "https://www.googleapis.com/oauth2/v3/tokeninfo?id_token={0}";

public ProviderUserDetails GetUserDetails(string providerToken)
{
    var httpClient = new MonitoredHttpClient();
    var requestUri = new Uri(string.Format(GoogleApiTokenInfoUrl, providerToken));

    HttpResponseMessage httpResponseMessage;
    try
    {
        httpResponseMessage = httpClient.GetAsync(requestUri).Result;
    }
    catch (Exception ex)
    {
        return null;
    }

    if (httpResponseMessage.StatusCode != HttpStatusCode.OK)
    {
        return null;
    }

    var response = httpResponseMessage.Content.ReadAsStringAsync().Result;
    var googleApiTokenInfo = JsonConvert.DeserializeObject<GoogleApiTokenInfo>(response);

    if (!SupportedClientsIds.Contains(googleApiTokenInfo.aud))
    {
        Log.WarnFormat("Google API Token Info aud field ({0}) not containing the required client id", googleApiTokenInfo.aud);
        return null;
    }

    return new ProviderUserDetails
    {
        Email = googleApiTokenInfo.email,
        FirstName = googleApiTokenInfo.given_name,
        LastName = googleApiTokenInfo.family_name,
        Locale = googleApiTokenInfo.locale,
        Name = googleApiTokenInfo.name,
        ProviderUserId = googleApiTokenInfo.sub
    };
}

Класс GoogleApiTokenInfo:

public class GoogleApiTokenInfo
{
/// <summary>
/// The Issuer Identifier for the Issuer of the response. Always https://accounts.google.com or accounts.google.com for Google ID tokens.
/// </summary>
public string iss { get; set; }

/// <summary>
/// Access token hash. Provides validation that the access token is tied to the identity token. If the ID token is issued with an access token in the server flow, this is always
/// included. This can be used as an alternate mechanism to protect against cross-site request forgery attacks, but if you follow Step 1 and Step 3 it is not necessary to verify the 
/// access token.
/// </summary>
public string at_hash { get; set; }

/// <summary>
/// Identifies the audience that this ID token is intended for. It must be one of the OAuth 2.0 client IDs of your application.
/// </summary>
public string aud { get; set; }

/// <summary>
/// An identifier for the user, unique among all Google accounts and never reused. A Google account can have multiple emails at different points in time, but the sub value is never
/// changed. Use sub within your application as the unique-identifier key for the user.
/// </summary>
public string sub { get; set; }

/// <summary>
/// True if the user e-mail address has been verified; otherwise false.
/// </summary>
public string email_verified { get; set; }

/// <summary>
/// The client_id of the authorized presenter. This claim is only needed when the party requesting the ID token is not the same as the audience of the ID token. This may be the
/// case at Google for hybrid apps where a web application and Android app have a different client_id but share the same project.
/// </summary>
public string azp { get; set; }

/// <summary>
/// The user email address. This may not be unique and is not suitable for use as a primary key. Provided only if your scope included the string "email".
/// </summary>
public string email { get; set; }

/// <summary>
/// The time the ID token was issued, represented in Unix time (integer seconds).
/// </summary>
public string iat { get; set; }

/// <summary>
/// The time the ID token expires, represented in Unix time (integer seconds).
/// </summary>
public string exp { get; set; }

/// <summary>
/// The user full name, in a displayable form. Might be provided when:
/// The request scope included the string "profile"
/// The ID token is returned from a token refresh
/// When name claims are present, you can use them to update your app user records. Note that this claim is never guaranteed to be present.
/// </summary>
public string name { get; set; }

/// <summary>
/// The URL of the user profile picture. Might be provided when:
/// The request scope included the string "profile"
/// The ID token is returned from a token refresh
/// When picture claims are present, you can use them to update your app user records. Note that this claim is never guaranteed to be present.
/// </summary>
public string picture { get; set; }

public string given_name { get; set; }

public string family_name { get; set; }

public string locale { get; set; }

public string alg { get; set; }

public string kid { get; set; }
}

Ответ 2

Согласно этой проблеме github, теперь вы можете использовать метод GoogleJsonWebSignature.ValidateAsync для проверки подписанного Google JWT. Просто idToken строку idToken методу.

var validPayload = await GoogleJsonWebSignature.ValidateAsync(idToken);
Assert.IsNotNull(validPayload);

Если это не допустимый токен, он вернет null.

Обратите внимание, что для использования этого метода вам нужно установить Google.Apis.Auth nuget из первых рук.

Ответ 3

Google заявляет в документации для openId connect

В целях отладки вы можете использовать конечную точку Gookles tokeninfo. Предположим, что значение вашего токена ID - XYZ123.

Вы не должны использовать эту конечную точку для проверки своего JWT.

Проверка маркера ID требует нескольких шагов:

  • Убедитесь, что маркер ID правильно подписан эмитентом. Токены, подписанные в Google, подписываются с использованием одного из сертификатов, найденных в URI, указанных в поле jwks_uri документа .
  • Убедитесь, что значение iss в токене ID равно https://accounts.google.com или accounts.google.com.
  • Убедитесь, что значение aud в токене идентификатора равно идентификатору клиента вашего приложения.
  • Убедитесь, что время истечения (exp) идентификационного маркера не прошло.
  • Если вы передали параметр hd в запросе, убедитесь, что маркер ID имеет hd-заявку, которая соответствует вашему хостинговому домену G Suite.

Существует официальный образец проекта о том, как проверить их здесь. К сожалению, мы еще не добавили это в библиотеку Google.Net Client. Он зарегистрирован как issue

Ответ 4

Итак, я обнаружил, что, поскольку в спецификациях OpenIDConnect есть /.well-known/url, который содержит информацию, необходимую для проверки токена. Это включает в себя доступ к открытым ключам для подписи. Связующее ПО JWT формирует этот хорошо известный URL-адрес от авторитета, извлекает информацию и продолжает проверять ее на своем собственном.

Короткий ответ на вопрос заключается в том, что валидация уже происходит в промежуточном программном обеспечении, там ничего не остается.