Google Sign-In with Backend Verification Flow Clarification

余生颓废 提交于 2019-12-20 12:27:29

问题


I've spent hours trawling through various tutorials and articles and am finally giving in to asking.

I want to enforce the use of Google Sign-In authentication for all users of my Angular 7 application. However, once Google authentication is complete, I want to first check that the user exists in my back-end DB (PostgreSQL). If they do, then I want to issue a JWT for two purposes:

  1. Ensure that future calls to my Spring Boot REST services are only fulfilled for authorized users.
  2. Protect my Angular routes using an AuthGuard that is aware of the token.

So far, I have been able to retrieve the id_token from the gapi auth2 auth response and forward it to my Spring Boot POST mapping, but I'm struggling to pin down exactly which OAuth 2.0/OpenId flows/grants I'm aiming for which is making life difficult when sourcing appropriate Spring Boot documentation/tutorials.

Is anyone able to clarify which flow/grant I should be aiming for and whether or not my current direction is valid?


回答1:


I suggest you to implement a “stateless” authentication system, coupled with Google Sign-in ID provider.

“Using a JWT as a bearer for authorization, you can statelessly verify if the user is authenticated by simply checking if the expiration in the payload hasn’t expired and if the signature is valid.” — Jonatan Nilsson

Some good resources on the subject :

  • https://www.jbspeakr.cc/purpose-jwt-stateless-authentication/
  • https://auth0.com/blog/stateless-auth-for-stateful-minds/

The general idea is :

  • frontend retrieves a Google Sign-in authentication JWT token.
  • frontend sends JWT token with each HTTP request (with authorization header)
  • backend retrieves JWT for each request, validates its signature, and gets payload attributes (email, id…)
  • then, backend checks ‘email’ or ‘id’ in users database to allow or not request.

Backend is stateless, and simple to implement. This design tends to become a good practice in cloud platform, and for instance, Google Cloud is using this a lot in its new products : Cloud Run

Some details on each step:

1) frontend retrieves a Google Sign-in authentication JWT token.

To do that, you can use Google Sign-in library directly or use ng-gapi to manage Google Sign-In in Angular.

2) Each http call to backend has an authorization header with JWT token (id_token) retrieved from Google Sign-in.

You can use an HttpInterceptor for that.

headers: {
  Authorization: Bearer ___JWT ID TOKEN___
}

See Top 10 ways to use Interceptors in Angular from Michael Karén.

Pay attention, to not store the Google JWT Id_token in variable. It could be refreshed if expired (automatically done by Google Sign-in), so you should take a fresh version each time you use it inside HttpInterceptor.

3) Implement a filter inside Spring Boot

For each request, this security filter will retrieve JWT ID TOKEN and validate it with Google library.

NetHttpTransport transport = new NetHttpTransport();
JsonFactory jsonFactory = new GsonFactory();

GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory)
  .setAudience(Collections.singletonList(clientId))
  .build();

GoogleIdToken idToken = GoogleIdToken.parse(verifier.getJsonFactory(), token);
boolean tokenIsValid = (idToken != null) && verifier.verify(idToken);

if (tokenIsValid) {
  GoogleIdToken.Payload payload = idToken.getPayload();

  // Get profile information from payload
  payload.getEmail())...
...

But be careful, to not create a GoogleIdTokenVerifier for each request, use factory pattern. This class will retrieve certificates and cache them automatically, to avoid useless request to google servers.

Some resources : Google Sign-in, Authenticate with a backend server




回答2:


I am also working on Google Auth/OAuth 2.0/Credential Management API and can empathise with the disappointment from examples and rabbit holes on the web.

Not sure your question is answered by the following but it is what I am doing after completing the Credential Management API and Google front-end vetting: -

public String ValidateToken(string idToken, string accessToken)
{
    bool isValid = false;
    string errorMessage = "Illegal Access Token: ";

    if (accessToken != null)
    {
        var tokeninfoRequest = new Oauth2Service().Tokeninfo();
        tokeninfoRequest.AccessToken = accessToken;
        Tokeninfo tokeninfo = null;
        try
        {
            tokeninfo = tokeninfoRequest.Execute();
            if (tokeninfo.IssuedTo != CLIENT_ID)
            {
                errorMessage += "Imposter";
            }
        }
        catch (Exception e)
        {
            errorMessage += e.Message;
        }
        isValid = true;
    }

    if (!isValid) abortRequest(401, errorMessage);

    errorMessage = "Invalid ID Token";
    string gplus_id = "";
    JwtSecurityToken JWToken = new JwtSecurityToken(idToken);

    Byte[][] certBytes = getCertBytes(GOOGLE_CERTS);
    Dictionary<String, X509Certificate2> certificates = new Dictionary<String, X509Certificate2>();
    for (int i = 0; i < certBytes.Length; i++)
    {
        X509Certificate2 certificate = new X509Certificate2(certBytes[i]);
        certificates.Add(certificate.Thumbprint, certificate);
    }

    TokenValidationParameters JWTparams = new TokenValidationParameters()
    {
        ValidateActor = false,
        ValidateAudience = true, 
        ValidAudience = CLIENT_ID,
        ValidateIssuer = true, 
        ValidIssuers = VALID_ISSUERS,
        ValidateIssuerSigningKey = true,
        RequireSignedTokens = true,
        IssuerSigningKeys = certificates.Values.Select(x => new X509SecurityKey(x)),
        IssuerSigningKeyResolver = (tokenString, securityToken, kid, parameters) =>
        {
            return certificates
           .Where(x => x.Key.ToUpper() == kid.ToUpper())
           .Select(x => new X509SecurityKey(x.Value));
        },
        ValidateLifetime = true,
        RequireExpirationTime = true,
        ClockSkew = TimeSpan.FromHours(13)
    };

    Claim[] claims = JWToken.Claims.ToArray<Claim>();
    for (int i = 0; i < claims.Length; i++)
    {
        if (claims[i].Type.Equals("sub"))
        {
            gplus_id = claims[i].Value;
            break;
        }
    }

    if (gplus_id == null) abortRequest(401, "Invalid Google id");

    SecurityToken validatedToken;
    ClaimsPrincipal cp;
    JwtSecurityTokenHandler JWThndlr = new JwtSecurityTokenHandler();
    try
    {
        cp = JWThndlr.ValidateToken(idToken, JWTparams, out validatedToken);
    }
    catch
    {
        cp = null;
    }

    if (cp == null) abortRequest(401, "Invalid ID Token");

    return "{\"success\": true}";
}


来源:https://stackoverflow.com/questions/56293624/google-sign-in-with-backend-verification-flow-clarification

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!