Google Sign-In with Backend Verification Flow Clarification

前端 未结 2 1539
情深已故
情深已故 2020-12-30 05:42

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

相关标签:
2条回答
  • 2020-12-30 06:24

    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

    0 讨论(0)
  • 2020-12-30 06:37

    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}";
    }
    
    0 讨论(0)
提交回复
热议问题