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
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 :
The general idea is :
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
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}";
}