Firebase: Failed to verify the signature of Firebase ID token - java

When I try to verify the Firebase jwt token in my Spring Boot backend application, I get the following error:
Failed to verify the signature of Firebase ID token. See
https://firebase.google.com/docs/auth/admin/verify-id-tokens for
details on how to retrieve an ID token.
In the client (Flutter) I log the jwt as follows:
GoogleSignInAccount googleSignInAccount = await _googleSignIn.signIn();
GoogleSignInAuthentication googleSignInAuthentication = await googleSignInAccount.authentication;
AuthCredential credential = GoogleAuthProvider.credential(
accessToken: googleSignInAuthentication.accessToken,
idToken: googleSignInAuthentication.idToken,
);
UserCredential authResult = await _auth.signInWithCredential(credential);
_user = authResult.user;
logger.i(await _user.getIdToken()); // Print jwt
I send the jwt that gets logged to my backend through the Authorization header as a bearer token.
Using Spring security (it doesn't matter), I just perform the following check:
FirebaseToken decoded = FirebaseAuth.getInstance().verifyIdToken(token);
My firebase app init config is pretty standard (env variable pointing to config.json is set):
#Primary
#Bean
public void firebaseInit() throws IOException {
FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(GoogleCredentials.getApplicationDefault())
.build();
if (FirebaseApp.getApps().isEmpty()) {
FirebaseApp.initializeApp(options);
}
}
After debugging, following method throws in class RSASignature (package package sun.security.rsa):
#Override
protected boolean engineVerify(byte[] sigBytes) throws SignatureException {
if (publicKey == null) {
throw new SignatureException("Missing public key");
}
try {
if (sigBytes.length != RSACore.getByteLength(publicKey)) {
throw new SignatureException("Signature length not correct: got " +
sigBytes.length + " but was expecting " +
RSACore.getByteLength(publicKey));
}
sigBytes length is 113, whereas it expects to be 256.
Perhaps I'm doing something wrong though...

My God... the logger that I used in dart decided to just cap the jwt string so the jwt was incomplete.
Now I get a message 'forbidden' happy joy. But the previous error has been resolved.
Edit 'Forbidden' was consequence of a minor Spring Boot issue (adding roles to authorities).
It now works as expected.

Related

Can't set up OAuth with Vkontakte via SpringBoot

Need help to implement login via Vkontakte OAuth.
According to spring-boot docs made this:
#Bean
public ClientRegistrationRepository clientRegistrationRepository() {
return new InMemoryClientRegistrationRepository(vkClientRegistration());
}
private ClientRegistration vkClientRegistration() {
return ClientRegistration.withRegistrationId("vk")
.authorizationUri("https://oauth.vk.com/authorize")
.clientId("######")
.clientSecret("######")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUri("{baseUrl}/login/oauth2/code/{registrationId}")
.scope("friends", "groups")
.tokenUri("https://oauth.vk.com/access_token")
// .userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo")
// .userNameAttributeName(IdTokenClaimNames.SUB)
// .jwkSetUri("https://www.googleapis.com/oauth2/v3/certs")
.clientName("Vkontakte")
.build();
}
As a result: context starts, localhost:8080 redirects to authorization page.
But after clicking "Allow" button browser comes back to login page with error:
[invalid_token_response] An error occurred while attempting to
retrieve the OAuth 2.0 Access Token Response: 401 Unauthorized: [no
body]
I used VK API documentation from this:
https://dev.vk.com/api/access-token/authcode-flow-user
What should I correct to complete authorization flow?
Update
Debugging I found the reason of such behavior:
VK service returns access token like this:
{
"access_token":"ed0f46118dec0a5e6935ca198......",
"expires_in":86400,
"user_id":1111111,
"email":"1111222#mail.ru"
}
And it does not have property "token_type". As a result OAuth2AccessToken constructor fails.
public OAuth2AccessToken(TokenType tokenType, String tokenValue, Instant issuedAt, Instant expiresAt,
Set<String> scopes) {
super(tokenValue, issuedAt, expiresAt);
Assert.notNull(tokenType, "tokenType cannot be null");
this.tokenType = tokenType;
this.scopes = Collections.unmodifiableSet((scopes != null) ? scopes : Collections.emptySet());
}
There is a solution which detailed describes configuring flow at the link (in Russian)

How to use vert.x AzureADAuth?

In our company we try to start using oauth2.0 with our Azure AD Tenant using vue.js as frontend and vert.x services on the backend.
The idea would be that i want to
If i call our vert.x service with the jwt which we got from Azure AD i got a runtime exception saying: "Not enough or too many segments". The JWT has 3 segments like expected. This is how i create the AzureADAuth:
OAuth2ClientOptions opts = new OAuth2ClientOptions();
opts.setFlow(OAuth2FlowType.AUTH_JWT);
OAuth2Auth auth = AzureADAuth.create(vertx,"{{application-id}}","{{secret}}","{{tenant-id}}", opts);
Inside my handler i try to authenticate:
HttpServerRequest request = context.request();
String authorization = request.headers().get(HttpHeaders.AUTHORIZATION);
String[] parts = authorization.split(" ");
scheme = parts[0];
token = parts[1];
JsonObject creds = new JsonObject();
creds.put("token_type", scheme);
creds.put("access_token", token);
authProvider.authenticate(creds,userAsyncResult -> {
if(userAsyncResult.succeeded()){
context.next();
} else {
context.fail(401);
}
});
So after i figured out that i need to add a jwk i tried to use the AzureADAuth.discover method.
My code looks like this:
OAuth2ClientOptions optsDisc = new OAuth2ClientOptions();
optsDisc.setSite("https://login.windows.net/{tenant-id}");
optsDisc.setClientID("{application-id}");
AzureADAuth.discover(vertx, optsDisc,res -> {
if (res.succeeded()) {
if(log.isDebugEnabled()) {
log.debug("Discover succeeded.");
}
} else {
log.error("Discover failed.");
}
});
Running this code causes a "Discover failed" with the following message:
java.lang.RuntimeException: java.security.cert.CertificateException: Could not parse certificate: java.io.IOException: Empty input
So my question is how do i authenticate my user with my given bearer token with vert.x?
I obviously had a version conflict here.
I set all my dependencies to 3.6.2 and now it works. Just took me a bit to figure out how to handle the discovery and that i don't need to create a new OAuth2Auth object with AzureAdAuth after the discovery.
For future reference:
OAuth2ClientOptions optsDisc = new OAuth2ClientOptions();
opts.setClientID("{client-id}");
AzureADAuth.discover(vertx, opts,res -> {
if (res.succeeded()) {
//use res.result() to access the through discovery already created OAuth2Auth Object
log.debug("Discover succeeded.");
} else {
log.error("Discover failed.");
}
})

Getting Cognito Credentials on Android

I need to authenticate a user using AWS's Cognito in Android and get a token to use on my future requests. Some information is provided to me by the backend but I still haven't managed to use it in the appropriate way, and Cognito's documentation did not help me on this. I have this fixed info:
Pool Region: us-east-1
Pool ID: us-east-1:xxxxx-xxxxx-xxxxx-xxxx-xxxxxxxx
And after authenticating the user on the login endpoint I get this info:
{
"cognitoId": "us-east-1:yyyy-yyyy-yyyy-yyyy-yyyyyyy",
"cognitoToken": "hH1Q8bCLh9-pamP6DCrC0-KY4rNtZ115xDedE224CeEanex-CCWh4tWUtJjPc_tU3d6eJ_7Uk23ceTNhCFYT1qnAL_7kAH_lHod4a1GQo29FuTLQSqx4lOFv2Ev3RvYcCzjyLEAA1-EIKBtfSm_YN9y6DHBOzDJ8owLJTxB0JEWvsWfATjug4P8hxCI97RVB2cetrmq4JvZr__bCziUb-7AifPvy4VMW3xLjJ7uyDvogwcx5gJ1rF8Z38_z7kREB1R_CYPRVQuoHzag0j9RoOTNeAYFGO42qgCewTl3Lvm5PUbTIGhCIp6y1RVWAPLEdMWmQ3LVpqJcZKLQRhMmEzOGMyTUiXSwiaXNzIjoiaHR0cHM6Ly9jb2duaXRvLWlkZW50aXR5LmFtYXpvbmF3cy5jb20iLCJleHAiOjE1MTE2NDEzMDksImlhdCI6MTUxMTYyNjkwOX0.QFWGxh_"
}
The IDs were omitted and the token was altered in order to preserve the information. It is important to note that the Pool ID (constant in the app) and the cognitoId (returned by the backend) are different.
I have a static Credentials Provider initialized like this:
credentialsProvider = new CognitoCachingCredentialsProvider(
getApplicationContext(), /* get the context for the application */
IDENTITY_POOL_ID, /* Identity Pool ID */
Regions.US_EAST_1 /* Region for your identity pool--US_EAST_1 or EU_WEST_1*/
);
This is the task that does the work of trying to get the Cognito auth:
private static final class CognitoAuthTask extends AsyncTask<String, Void, String> {
#Override
protected String doInBackground(String... strings) {
String userId = strings[0];
String token = strings[1];
String sessionToken = null;
try {
Map<String, String> logins = new HashMap<String, String>();
logins.put(userId, token);
credentialsProvider.setLogins(logins);
AWSSessionCredentials credentials = credentialsProvider.getCredentials();
sessionToken = credentials.getSessionToken();
} catch (Exception e) {
if (BuildConfig.DEBUG) {
e.printStackTrace();
}
} finally {
return sessionToken;
}
}
#Override
protected void onPostExecute(String authToken) {
super.onPostExecute(authToken);
cognitoAuthToken = authToken;
if (BuildConfig.DEBUG) {
Log.d("Cognito Token", cognitoAuthToken == null ? "null" : cognitoAuthToken);
}
}
}
And this is where I call it when I have the information from my login endpoint (as I showed above):
public void authenticateCognito(String userId, String token) {
new CognitoAuthTask().execute(userId, token);
}
The problem is that this is not working, I get this error here:
Invalid login token. Can't pass in a Cognito token. (Service:
AmazonCognitoIdentity; Status Code: 400; Error Code:
NotAuthorizedException; Request ID: zzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzz)
The error happens on the task, on this line of code here:
credentialsProvider.getCredentials();
The backend team mentioned that I would need to use the GetCredentialsForIdentity method, but I can't find anything like that on the Cognito Android SDK.
Any help is appreciated.
The class you should be using is AmazonCognitoIdentityClient, that is the class implementing the GetCredentialsForIdentity API.
When credentialsProvider.getCredentials(); is invoked, the internal AmazonCognitoIdentityClient calls GetCredentialsForIdentity to get new credentials from Cognito.
The Invalid login token error is returned by the service if the provided token has expired.

Spring STOMP Token Based Security

I want to implement token based authentication for web socket connection.
My backend system is server and a machine is client, how to implement authentication during sending and receiving message between machine and server, no user authentication here.
#Configuration
#EnableWebSocketMessageBroker
public class MyConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.setInterceptors(new ChannelInterceptorAdapter() {
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor =
MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
String authToken = accessor.getFirstNativeHeader("X-Auth-Token");
log.debug("webSocket token is {}", authToken);
Principal user = ... ; // access authentication header(s)
accessor.setUser(user);
}
return message;
}
});
}
}
However, I'm totally lost how I would do at "Principal user = ... ;". How would I get Principle with the token? As here no user exist, only communication between a machine and a backend server
Guess you can try to get principal from spring security context
SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Sounds like you might need a solution similar to something that would be used by someone building an IoT solution. A good starting point would be to have a look at OpenID Connect.
There's a good article here discussing the related challenges and solutions:
https://www.gluu.org/blog/oauth2-for-iot/
OpenId Connect's site:
http://openid.net/connect/
I know this issue has been around for a long time, and there are already plenty of answers available on Stack Overflow, such as this and this. These answers indeed fix this issue but all are trying to have their own implementations of the Principle interface which is not needed as Spring Security has already implemented all we need.
Answer your question: The principle needs to be created from the token provided in the request. You can easily convert string token to a Principle by using new BearerTokenAuthenticationToken("token-string-without-bearer"), then after that, you should authenticate the token by using authenticationManager.authenticate(bearerTokenAuthenticationToken). Therefore, the code is like this:
// access authentication header(s)
Principal user = authenticationManager.authenticate(new BearerTokenAuthenticationToken(authToken));
The full implementation of the configureClientInboundChannel method should be (sorry I only have Kotlin code, but you should be able to get the idea):
override fun configureClientInboundChannel(registration: ChannelRegistration) {
registration.interceptors(object : ChannelInterceptor {
override fun preSend(message: Message<*>, channel: MessageChannel): Message<*>? {
val accessor = MessageHeaderAccessor
.getAccessor(message, StompHeaderAccessor::class.java)
if (accessor != null && accessor.command == StompCommand.CONNECT) {
// assume the value of Authorization header has Bearer at the beginning
val authorization = accessor.getFirstNativeHeader("Authorization")
?.split(" ", limit = 2)
?: return message
// check token type is Bearer
if (authorization.getOrNull(0) == "Bearer") {
val token = authorization.getOrNull(1)?.takeIf { it.isNotBlank() }
// if token exists, authenticate and (if succeeded) then assign user to the session
if (token != null) {
val user = try {
authenticationManager.authenticate(BearerTokenAuthenticationToken(token))
} catch (ex: AuthenticationException) {
// if throw an exception, do not touch the user header
null
}
if (user != null) {
accessor.user = user
}
}
}
}
return message
}
})
}
Note that BearerTokenAuthenticationToken comes from org.springframework.boot:spring-boot-starter-oauth2-resource-server, so you need this dependency in order to use it.
authenticationManager comes from the implementation of WebSecurityConfigurerAdapter, exposing as a Bean by overriding authenticationManagerBean method and mark it as #Bean, and then injecting the Bean into your AbstractWebSocketMessageBrokerConfigurer implementation, which in this case, is MyConfig.
Also, don't forget to mark MyConfig class with #Order(Ordered.HIGHEST_PRECEDENCE + 99)
so that it sets up the user header before Spring Security takeover.

