I'm building a test version of an app for a client. Part of this app uses a WebView that calls out to a SSL-based site. In turn, the client has provided a test domain where the certificate name does not match the FQDN. Alas, they are not in a position to provision a cert that matches. :(
I'm working around this issue on the companion iOS ad hoc app with one line of code (again, not for production use - just for test purposes). I have searched for similar info on Android OS, but the solutions I've seen here and elsewhere are enough to make my head spin big time by comparison!
Is there a straightforward way to work around this? Even a user-facing setting tucked away somewhere?
Clues appreciated!
Create a WebViewClient and handle the onReceivedSslError which looks like this:
public void onReceivedSslError (WebView view, SslErrorHandler handler, SslError error)
Inside this callback you can just call handler.proceed() and the page will continue loading. If you don't handle this callback and call the proceed() method then the default behaviour will be for the page not to load.
Updated answer according Google's new Security policy update for SSL Error Handler, please see this Android Developers Help Center article.
For prevent rejection of application on Google Play for violating our Malicious Behavior policy.
To properly handle SSL certificate validation, change your code to invoke SslErrorHandler.proceed() whenever the certificate presented by the server meets your expectations, and invoke SslErrorHandler.cancel() otherwise.
For example, I add an alert dialog to make user have confirmed and seems Google no longer shows warning.
#Override
public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
String message = "SSL Certificate error.";
switch (error.getPrimaryError()) {
case SslError.SSL_UNTRUSTED:
message = "The certificate authority is not trusted.";
break;
case SslError.SSL_EXPIRED:
message = "The certificate has expired.";
break;
case SslError.SSL_IDMISMATCH:
message = "The certificate Hostname mismatch.";
break;
case SslError.SSL_NOTYETVALID:
message = "The certificate is not yet valid.";
break;
}
message += " Do you want to continue anyway?";
builder.setTitle("SSL Certificate Error");
builder.setMessage(message);
builder.setPositiveButton("continue", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
handler.proceed();
}
});
builder.setNegativeButton("cancel", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
handler.cancel();
}
});
final AlertDialog dialog = builder.create();
dialog.show();
}
Related
Hello I have a fully working code for signing in a Player by Google Play Games Sign in. See this code snippet
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN)
.requestServerAuthCode("720182182679-fv285c7k5kecqhqdmc9ggc9f73jc9hef.apps.googleusercontent.com")
.build();
mGoogleSignInClient = GoogleSignIn.getClient(start.this, gso);
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(Games.API)
.addScope(Games.SCOPE_GAMES)
.addApi(Auth.GOOGLE_SIGN_IN_API, gso)
.build();
private void startSignInIntent() {
GoogleSignInClient googleSignInClient = GoogleSignIn.getClient(this, GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN);
Intent intent = googleSignInClient.getSignInIntent();
startActivityForResult(intent, RC_SIGN_IN);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == RC_SIGN_IN) {
GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
if (result.isSuccess()) {
// The signed in account is stored in the result.
GoogleSignInAccount signedInAccount = result.getSignInAccount();
assert signedInAccount != null;
Toast.makeText(start.this, "Google Play Games Connectet", Toast.LENGTH_SHORT).show();
} else {
String message = result.getStatus().getStatusMessage();
new AlertDialog.Builder(this).setMessage(message)
.setNeutralButton(android.R.string.ok, null).show();
}
}
}
The code above works perfektly. But if I want to trigger the following method the game crashes, and gets me the error in the title.
private void firebaseAuthWithPlayGames(GoogleSignInAccount acct) {
Log.d(TAG, "firebaseAuthWithPlayGames:" + acct.getId());
AuthCredential credential = PlayGamesAuthProvider.getCredential(acct.getServerAuthCode());
mAuth.signInWithCredential(credential)
.addOnCompleteListener(this, task -> {
if (task.isSuccessful()) {
// Sign in success, update UI with the signed-in user's information
Log.d(TAG, "signInWithCredential:success");
FirebaseUser user = mAuth.getCurrentUser();
updateUI(user);
} else {
// If sign in fails, display a message to the user.
Log.w(TAG, "signInWithCredential:failure", task.getException());
Toast.makeText(start.this, "Authentication failed.",
Toast.LENGTH_SHORT).show();
updateUI(null);
}
// ...
});
}
I trigger this method by calling it in the on ActivityResult of startSignInIntent like this:
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == RC_SIGN_IN) {
GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
if (result.isSuccess()) {
// The signed in account is stored in the result.
GoogleSignInAccount signedInAccount = result.getSignInAccount();
assert signedInAccount != null;
---------> firebaseAuthWithPlayGames(signedInAccount); <-----------
Toast.makeText(start.this, "Google Play Games Connectet", Toast.LENGTH_SHORT).show();
} else {
String message = result.getStatus().getStatusMessage();
new AlertDialog.Builder(this).setMessage(message)
.setNeutralButton(android.R.string.ok, null).show();
}
}
}
I hope someone has faced this issue and can help.
I believe this is because it is not getting the token correctly between Game Services and Firebase
You have to configure the web app that is linked to the same game in the play console. Then copy the client id from the web app and paste it into the setup dialog for the plugin.
Steps are here...
https://firebase.google.com/docs/auth/android/play-games
Double check these steps:
Enable Google Play Games as a sign-in provider:
Find your project's web server client ID and client secret. The web server client ID identifies your Firebase project to the Google Play auth servers.
To find these values:
Open your Firebase project in the Google APIs console credentials page.
In the OAuth 2.0 client IDs section, open the Web client (auto created by Google Service) details page. This page lists your web server client ID and secret.
Then, in the Firebase console, open the Authentication section.
On the Sign in method tab, enable the Play Games sign-in provider. You will need to specify your project's web server client ID and client secret, which you got from the APIs console.
I think I might have just bumped into the exact same issue you have experienced earlier. I see, already 1 year have passed since your question, but I hope maybe it still can help you or others.
I was doing the integration of Firebase with my existing game that had Google Play Games Services. I wanted Firebase to use Play Games authentication, but after I have done everything according to the manual, the sign-in into Firebase was unsuccessful with the error message: Given String is empty or null.
I have figured out that actually there was no problem with the code, nor with the integration setup. The solution was that simply I had to log out from my Google account and re-login again.
I think, at the time when I last logged in to my Google account and Games Services in my app, the info about the Firebase integration setup was not existing there yet, so my cached account on my phone did not know about it at all. The re-login refreshed the settings in the cache and the following authCode request could finally successfully return the needed string:
String authCode = signedInAccount.getServerAuthCode();
I have a link which will open in WebView. The problem is it cannot be open until I override onReceivedSslError like this:
#Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
handler.proceed();
}
I am getting security alert from Google Play saying:
Security alert
Your application has an unsafe implementation of the WebViewClient.onReceivedSslError handler. Specifically, the implementation ignores all SSL certificate validation errors, making your app vulnerable to man-in-the-middle attacks. An attacker could change the affected WebView's content, read transmitted data (such as login credentials), and execute code inside the app using JavaScript.
To properly handle SSL certificate validation, change your code to invoke SslErrorHandler.proceed() whenever the certificate presented by the server meets your expectations, and invoke SslErrorHandler.cancel() otherwise. An email alert containing the affected app(s) and class(es) has been sent to your developer account address.
Please address this vulnerability as soon as possible and increment the version number of the upgraded APK. For more information about the SSL error handler, please see our documentation in the Developer Help Center. For other technical questions, you can post to https://www.stackoverflow.com/questions and use the tags “android-security” and “SslErrorHandler.” If you are using a 3rd party library that’s responsible for this, please notify the 3rd party and work with them to address the issue.
To confirm that you've upgraded correctly, upload the updated version to the Developer Console and check back after five hours. If the app hasn't been correctly upgraded, we will display a warning.
Please note, while these specific issues may not affect every app that uses WebView SSL, it's best to stay up to date on all security patches. Apps with vulnerabilities that expose users to risk of compromise may be considered dangerous products in violation of the Content Policy and section 4.4 of the Developer Distribution Agreement.
Please ensure all apps published are compliant with the Developer Distribution Agreement and Content Policy. If you have questions or concerns, please contact our support team through the Google Play Developer Help Center.
If I remove onReceivedSslError (handler.proceed()), then page won't open.
Is there any way I can open the page in WebView and avoid security alert?
To properly handle SSL certificate validation, change your code to
invoke SslErrorHandler.proceed() whenever the certificate presented by
the server meets your expectations, and invoke
SslErrorHandler.cancel() otherwise.
As email said, onReceivedSslError should handle user is going to a page with invalid cert, such like a notify dialog. You should not proceed it directly.
For example, I add an alert dialog to make user have confirmed and seems Google no longer shows warning.
#Override
public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(R.string.notification_error_ssl_cert_invalid);
builder.setPositiveButton("continue", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
handler.proceed();
}
});
builder.setNegativeButton("cancel", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
handler.cancel();
}
});
final AlertDialog dialog = builder.create();
dialog.show();
}
More explain about the email.
Specifically, the implementation ignores all SSL certificate validation
errors, making your app vulnerable to man-in-the-middle attacks.
The email says the default implement ignored an important SSL security problem. So we need to handle it in our own app which used WebView. Notify user with a alert dialog is a simple way.
The proposed solutions so far just bypass the security check, so they are not safe.
What I suggest is to embed the certificate(s) in the App, and when a SslError occurs, check that the server certificate matches one of the embedded certificates.
So here are the steps:
Retrieve the certificate from the website.
Open the site on Safari
Click on the padlock icon near the website name
Click on Show Certificate
Drag and drop the certificate in a folder
see https://www.markbrilman.nl/2012/03/howto-save-a-certificate-via-safari-on-mac/
Copy the certificate (.cer file) into the res/raw folder of your app
In your code, load the certificate(s) by calling loadSSLCertificates()
private static final int[] CERTIFICATES = {
R.raw.my_certificate, // you can put several certificates
};
private ArrayList<SslCertificate> certificates = new ArrayList<>();
private void loadSSLCertificates() {
try {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
for (int rawId : CERTIFICATES) {
InputStream inputStream = getResources().openRawResource(rawId);
InputStream certificateInput = new BufferedInputStream(inputStream);
try {
Certificate certificate = certificateFactory.generateCertificate(certificateInput);
if (certificate instanceof X509Certificate) {
X509Certificate x509Certificate = (X509Certificate) certificate;
SslCertificate sslCertificate = new SslCertificate(x509Certificate);
certificates.add(sslCertificate);
} else {
Log.w(TAG, "Wrong Certificate format: " + rawId);
}
} catch (CertificateException exception) {
Log.w(TAG, "Cannot read certificate: " + rawId);
} finally {
try {
certificateInput.close();
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
} catch (CertificateException e) {
e.printStackTrace();
}
}
When a SslError occurs, check that the server certificate matches one embedded certificate. Note that it is not possible to directly compare certificates, so I use SslCertificate.saveState to put the certificate data into a Bundle, and then I compare all the bundle entries.
webView.setWebViewClient(new WebViewClient() {
#Override
public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) {
// Checks Embedded certificates
SslCertificate serverCertificate = error.getCertificate();
Bundle serverBundle = SslCertificate.saveState(serverCertificate);
for (SslCertificate appCertificate : certificates) {
if (TextUtils.equals(serverCertificate.toString(), appCertificate.toString())) { // First fast check
Bundle appBundle = SslCertificate.saveState(appCertificate);
Set<String> keySet = appBundle.keySet();
boolean matches = true;
for (String key : keySet) {
Object serverObj = serverBundle.get(key);
Object appObj = appBundle.get(key);
if (serverObj instanceof byte[] && appObj instanceof byte[]) { // key "x509-certificate"
if (!Arrays.equals((byte[]) serverObj, (byte[]) appObj)) {
matches = false;
break;
}
} else if ((serverObj != null) && !serverObj.equals(appObj)) {
matches = false;
break;
}
}
if (matches) {
handler.proceed();
return;
}
}
}
handler.cancel();
String message = "SSL Error " + error.getPrimaryError();
Log.w(TAG, message);
}
});
I needed to check our truststore before show any message to the user so I did this:
public class MyWebViewClient extends WebViewClient {
private static final String TAG = MyWebViewClient.class.getCanonicalName();
Resources resources;
Context context;
public MyWebViewClient(Resources resources, Context context){
this.resources = resources;
this.context = context;
}
#Override
public void onReceivedSslError(WebView v, final SslErrorHandler handler, SslError er){
// first check certificate with our truststore
// if not trusted, show dialog to user
// if trusted, proceed
try {
TrustManagerFactory tmf = TrustManagerUtil.getTrustManagerFactory(resources);
for(TrustManager t: tmf.getTrustManagers()){
if (t instanceof X509TrustManager) {
X509TrustManager trustManager = (X509TrustManager) t;
Bundle bundle = SslCertificate.saveState(er.getCertificate());
X509Certificate x509Certificate;
byte[] bytes = bundle.getByteArray("x509-certificate");
if (bytes == null) {
x509Certificate = null;
} else {
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
Certificate cert = certFactory.generateCertificate(new ByteArrayInputStream(bytes));
x509Certificate = (X509Certificate) cert;
}
X509Certificate[] x509Certificates = new X509Certificate[1];
x509Certificates[0] = x509Certificate;
trustManager.checkServerTrusted(x509Certificates, "ECDH_RSA");
}
}
Log.d(TAG, "Certificate from " + er.getUrl() + " is trusted.");
handler.proceed();
}catch(Exception e){
Log.d(TAG, "Failed to access " + er.getUrl() + ". Error: " + er.getPrimaryError());
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
String message = "SSL Certificate error.";
switch (er.getPrimaryError()) {
case SslError.SSL_UNTRUSTED:
message = "O certificado não é confiável.";
break;
case SslError.SSL_EXPIRED:
message = "O certificado expirou.";
break;
case SslError.SSL_IDMISMATCH:
message = "Hostname inválido para o certificado.";
break;
case SslError.SSL_NOTYETVALID:
message = "O certificado é inválido.";
break;
}
message += " Deseja continuar mesmo assim?";
builder.setTitle("Erro");
builder.setMessage(message);
builder.setPositiveButton("Sim", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
handler.proceed();
}
});
builder.setNegativeButton("Não", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
handler.cancel();
}
});
final AlertDialog dialog = builder.create();
dialog.show();
}
}
}
Fix which works for me is just disable onReceivedSslError function defined in AuthorizationWebViewClient. In this case handler.cancel will be called in case of SSL error. However it works good with One Drive SSL certificates. Tested on Android 2.3.7, Android 5.1.
According to Google Security Alert: Unsafe implementation of the interface X509TrustManager, Google Play won't support X509TrustManager from 11th July 2016:
Hello Google Play Developer,
Your app(s) listed at the end of this email use an unsafe
implementation of the interface X509TrustManager. Specifically, the
implementation ignores all SSL certificate validation errors when
establishing an HTTPS connection to a remote host, thereby making your
app vulnerable to man-in-the-middle attacks. An attacker could read
transmitted data (such as login credentials) and even change the data
transmitted on the HTTPS connection. If you have more than 20 affected
apps in your account, please check the Developer Console for a full
list.
To properly handle SSL certificate validation, change your code in the
checkServerTrusted method of your custom X509TrustManager interface to
raise either CertificateException or IllegalArgumentException whenever
the certificate presented by the server does not meet your
expectations. For technical questions, you can post to Stack Overflow
and use the tags “android-security” and “TrustManager.”
Please address this issue as soon as possible and increment the
version number of the upgraded APK. Beginning May 17, 2016, Google
Play will block publishing of any new apps or updates containing the
unsafe implementation of the interface X509TrustManager.
To confirm you’ve made the correct changes, submit the updated version
of your app to the Developer Console and check back after five hours.
If the app hasn’t been correctly upgraded, we will display a warning.
While these specific issues may not affect every app with the
TrustManager implementation, it’s best not to ignore SSL certificate
validation errors. Apps with vulnerabilities that expose users to risk
of compromise may be considered dangerous products in violation of the
Content Policy and section 4.4 of the Developer Distribution
Agreement.
...
I had the same issue and tried all the above-mentioned suggestions as below.
Implement onReceivedSslError() by giving the chance to the user to
decide handler.proceed(); or handler.cancel(); when a SSL error
occurred
Implement onReceivedSslError() to call handler.cancel(); whenever a
SSL issue occurred without considering user's decision.
Implement onReceivedSslError() to verify SSL certificate locally
addition to checking error.getPrimaryError() and providing the user
to decide handler.proceed(); or handler.cancel(); only if the SSL
certificate is valid. If not just call handler.cancel();
Removing the implementation of onReceivedSslError() and just let to
happen Android default behavior.
Even after trying all the above attempts, Google Play was keeping sending the same notification mail mentioning the same error and the old APK version (Even though in the all above attempts we changed both version code and version name in the Gradle)
We were in huge trouble and contacted Google Support via mail and asked
"We are uploading the higher versions of the APK but the review result
says the same error mentioning the old buggy APK version. What's the
reason for that ?"
After a few days, google support replied to our request as follows.
Please note that you must completely replace version 12 in your
Production track. It means that you'll have to full rollout a higher
version in order to deactivate version 12.
The highlighted point was never found or mentioned in the play console or any forum.
According to that guideline in the classic Google play view, we checked the production track and there were both buggy versions and the latest bug fixed version but the bug fix version's rollout percentage is 20%. So, turned it to full rollout then the buggy version disappeared from the production track. After more than 24 hour review time version has come back.
NOTE: When we had this issue Google has just moved to a new UI version of their play console and it had missed some views in the previous UI version or classic view. Since we were using the latest view we couldn't notice what was happening. Simply what happened was Google reviewed the same previous buggy version of the APK since the new one was not full roll-out.
You can use SslError for show, some information about the error of this certificated, and you can write in your dialog the string of the type error.
#Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
final SslErrorHandler handlerFinal;
handlerFinal = handler;
int mensaje ;
switch(error.getPrimaryError()) {
case SslError.SSL_DATE_INVALID:
mensaje = R.string.notification_error_ssl_date_invalid;
break;
case SslError.SSL_EXPIRED:
mensaje = R.string.notification_error_ssl_expired;
break;
case SslError.SSL_IDMISMATCH:
mensaje = R.string.notification_error_ssl_idmismatch;
break;
case SslError.SSL_INVALID:
mensaje = R.string.notification_error_ssl_invalid;
break;
case SslError.SSL_NOTYETVALID:
mensaje = R.string.notification_error_ssl_not_yet_valid;
break;
case SslError.SSL_UNTRUSTED:
mensaje = R.string.notification_error_ssl_untrusted;
break;
default:
mensaje = R.string.notification_error_ssl_cert_invalid;
}
AppLogger.e("OnReceivedSslError handel.proceed()");
View.OnClickListener acept = new View.OnClickListener() {
#Override
public void onClick(View v) {
dialog.dismiss();
handlerFinal.proceed();
}
};
View.OnClickListener cancel = new View.OnClickListener() {
#Override
public void onClick(View v) {
dialog.dismiss();
handlerFinal.cancel();
}
};
View.OnClickListener listeners[] = {cancel, acept};
dialog = UiUtils.showDialog2Buttons(activity, R.string.info, mensaje, R.string.popup_custom_cancelar, R.string.popup_custom_cancelar, listeners); }
In my situation:This error occured when we try to updated apk uploaded
into the Google Play store,and getting SSL Error:
Then i have used following code
private class MyWebViewClient extends WebViewClient {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
#Override
public void onPageFinished(WebView view, String url) {
try {
progressDialog.dismiss();
} catch (WindowManager.BadTokenException e) {
e.printStackTrace();
}
super.onPageFinished(view, url);
}
#Override
public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) {
final AlertDialog.Builder builder = new AlertDialog.Builder(PayNPayWebActivity.this);
builder.setMessage(R.string.notification_error_ssl_cert_invalid);
builder.setPositiveButton("continue", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
handler.proceed();
}
});
builder.setNegativeButton("cancel", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
handler.cancel();
}
});
final AlertDialog dialog = builder.create();
dialog.show();
}
}
I am setting GoogleSignInOptions and Google Api Client like this
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(getString(R.string.server_client_ID))
.build();
mGoogleApiClient = new GoogleApiClient.Builder(this)
.enableAutoManage(this /* FragmentActivity */, this /* OnConnectionFailedListener */)
.addApi(Auth.GOOGLE_SIGN_IN_API, gso)
.addApi(Plus.API)
.build();
and my google web app client id like this:
1020847812450-xxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com
but always at onActivityResult
if (requestCode == RC_SIGN_IN) {
GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
handleSignInResult(result);
}
is returning false
where am i doing wrong here :S
onStart Section
mGoogleApiClient.connect();
OptionalPendingResult<GoogleSignInResult> opr = Auth.GoogleSignInApi.silentSignIn(mGoogleApiClient);
if (opr.isDone()) {
// If the user's cached credentials are valid, the OptionalPendingResult will be "done"
// and the GoogleSignInResult will be available instantly.
Log.d(TAG, "Got cached sign-in");
// GoogleSignInResult result = opr.get();
// handleSignInResult(result);
} else {
// If the user has not previously signed in on this device or the sign-in has expired,
// this asynchronous branch will attempt to sign in the user silently. Cross-device
// single sign-on will occur in this branch.
opr.setResultCallback(new ResultCallback<GoogleSignInResult>() {
#Override
public void onResult(GoogleSignInResult googleSignInResult) {
handleSignInResult(googleSignInResult);
}
});
}
onStop section
protected void onStop() {
super.onStop();
if (mGoogleApiClient.isConnected()) {
mGoogleApiClient.disconnect();
}
}
onHandleSignInResult
private void handleSignInResult(GoogleSignInResult result) {
Log.e(TAG, "handleSignInResult:" + result.isSuccess());
if (result.isSuccess()) {
// Signed in successfully, show authenticated UI.
final GoogleSignInAccount acct = result.getSignInAccount();
Log.e(TAG, acct.getDisplayName());
}
}
I am also facing the same issue.First remove the current OAuth client ID,after that create one more OAuth client ID.Its worked for me.
Are you getting error 12501? I also had this issue because I was using debug.keystore which comes with the SDK (for some reason unknown to me, it didn't work). I created a new one on my own, got SHA-1 hash from it, entered in Google API console and then it worked.
Be sure you set up signing configs for both debug and release builds with the new keystore.
Follow all the step!!..
Release APK and debug APK has different SHA1 and different API keys for google services. Both of them must be added in Firebase Console -> Project settings. Then download google-services.json from here, add it to project and recompile with release keystore using the option "Build signed APK". That should work
and also read carefully...
https://developer.android.com/studio/publish/app-signing
I believe that you need a call to client.connect(); as per documentation example:
GoogleApiClient client = new GoogleApiClient.Builder(this)
.addApi(Plus.API)
.addScope(Plus.SCOPE_PLUS_LOGIN)
.setAccountName("users.account.name#gmail.com")
.build();
client.connect();
Or is it missing from your question and you are calling connect somwhere else in your code?
While creating a Open Request using the Facebook SDK, i get the following error.
Error:
Caused by: java.lang.UnsupportedOperationException: Session: an attempt was made to open an already opened session.
at com.facebook.Session.open(Session.java:985)
at com.facebook.Session.openForRead(Session.java:388)
at com.photos.pixitor.activities.PhotoEffectBaseActivity.loginRequest(PhotoEffectBaseActivity.java:619)
The error does not occur If I first make the request. But after making the login request first and then cancelling the request and again main the login request , the application crashes.
Here is the Code:
OpenRequest request = new Session.OpenRequest(this);
request.setPermissions(Arrays.asList("basic_info"));
if(session.isOpened()){
session.requestNewReadPermissions(new NewPermissionsRequest(
PhotoEffectBaseActivity.this,"basic_info"));
session.addCallback(new StatusCallback() {
#Override
public void call(Session session, SessionState state, Exception exception) {
if(state.isOpened()){
Util.logd("Opened+Publishing Request");
publishPhotoRequest(session);
}
if(session.isOpened()){
Util.logd("Session is Opened");
getUserDetails(session);
}
}
});
return session;
}
Util.logd("Session Not Opened: Opening For Read");
session.openForRead(request);
Util.logd("Session is Opened for Read");
session.addCallback(new StatusCallback() {
#Override
public void call(Session session, SessionState state, Exception exception) {
if(state.isOpened()){
Util.logd("Opened+Publishing Request");
publishPhotoRequest(session);
}
if(session.isOpened()){
Util.logd("Session is Opened");
getUserDetails(session);
}
}
});
One thing to realize is that session opening is asynchronous (since it needs to possibly call out to the Facebook app, and get user input). So you can't make two session.open* calls in a row without waiting for the first one to return.
What's happening in your code is that you have:
if (session.isOpened()) {
// MAKE AN OPEN REQUEST
}
// MAKE ANOTHER OPEN REQUEST
This basically makes 2 open requests in a row if your session was already opened.
So how do you fix this?
First of all, the session.requestNewReadPermissions() call is unnecessary since it's only asking for "basic_info", and that comes by default, so you don't need to ask for any additional permissions. You can just remove this whole block.
Secondly, if you did want to request additional read permissions, you can just add them to the session.openForRead() method you're calling later on.
Lastly, a couple of other issues I noticed with your code: you're adding the callback AFTER you're calling session.openForRead(), this probably won't work the way you want. You'll want to add the callback to your request, and BEFORE you call openForRead. You're also trying to publish photos, and I'm not seeing any publish permissions being requested.
The WebAuthenticationBroker doesn't seem to be able to handle navigation to my ms-app://. Just throws this ugly error as you will see below.
Steps
Call AuthenticateAsync(), including callback uri obtained at runtime: WebAuthenticationBroker.GetCurrentApplicationCallbackUri()
Go through authorize process, hit Allow.
Instead of returning, the broker shows the page Can't connect to service. We can't connect to the service you need right now. Unable to do anything, so I hit the Back button visible.
Debugger breaks on catch: "The specified protocol is unknown. (Exception from HRESULT: 0x800C000D)"
The callback for WebAuthenticationBroker.AuthenticateAsync() is received (according to Fiddler4 & the Event Viewer) but it throws the aforementioned exception as if it doesn't know how to interpret the ms-app:// protocol.
All examples imply my code should work but I think there's something less obvious causing an issue.
Code
private static string authorizeString =
"https://api.imgur.com/oauth2/authorize?client_id=---------&response_type=token";
private Uri startUri = new Uri(authorizeString);
public async void RequestToken() {
try {
var war = await WebAuthenticationBroker.AuthenticateAsync(
WebAuthenticationOptions.UseTitle
, startUri);
// Imgur knows my redirect URI, so I am not passing it through here
if (war.ResponseStatus == WebAuthenticationStatus.Success) {
var token = war.ResponseData;
}
} catch (Exception e) { throw e; }
}
Event Viewer log excerpts (chronological order)
For information on how I obtained this, read the following MSDN: Web authentication problems (Windows). Unfortunately this is the only search result when querying authhost.exe navigation error.
Information: AuthHost redirected to URL: <ms-app://s-1-15-2-504558873-2277781482-774653033-676865894-877042302-1411577334-1137525427/#access_token=------&expires_in=3600&token_type=bearer&refresh_token=------&account_username=------> from URL: <https://api.imgur.com/oauth2/authorize?client_id=------&response_type=token> with HttpStatusCode: 302.
Error: AuthHost encountered a navigation error at URL: <https://api.imgur.com/oauth2/authorize?client_id=------&response_type=token> with StatusCode: 0x800C000D.
Information: AuthHost encountered Meta Tag: mswebdialog-title with content: <Can't connect to the service>.
Thanks for reading, Stack. Don't fail me now!
Afaik, you need to pass the end URL to AuthenticateAsync even if you assume that the remote service knows it.
The way WebAuthenticationBroker works is like the following: you specify an "endpoint" URL and when it encounters a link that starts with this URL, it will consider the authentication process complete and doesn't even try navigating to this URL anymore.
So if you specify "foo://bar" as callback URI, navigating to "foo://bar" will finish the authentication, as will "foo://barbaz", but not "foo://baz".
Resolved! #ma_il helped me understand how the broker actually evaluates the redirect callback and it led me back to square one where I realized I assumed WebAuthenticationOptions.UseTitle was the proper usage. Not so. Up against Imgur's API using a token, it requires WebAuthenticationOptions.None and it worked immediately.
As an example to future answer-seekers, here's my code.
private const string clientId = "---------";
private static Uri endUri = WebAuthenticationBroker.GetCurrentApplicationCallbackUri();
private static string authorizeString = "https://api.imgur.com/oauth2/authorize?"
+ "client_id="
+ clientId
+ "&response_type=token"
+ "&state=somestateyouwant"
+ "&redirect_uri="
+ endUri;
private Uri startUri = new Uri(authorizeString);
public async void RequestToken() {
try {
WebAuthenticationResult webAuthenticationResult =
await WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.None
, startUri
, endUri);
if (webAuthenticationResult.ResponseStatus == WebAuthenticationStatus.Success) {
string token = webAuthenticationResult.ResponseData;
// now you have the token
}
} catch { throw; }
}