web-dev-qa-db-fra.com

Valider le jeton d'identification Google

J'utilise ASP.NET Core pour servir une API à un client Android. Android se connecte en tant que compte Google et transmet un JWT, le jeton d'identification) , à l'API comme jeton de porteur. J'ai l'application qui fonctionne, elle passe les vérifications d'authentification, mais je ne pense pas que cela valide la signature du jeton.

Selon les documents de Google, je peux appeler cette URL pour le faire: https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=XYZ12 , mais je ne trouve pas les crochets appropriés sur côté serveur pour le faire. Toujours selon les documents de Google, je peux en quelque sorte utiliser les API d'accès client pour le faire sans appeler le serveur à chaque fois.

Mon code de configuration:

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,
});

Comment puis-je obtenir le middleware JWTBearer pour valider la signature? Je suis sur le point d'abandonner l'utilisation du middleware MS et de lancer le mien.

16
Darthg8r

Il existe plusieurs façons différents dans lesquels vous pouvez valider l'intégrité du jeton ID côté serveur:

  1. "Manuellement" - téléchargez constamment les clés publiques de Google, vérifiez la signature, puis chaque champ, y compris celui de iss; le principal avantage (quoique petit à mon avis) que je vois ici est que vous pouvez minimiser le nombre de demandes envoyées à Google.
  2. "Automatiquement" - effectuez un GET sur le point de terminaison de Google pour vérifier ce jeton https://www.googleapis.com/oauth2/v3/tokeninfo?id_token={0}
  3. Utiliser une bibliothèque cliente Google API - comme la officielle .

Voici à quoi pourrait ressembler le second:

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
    };
}

Classe 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's 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's 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's 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's user records. Note that this claim is never guaranteed to be present.
/// </summary>
public string name { get; set; }

/// <summary>
/// The URL of the user's 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's 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; }
}
30

Selon ce github issue , vous pouvez maintenant utiliser GoogleJsonWebSignature.ValidateAsync méthode pour valider un JWT signé par Google. Passez simplement la chaîne idToken à la méthode.

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

S'il ne s'agit pas d'un jeton valide, il renverra null.

Notez que pour utiliser cette méthode, vous devez installer Google.Apis.Auth nuget de première main.

18
edmundpie

Google indique dans la documentation pour openId connect

À des fins de débogage, vous pouvez utiliser le point de terminaison tokeninfo de Google. Supposons que la valeur de votre jeton ID soit XYZ123.

Vous ne devez pas utiliser ce point de terminaison pour valider votre JWT.

La validation d'un jeton ID nécessite plusieurs étapes:

  1. Vérifiez que le jeton d'identification est correctement signé par l'émetteur. Les jetons émis par Google sont signés à l'aide de l'un des certificats trouvés à l'URI spécifié dans le champ jwks_uri du document de découverte .
  2. Vérifiez que la valeur de iss dans le jeton d'ID est égale à https://accounts.google.com ou accounts.google.com.
  3. Vérifiez que la valeur de aud dans le jeton ID est égale à l'ID client de votre application.
  4. Vérifiez que le délai d'expiration (exp) du jeton d'ID n'est pas passé.
  5. Si vous avez transmis un paramètre hd dans la demande, vérifiez que le jeton ID a une revendication hd qui correspond à votre domaine hébergé G Suite.

Il existe un exemple de projet officiel sur la façon de les valider ici . Malheureusement, nous ne l'avons pas encore ajouté à la bibliothèque du client Google .Net. Il a été enregistré en tant que problème

3
DaImTo

Donc, ce que j'ai trouvé, c'est que les spécifications OpenIDConnect ont une URL /.well-known/ qui contient les informations dont vous avez besoin pour valider un jeton. Cela comprend l'accès aux clés publiques pour la signature. Le middleware JWT forme cette URL bien connue de l'autorité, récupère les informations et procède à leur validation par lui-même.

La réponse courte à la question est que la validation est déjà en cours dans le middleware, il n'y a plus rien à faire.

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

       Public ProviderUserDetails ValidateGoogleToken(string providerToken)        
       {

        var httpClient = new HttpClient();

        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);

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