Google OAuth, handle a revoked authorization

I've been using Google OAuth to let users authorize access to the Calendar Service for my Web Application. After a successful 3-legged auth flow, I was storing all user's credentials in a common file on the app Server. The next time the app needs to use the service, it will check if the credentials exist, and if yes, it will assume they are valid
code works like that
#Override
public void _authorize(String userId) throws IOException {
// Check if user has already authorised the service.
Credential credents = flow.loadCredential(userId);
// Checking if the given user is not authorized
if (credents == null) {
//Create credentials now. user will be redirected to authorise
try {
//Creating a LocalServer Receiver
// Getting the redirect URI
// Creating a new authorization URL
// Setting the redirect URI
// Building the authorization URL
// Receiving authorization code
// Exchanging it for an access token
// Storing the credentials for later access
credents = flow.createAndStoreCredential(response, id);
} finally {
// Releasing resources
}
} else {
// Assume the credentials are valid. so there's nothing left to do here, let's get that client
//Update: Nooooooot! the user might have revoked the authorization, so credents != null BUT they are invalid
//TODO: handle an Exception here, and manage the revoked credentials
}
// Setting up the calendar service client
client = new com.google.api.services.calendar.Calendar.Builder(httpTransport, jsonFactory, credents).setApplicationName(APPLICATION_NAME)
.build();
}
This works fine, as long as the user never changes his mind. But if the user decides to manually revoke the authorization using the Google Account security options, the com.google.api.services.calendar.Calendar retrieval will Fail.
My question is :
Is there a way to check if the credentials are still valid, before trying to use them ?
Else, I can only guess that the failure to get the client object, is the only way to have my portal realize that the credentials are no more valid ?
What should I do about the invalid/revoked credentials ? should I just call flow.createAndStoreCredential and they are going to be overwritten? Or do I have to delete the old ones first ? (how ?)
You can use the refreshToken() method for this. See example:
// Fetch credential using the GoogleAuthorizationCodeFlow
GoogleAuthorizationCodeFlow authorizationCodeFlow;
Credential credential = authorizationCodeFlow.loadCredential(userId);
if (credential != null) {
try {
// refresh the credential to see if the refresh token is still valid
credential.refreshToken();
System.out.println("Refreshed: expires in: " + credential.getExpiresInSeconds());
} catch (TokenResponseException e) {
// process exception here.
// This will catch the Exception.
// This Exception contains the HTTP status and reason etc.
// In case of a revoke, this will throw something like a 401 - "invalid_grant"
return;
}
} else {
// No credential yet known.
// Flow for creating a new credential here
}
EDIT
If you indeed have an invalid refresh token and you want to renew it, then you need to repeat the steps that you did in the first place to get the credentials. So:
genererate a new authorization URL
redirect the user to it
user accepts the consent screen
catch the authorization code from the redirect back to your app
request a new token from Google using the authorization code
create and store a new Credential using the response from Google
No need to delete the old credential. But if you want to explicitly do so, it is possible.
Something like:
// This userId is obviously the same as you used to create the credential
String userId = "john.doe";
authorizationCodeFlow.getDataStore().delete(userId);
You can use the endpoint https://www.googleapis.com/oauth2/v1/tokeninfo to determine if an OAuth2 token is still valid. More information is available in the OAuth2 guide.
Answer to the first question:
When using the Service object for retrieving calendar items from Google Calendar, the token are automatically verified. When they are invalid, they will be refreshed automatically, and stored in the datastore you provided to the flow.
this can also be done manually. A token is valid for 3600 seconds (one hour). When retrieving a token you get this value with the timestamp when it was issued. You could manually determine if a token is valid. If it is not valid call the following async method.
await credents.RefreshtokenAsync(CancellationToken.None);
This function gets you fresh tokens, and stores them in the datastore you provided.
You could check token with tokeninfo and if token is not valid:
- remove credential from datastore
- invoke new auth
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
UserService userService = UserServiceFactory.getUserService();
if (userService.isUserLoggedIn()) {
User user = userService.getCurrentUser();
log.info(String.format("LoggedUser: %s %s", user.getEmail(), user.getUserId()));
Credential credential = this.getCredential();
Tokeninfo tokenInfo = OAuth2Utils.getTokenInfo(credential, null);
if (tokenInfo != null)
log.info(String.format("Token expires in: %d", tokenInfo.getExpiresIn()));
else {
OAuth2Utils.deleteCredential(user.getUserId());
response.sendRedirect(request.getRequestURI()); // recall this servlet to require new user authorization
return;
}
}
public static Tokeninfo getTokenInfo(Credential credential, String accessToken) {
Oauth2 service = new Oauth2.Builder(new NetHttpTransport(), Constant.JSON_FACTORY, credential).setApplicationName(Constant.APP_NAME).build();
Tokeninfo tokenInfo = null;
try {
tokenInfo = service.tokeninfo().setAccessToken( accessToken == null ? credential.getAccessToken() : accessToken ).execute();
} catch (IOException e) {
log.warning("An error occurred: " + e);
}
return tokenInfo;
}

Categories

Resources