AppAuth oidc invalid State error when authenticating against identity server - android

I've configured an identity server 3 as an IdP for a project, we have 3 clients: MVC web, IOS and Android.
everything is good for MVC app, using Hybrid flow.
for IOS and Android, using the native oidc client (AppAuth IOS and AppAuth android) is not working, even though I configured the flow as Hybrid with PKCE.
now when I try to make a POC on android using Xamarin, and using IdentityModel.oidcClient everything works as expected, getting access, refresh and id tokens.
when using the AppAuth for IOS and android I am getting the following error:
{"type":0,"code":9,"errorDescription":"Response state param did not match request state"}
any idea what is missing ?
I am suspecting that those two native oidc clients aren't asking for shared secret of the clients, so the flow is corrupted because of that.

The data should be the same in the mobile app and in the Identity Server,
On The Server :
new Client
{
ClientId = "myClientId",
ClientName = "myClientName",
AllowedGrantTypes = GrantTypes.CodeAndClientCredentials,
RequireConsent = false,
ClientSecrets =
{
new Secret("myClientSecret".Sha256())
},
RedirectUris = { "myRedirectUri://callback" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
IdentityServerConstants.StandardScopes.Phone,
},
AllowOfflineAccess = true
}
On click login in android :
AuthManager authManager = AuthManager.getInstance(this);
AuthorizationService authService = authManager.getAuthService();
Auth auth = authManager.getAuth();
AuthorizationRequest authRequest = new AuthorizationRequest
.Builder(
authManager.getAuthConfig(),
auth.getClientId(),
auth.getResponseType(),
Uri.parse(auth.getRedirectUri()))
.setScope(auth.getScope())
.build();
Intent authIntent = new Intent(this, LoginAuthActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, authRequest.hashCode(), authIntent, 0);
authService.performAuthorizationRequest(
authRequest,
pendingIntent);
Ask for the token :
final AuthorizationResponse resp = AuthorizationResponse.fromIntent(getIntent());
AuthorizationException ex = AuthorizationException.fromIntent(getIntent());
final AuthManager authManager = AuthManager.getInstance(this);
authManager.setAuthState(resp,ex);
if (resp != null) {
ClientSecretPost clientSecretPost = new ClientSecretPost(authManager.getAuth().getClientSecret());
TokenRequest tokenRequest = new TokenRequest
.Builder(authManager.getAuthConfig(), authManager.getAuth().getClientId())
.setAuthorizationCode(resp.authorizationCode)
.setRedirectUri(Uri.parse(authManager.getAuth().getRedirectUri()))
.build();
mAuthService = authManager.getAuthService();
mAuthService.performTokenRequest(tokenRequest, clientSecretPost, new AuthorizationService.TokenResponseCallback() {
#Override public void onTokenRequestCompleted(#Nullable TokenResponse response, #Nullable AuthorizationException ex) {
if(ex == null) {
authManager.updateAuthState(response,ex);
MyApp.Token = authManager.getAuthState().getIdToken();
startService(new Intent(LoginAuthActivity.this, TokenService.class));
Intent mainIntent = new Intent(LoginAuthActivity.this, MainActivity.class);
startActivity(mainIntent);
finish();
}
else{
Intent loginIntent = new Intent(LoginAuthActivity.this, LoginActivity.class);
startActivity(loginIntent);
finish();
}
}
});
// authorization completed
} else {
// authorization failed, check ex for more details
Intent loginIntent = new Intent(LoginAuthActivity.this, LoginActivity.class);
startActivity(loginIntent);
finish();
}
The AuthManager Class :
public class AuthManager {
private static AuthManager instance;
private AuthState mAuthState;
private Auth mAuth;
private AuthorizationServiceConfiguration mAuthConfig;
private SharedPreferencesRepository mSharedPrefRep;
private AuthorizationService mAuthService;
public static AuthManager getInstance(Context context) {
if (instance == null) {
instance = new AuthManager(context);
}
return instance;
}
private AuthManager(Context context){
mSharedPrefRep = new SharedPreferencesRepository(context);
setAuthData();
mAuthConfig = new AuthorizationServiceConfiguration(
Uri.parse(mAuth.getAuthorizationEndpointUri()),
Uri.parse(mAuth.getTokenEndpointUri()),
null);
mAuthState = mSharedPrefRep.getAuthState();
mAuthService = new AuthorizationService(context);
}
public AuthorizationServiceConfiguration getAuthConfig() {
return mAuthConfig;
}
public Auth getAuth() {
if(mAuth == null){
setAuthData();
}
return mAuth;
}
public AuthState getAuthState(){
return mAuthState;
}
public void updateAuthState(TokenResponse response, AuthorizationException ex){
mAuthState.update(response,ex);
mSharedPrefRep.saveAuthState(mAuthState);
}
public void setAuthState(AuthorizationResponse response, AuthorizationException ex){
if(mAuthState == null)
mAuthState = new AuthState(response,ex);
mSharedPrefRep.saveAuthState(mAuthState);
}
public AuthorizationService getAuthService(){
return mAuthService;
}
private void setAuthData(){
mAuth = new Auth();
mAuth.setClientId(BuildConfig.CLIENT_ID);
mAuth.setAuthorizationEndpointUri(BuildConfig.AUTHORIZSTION_END_POINT_URI);
mAuth.setClientSecret(BuildConfig.CLIENT_SECRET);
mAuth.setRedirectUri(BuildConfig.REDIRECT_URI);
mAuth.setScope(BuildConfig.SCOPE);
mAuth.setTokenEndpointUri(BuildConfig.TOKEN_END_POINT_URI);
mAuth.setResponseType(BuildConfig.RESPONSE_TYPE);
}
}
The Service here will ask for the refresh token.
I have made a sample using Identity Server 4 with AppAuth-Android you can check it here

We have an open issue for hybrid flow support on AppAuth-Android here. The main issue with this is that the hybrid flow is a poor fit for mobile applications, as it would require repeatedly triggering a web flow via SafariViewController / CustomTab every time the access token expires. Acquiring a refresh token to allow background update of access tokens is better for native apps.
As IdentityServer3 is a certified OpenID Connect implementation, you should be able to use the authorization code flow to obtain a refresh token.

Related

Error while processing request on Azure

I have an app connected with Azure backend. I created a login and some api calls 2 months ago. They worked fine until a few days ago and then it starts to fail "sometimes".
The login log onFailure says: Error while authenticating user
The callback log onFailure says: Error while processing request
And the cause of both says : stream was reset: PROTOCOL_ERROR
This post is to similar to this but didn't work.
Some code here:
LoginFragment.java
private void login(String email, String password){
loginProgressBar.setVisibility(View.VISIBLE);
try {
JsonObject params = new JsonObject();
params.addProperty("Username", email);
params.addProperty("Password", password);
ListenableFuture<MobileServiceUser> listenable = Client.logIn(getContext(), params);
Futures.addCallback(listenable, new FutureCallback<MobileServiceUser>() {
#Override
public void onSuccess(MobileServiceUser mobileServiceUser) {
loginProgressBar.setVisibility(View.GONE);
SharedPreferences settings = getActivity().getSharedPreferences(Client.MS_USER,0);
SharedPreferences.Editor editor = settings.edit();
Client.clientId = mobileServiceUser.getUserId();
Client.token = mobileServiceUser.getAuthenticationToken();
editor.putString(Client.MS_USER_ID, Client.clientId);
editor.putString(Client.MS_AUTH_TOKEN, Client.token);
editor.apply();
Client.getInstance(getContext()).setCurrentUser(mobileServiceUser);
Intent i = new Intent(getContext(), MainActivity.class);
startActivity(i);
}
#Override
public void onFailure(Throwable t) {
loginProgressBar.setVisibility(View.GONE);
Throwable t2 = t.getCause();
Throwable t3 = t2.getCause();
Log.e("LoginFail", t.getMessage());
Log.e("LoginFail", t2.getMessage());
if(t3 != null){
Log.e("LoginFail", t3.getMessage());
}
Toast.makeText(getContext(), getResources().getString(R.string.bad_login), Toast.LENGTH_LONG).show();
}
}, MoreExecutors.directExecutor());
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
Client.java
public class Client {
public static final String MS_USER = "MS_USER";
public static final String MS_USER_ID = "MS_USER_ID";
public static final String MS_AUTH_TOKEN = "MS_AUTH_TOKEN";
public static String clientId;
public static String token;
private static MobileServiceClient instance = null;
public static MobileServiceClient getInstance(Context context) {
if (instance ==null){
try {
instance = new MobileServiceClient(Env.AZURE_URL, context);
instance.setAndroidHttpClientFactory(() -> {
OkHttpClient client = new OkHttpClient();
client.setReadTimeout(20, TimeUnit.SECONDS);
client.setWriteTimeout(20, TimeUnit.SECONDS);
return client;
});
} catch (MalformedURLException e) {
e.printStackTrace();
}
} else{
instance.setContext(context);
}
return instance;
}
public static ListenableFuture<MobileServiceUser> logIn(Context context, JsonObject parameters) throws MalformedURLException {
String deviceID = "gcm:" + Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
parameters.addProperty("device_id", deviceID);
parameters.addProperty("device_dateTime", Env.DATE_FORMAT.format(new Date()));
parameters.addProperty("device_timeZone", API.getTimezone());
parameters.addProperty("device_language", Env.LANGUAGE);
parameters.addProperty("app", Env.APP_NAME);
return getInstance(context).login("auth", parameters);
}
public static ListenableFuture<JsonElement> callApi(Context context, String apiName, JsonObject parameters, String httpMethod){
if(httpMethod.equals("POST")){
String deviceID = "gcm:" + Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
parameters.addProperty("user_id", Client.clientId);
parameters.addProperty("device_id", deviceID);
parameters.addProperty("device_dateTime", Env.DATE_FORMAT.format(new Date()));
parameters.addProperty("device_timeZone", API.getTimezone());
parameters.addProperty("device_language", Env.LANGUAGE);
parameters.addProperty("app", Env.APP_NAME);
parameters.addProperty("role", "Patient");
return getInstance(context).invokeApi(apiName, parameters, httpMethod, null);
} else {
return getInstance(context).invokeApi(apiName, null, httpMethod, null);
}
}
This is probably related to an issue in Azure App Service that is weirdly enough not reported on the public Azure status page.
The message that affected Azure client received was (quoted from the link above):
Starting at 02:00 UTC on 3 Apr 2018, you have been identified as a
customer using App Services who may have received connection failure
notifications when using Android apps with older HTTP clients or
desktop browsers using cross-site scripting calls. Engineers have
identified an issue with a recent deployment and are investigating
mitigation options. Customers experiencing this issue can
self-mitigate by updating the site config setting "http20Enabled" to
false via resources.azure.com. Instructions on how to update site
config can be found here:
https://azure.microsoft.com/en-us/blog/azure-resource-explorer-a-new-tool-to-discover-the-azure-api/
Go to https://resources.azure.com/
Make sure you are in Read/Write mode by clicking in the option to the
left of your name.
Find the affected site and browse to Config > Web:
https://resources.azure.com/subscriptions//resourceGroups//providers/Microsoft.Web/sites//config/web
Change the property: "http20Enabled": from true to false by clicking
in Edit properties, Update to “false” and then clicking PUT to save
change.
If you have tried these steps and are continuing to experience issues
with your App Service, please create a technical support ticket to
further troubleshoot: aka.ms/azsupt. This message will be closed in 7
days.

How to configure OAuth2 connection in Android?

I need to connect my app to a secure back-end via OAuth2 server. The main problem is that I can't get a token. I have the necessary parameters like clientID, secret and go on. But I need to make a POST request. I decided to use OkHttp OAuth2 client (library' GitHub). It doesn't work and I'm not sure it makes a POST. So I wrote simple a OkHttp request and put all stuff to post method. But I still get token=null.
It looks like this:
OkHttpClient client1 = new OkHttpClient();
RequestBody formBody = new FormBody.Builder()
.add("client_id", clientID)
.add("client_secret", clientSecret)
.add("grant_type", "password")
.build();
Request request = new Request.Builder()
.url(site)
.post(formBody)
.build();
And then I expect to see the token as a part of response. Other version is like at a guide at library' github. Maybe someone worked with this or can tell a better solution?
I had to integrate OAuth 2.0 in a project a while ago to use Google contacts. I used this lib: https://github.com/openid/AppAuth-Android
You can also see some important documentation in here:
https://developers.google.com/identity/protocols/OAuth2
And an example:
Fragment.java:
AuthorizationServiceConfiguration serviceConfiguration =
new AuthorizationServiceConfiguration(
Uri.parse(GoogleConstants.OAUTH_URL) /* auth endpoint */,
Uri.parse(GoogleConstants.TOKEN_URL) /* token endpoint */,
null
);
AuthorizationRequest.Builder authRequestBuilder = new AuthorizationRequest.Builder(
serviceConfiguration,
getString(GoogleConstants.CLIENT_ID),
ResponseTypeValues.CODE,
Uri.parse(GoogleConstants.REDIRECT_URI))
.setScope(GoogleConstants.OAUTH_SCOPE);
AuthorizationRequest request = authRequestBuilder.build();
AuthorizationService authorizationService = new AuthorizationService(getActivity());
String action = GoogleConstants.APP_ACTION;
Intent postAuthorizationIntent = new Intent(getActivity(), ExampleActivity.class);
postAuthorizationIntent.setAction(action);
PendingIntent pendingIntent =
PendingIntent.getActivity(getActivity(), 0, postAuthorizationIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
authorizationService.performAuthorizationRequest(request, pendingIntent);
Activity.java:
#Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
checkIntentAction(intent);
}
...
private void checkIntentAction(#Nullable Intent intent) {
if (intent != null) {
String action = intent.getAction();
if (action != null) {
switch (action) {
case GoogleConstants.APP_ACTION:
if (!intent.hasExtra(USED_INTENT)) {
handleAuthorizationResponse(intent);
intent.putExtra(USED_INTENT, true);
}
break;
default:
// do nothing
}
}
}
}
...
private void handleAuthorizationResponse(final #NonNull Intent intent) {
final AuthorizationResponse response = AuthorizationResponse.fromIntent(intent);
if (response != null) {
final AuthorizationService service = new AuthorizationService(this);
service.performTokenRequest(response.createTokenExchangeRequest(),
new AuthorizationService.TokenResponseCallback() {
#Override
public void onTokenRequestCompleted(#Nullable TokenResponse tokenResponse,
#Nullable AuthorizationException exception) {
...
}
});
}
}

Using cached Cognito identity from Xamarin

When I first log into my app, I go through the following code:
auth = new Xamarin.Auth.OAuth2Authenticator(
"my-google-client-id.apps.googleusercontent.com",
string.Empty,
"openid",
new System.Uri("https://accounts.google.com/o/oauth2/v2/auth"),
new System.Uri("com.enigmadream.storyvoque:/oauth2redirect"),
new System.Uri("https://www.googleapis.com/oauth2/v4/token"),
isUsingNativeUI: true);
auth.Completed += Auth_Completed;
StartActivity(auth.GetUI(this));
Which triggers this activity:
[Activity(Label = "GoodleAuthInterceptor")]
[IntentFilter(actions: new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable },
DataSchemes = new[] { "com.enigmadream.storyvoque" }, DataPaths = new[] { "/oauth2redirect" })]
public class GoodleAuthInterceptor : Activity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Android.Net.Uri uri_android = Intent.Data;
Uri uri_netfx = new Uri(uri_android.ToString());
MainActivity.auth?.OnPageLoading(uri_netfx);
Finish();
}
}
And finally this code to link the account to Cognito:
private void Auth_Completed(object sender, Xamarin.Auth.AuthenticatorCompletedEventArgs e)
{
if (e.IsAuthenticated)
{
var idToken = e.Account.Properties["id_token"];
credentials.AddLogin("accounts.google.com", idToken);
AmazonCognitoIdentityClient cli = new AmazonCognitoIdentityClient(credentials, RegionEndpoint.USEast2);
var req = new Amazon.CognitoIdentity.Model.GetIdRequest();
req.Logins.Add("accounts.google.com", idToken);
req.IdentityPoolId = "us-east-2:79ebf8e1-97de-4d1c-959a-xxxxxxxxxxxx";
cli.GetIdAsync(req).ContinueWith((task) =>
{
if ((task.Status == TaskStatus.RanToCompletion) && (task.Result != null))
{
ShowMessage(string.Format("Identity {0} retrieved", task.Result.IdentityId));
}
else
ShowMessage(task.Exception.InnerException != null ? task.Exception.InnerException.Message : task.Exception.Message);
});
}
else
ShowMessage("Login cancelled");
}
This all works great, and after the login, I am able to use my identity/credentials to retrieve data from DynamoDB. With this object:
Amazon.DynamoDBv2.AmazonDynamoDBClient ddbc = new Amazon.DynamoDBv2.AmazonDynamoDBClient(credentials, RegionEndpoint.USEast2);
The second time I run my app, this code runs:
if (!string.IsNullOrEmpty(credentials.GetCachedIdentityId()) || credentials.CurrentLoginProviders.Length > 0)
{
if (!bDidLogin)
{
var idToken = credentials.GetIdentityId();
ShowMessage(string.Format("I still remember you're {0} ", idToken));
And if I try to use the credentials with DynamoDB (or anything, I assume) at this point, I get errors that I don't have access to the identity. I have to logout (credentials.Clear()) and login again to obtain proper credentials.
I could require that a user go through the whole login process every time my app runs, but that's a real pain because the Google login process requires the user to know how to manually close the web browser to get back to the application after authenticating. Is there something I'm missing about the purpose and usage of cached credentials? When I use most apps, they aren't requiring me to log into my Google account every time and close a web browser just to access their server resources.
It looks like the refresh token needs to be submitted back to the OAuth2 provider to get an updated id token to add to the credentials object. First I added some code to save and load the refresh_token in a config.json file:
private Dictionary<string, string> config;
const string CONFIG_FILE = "config.json";
private void Auth_Completed(object sender, Xamarin.Auth.AuthenticatorCompletedEventArgs e)
{
if (e.IsAuthenticated)
{
var idToken = e.Account.Properties["id_token"];
if (e.Account.Properties.ContainsKey("refresh_token"))
{
if (config == null)
config = new Dictionary<string, string>();
config["refresh_token"] = e.Account.Properties["refresh_token"];
WriteConfig();
}
credentials.AddLogin("accounts.google.com", idToken);
CognitoLogin(idToken).ContinueWith((t) =>
{
try
{
t.Wait();
}
catch (Exception ex)
{
ShowMessage(ex.Message);
}
});
}
else
ShowMessage("Login cancelled");
}
void WriteConfig()
{
using (var configWriter = new System.IO.StreamWriter(
Application.OpenFileOutput(CONFIG_FILE, Android.Content.FileCreationMode.Private)))
{
configWriter.Write(ThirdParty.Json.LitJson.JsonMapper.ToJson(config));
configWriter.Close();
}
}
public void Login()
{
try
{
if (!string.IsNullOrEmpty(credentials.GetCachedIdentityId()) || credentials.CurrentLoginProviders.Length > 0)
{
if (!bDidLogin)
{
var idToken = credentials.GetIdentityId();
if (ReadConfig())
{
LoginRefreshAsync().ContinueWith((t) =>
{
try
{
t.Wait();
if (!t.Result)
FullLogin();
}
catch (Exception ex)
{
ShowMessage(ex.Message);
}
});
}
else
{
credentials.Clear();
FullLogin();
}
}
}
else
FullLogin();
bDidLogin = true;
}
catch(Exception ex)
{
ShowMessage(string.Format("Error logging in: {0}", ex.Message));
}
}
private bool ReadConfig()
{
bool bFound = false;
foreach (string filename in Application.FileList())
if (string.Compare(filename, CONFIG_FILE, true) == 0)
{
bFound = true;
break;
}
if (!bFound)
return false;
using (var configReader = new System.IO.StreamReader(Application.OpenFileInput(CONFIG_FILE)))
{
config = ThirdParty.Json.LitJson.JsonMapper.ToObject<Dictionary<string, string>>(configReader.ReadToEnd());
return true;
}
}
Then refactored the code that initiates the interactive login into a separate function:
public void FullLogin()
{
auth = new Xamarin.Auth.OAuth2Authenticator(CLIENTID_GOOGLE, string.Empty, "openid",
new Uri("https://accounts.google.com/o/oauth2/v2/auth"),
new Uri("com.enigmadream.storyvoque:/oauth2redirect"),
new Uri("https://accounts.google.com/o/oauth2/token"),
isUsingNativeUI: true);
auth.Completed += Auth_Completed;
StartActivity(auth.GetUI(this));
}
Refactored the code that retrieves a Cognito identity into its own function:
private async Task CognitoLogin(string idToken)
{
AmazonCognitoIdentityClient cli = new AmazonCognitoIdentityClient(credentials, RegionEndpoint.USEast2);
var req = new Amazon.CognitoIdentity.Model.GetIdRequest();
req.Logins.Add("accounts.google.com", idToken);
req.IdentityPoolId = ID_POOL;
try
{
var result = await cli.GetIdAsync(req);
ShowMessage(string.Format("Identity {0} retrieved", result.IdentityId));
}
catch (Exception ex)
{
ShowMessage(ex.Message);
}
}
And finally implemented a function that can retrieve a new token based on the refresh token, insert it into the current Cognito credentials, and get an updated Cognito identity.
private async Task<bool> LoginRefreshAsync()
{
string tokenUrl = "https://accounts.google.com/o/oauth2/token";
try
{
using (System.Net.Http.HttpClient client = new System.Net.Http.HttpClient())
{
string contentString = string.Format(
"client_id={0}&grant_type=refresh_token&refresh_token={1}&",
Uri.EscapeDataString(CLIENTID_GOOGLE),
Uri.EscapeDataString(config["refresh_token"]));
System.Net.Http.HttpContent content = new System.Net.Http.ByteArrayContent(
System.Text.Encoding.UTF8.GetBytes(contentString));
content.Headers.Add("content-type", "application/x-www-form-urlencoded");
System.Net.Http.HttpResponseMessage msg = await client.PostAsync(tokenUrl, content);
string result = await msg.Content.ReadAsStringAsync();
string idToken = System.Json.JsonValue.Parse(result)["id_token"];
credentials.AddLogin("accounts.google.com", idToken);
/* EDIT -- discovered this is not necessary! */
// await CognitoLogin(idToken);
return true;
}
}
catch (Exception ex)
{
ShowMessage(ex.Message);
return false;
}
}
I'm not sure if this is optimal or even correct, but it seems to work. I can use the resulting credentials to access DynamoDB without having to prompt the user for permission/credentials again.
There's a very different solution I'm trying to fit with the other answer. But it's so different, I'm adding it as a separate answer.
It appears the problem was not so much related to needing to explicitly use a refresh token to get an updated access token (I think this is done implicitly), but rather needing to remember the identity token. So rather than include all the complexity of manually applying a refresh token, all that's needed is to store the identity token (which can be done in a way similar to how the refresh token was being stored). Then we just need to add that same identity token back to the credentials object when it's missing.
if (!string.IsNullOrEmpty(credentials.GetCachedIdentityId()) || credentials.CurrentLoginProviders.Length > 0)
{
if (config.Read())
{
if (config["id_token"] != null)
credentials.AddLogin(currentProvider.Name, config["id_token"]);
Edit: The problem of needing to use a refresh token does still exist. This code works if the token hasn't expired, but attempting to use these credentials after the token has expired will fail, so there is still some need to use a refresh token somehow in some cases.

How can I provide multiple-user access using Google+ accounts on the Android app

I'm working on a multi-user Android application that provides to its users access to GMaps (find one another), chat and so on. Users should login to application using their accounts on Twitter, Facebook, Google+ etc. Everything works fine with all accounts except G+ - application can get access to G+ API only with its owner account. With other accounts I receive com.google.api.client.googleapis.json.GoogleJsonResponseException: 404 Not Found or "authorization error". App is registered on the API Console, and OAuth2.0 authentication used. I use standard authentication mechanism from Google sites. Is it possible to use different G+ accounts to login?
Here is my code (Android v1.6):
public class GooglePlusActivity extends Activity {
public static final String LOG_TAG = GooglePlusActivity.class.getSimpleName();
public static final String EXTRA_FIRSTNAME = "firstname";
public static final String EXTRA_LASTNAME = "lastname";
public static final String EXTRA_NICKNAME = "nickname";
public static final String EXTRA_SEX = "sex";
public static final String EXTRA_AVATAR = "avatar";
public static final String EXTRA_ID_SOCNET = "id_socnet";
private ApplicationSettings mSettings;
private Person mProfile;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mSettings = ((TomskApplication)getApplication()).getSettings();
signIn();
}
private void signIn() {
WebView webView = new WebView(this);
setContentView(webView);
webView.getSettings().setJavaScriptEnabled(false);
String googleAuthorizationRequestUrl = new GoogleAuthorizationRequestUrl(
mSettings.getGPID(), mSettings.getGPRedirectURI(),
mSettings.getGPScope()).build();
webView.setWebViewClient(new WebViewClient() {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url){
if (url.startsWith(mSettings.getGPRedirectURI())) {
try {
Intent res_intent = new Intent();
if (url.indexOf("code=") != -1) {
String code = url.substring(mSettings
.getGPRedirectURI().length() + 7, url
.length());
AccessTokenResponse token = new GoogleAuthorizationCodeGrant(
new NetHttpTransport(),
new JacksonFactory(), mSettings.getGPID(),
mSettings.getGPSecret(), code, mSettings
.getGPRedirectURI()).execute();
mSettings.setGPToken(token);
// Loading user data
retrieveProfile();
if (mProfile == null) {retrieveProfile();}
res_intent.putExtra(EXTRA_FIRSTNAME, mProfile
.getName().getGivenName());
res_intent.putExtra(EXTRA_LASTNAME, mProfile
.getName().getFamilyName());
res_intent.putExtra(EXTRA_NICKNAME,
mProfile.getNickname());
res_intent.putExtra(EXTRA_SEX, mProfile.getGender());
res_intent.putExtra(EXTRA_AVATAR, mProfile
.getImage().getUrl());
res_intent.putExtra(EXTRA_ID_SOCNET, mProfile.getId());
setResult(Activity.RESULT_OK, res_intent);
view.setVisibility(View.INVISIBLE);
finish();
} else if (url.indexOf("error=") != -1) {
view.setVisibility(View.INVISIBLE);
setResult(Activity.RESULT_CANCELED);
finish();
}
} catch (IOException e) {
Log.d(LOG_TAG, e.toString());
}
return true;
} else {
return false;
}
}
});
webView.loadUrl(googleAuthorizationRequestUrl);
}
/**
* Retrieve user profile
*/
private void retrieveProfile() throws IOException {
JsonFactory jsonFactory = new JacksonFactory();
HttpTransport transport = new NetHttpTransport();
AccessTokenResponse token = mSettings.getGPToken();
GoogleAccessProtectedResource accessProtectedResource = new GoogleAccessProtectedResource(
token.accessToken, transport, jsonFactory,
mSettings.getGPID(), mSettings.getGPSecret(),
token.refreshToken);
Builder b = Plus.builder(transport, jsonFactory)
.setApplicationName("MyApp/1.0");
b.setHttpRequestInitializer(accessProtectedResource);
Plus plus = b.build();
mProfile = plus.people().get("me").execute();
}
}
I've searched on Google sites, Stack Overflow but found nothing. Please help.
Dont know that this will help but, if you are desperate....
New release of some of the Android client side libs on 4/4/2012 here
and there is fresh Google+ sample, using some reconfigured classes in the main() method where they access protected resources. The new version in R 1.8 is different than your code , at least at the top of the stack.... IMO the use in the new example of the Credential class and of the PLUS.Builder is probably going to boil down to pretty much the same implementation that you already have. You may want to look at the newer sample if you cannot get anything else to work.
new code from googlePlus sample in 1.8
public static void main(String[] args) {
try {
try {
// authorization
Credential credential = OAuth2Native.authorize(
HTTP_TRANSPORT, JSON_FACTORY, new LocalServerReceiver(),
Arrays.asList(PlusScopes.PLUS_ME));
// set up global Plus instance
plus = Plus.builder(HTTP_TRANSPORT, JSON_FACTORY)
.setApplicationName("Google-PlusSample/1.0").setHttpRequestInitializer(credential)
.build();
older code here

Google Calendar API OAuth2 Troubles on Android Honeycomb

I am working on an Android Honeycomb (v3.0) application that has a requirement of communicating with the Google Calendar API. I would like to allow my application to access a particular Google account's Calendar data in order to read and create events.
Unfortunately, I ran into a problem with authorization using OAuth2. Here's what I have so far:
1) The Google account whose calendar I would like to access is registered within the Android device I am working with.
2) I enabled the Calendar API within the Google APIs Console on the account.
3) I am able to access this account using the following code:
AccountManager accountManager = AccountManager.get(this.getBaseContext());
Account[] accounts = accountManager.getAccountsByType("com.google");
Account acc = accounts[0]; // The device only has one account on it
4) I would now like to obtain an AuthToken for use when communicating with the calendar. I followed this tutorial, but converted everything to work with Google Calendar instead of Google Tasks. I successfully retrieve an authToken from the AccountManager with the account I would like to use by using getAuthToken with AUTH_TOKEN_TYPE == "oauth2:https://www.googleapis.com/auth/calendar".
5) Here's where the problems begin. I am now at this point:
AccessProtectedResource accessProtectedResource = new GoogleAccessProtectedResource(tokens[0]); // this is the correct token
HttpTransport transport = AndroidHttp.newCompatibleTransport();
Calendar service = Calendar.builder(transport, new JacksonFactory())
.setApplicationName("My Application's Name")
.setHttpRequestInitializer(accessProtectedResource)
.build();
service.setKey("myCalendarSimpleAPIAccessKey"); // This is deprecated???
Events events = service.events().list("primary").execute(); // Causes an exception!
6) Here's the exception returned by the last line:
com.google.api.client.googleapis.json.GoogleJsonResponseException: 403 Forbidden
{
"code" : 403,
"errors" : [ {
"domain" : "usageLimits",
"message" : "Daily Limit Exceeded. Please sign up",
"reason" : "dailyLimitExceededUnreg",
"extendedHelp" : "https://code.google.com/apis/console"
} ],
"message" : "Daily Limit Exceeded. Please sign up"
}
7) According to this Google API Video (wait a minute or so to get to the applicable content), a reason for this exception may be the fact that I did not enable the API access within the Google APIs Console for the account. However, if you look at 2), you can see that I did do so.
8) To me, it seems that the problem is that I was unable to set the Simple API Access Key correctly, because the Calendar.setKey method is deprecated. Within the Google Tasks tutorial that I previously linked, the key is set using Tasks.accessKey = "key". I'm not sure how to get this working with the Calendar API, though. I have tried multiple Google accounts, which all came up with the exception from 5).
9) I would like to point out that the traditional method of using OAuth2 did work for me. Here's the code I used for that:
HttpTransport TRANSPORT = new NetHttpTransport();
JsonFactory JSON_FACTORY = new JacksonFactory();
String SCOPE = "https://www.googleapis.com/auth/calendar";
String CALLBACK_URL = "urn:ietf:wg:oauth:2.0:oob";
String CLIENT_ID = "myClientID";
String CLIENT_SECRET = "myClientSecret";
String authorizeUrl = new GoogleAuthorizationRequestUrl(CLIENT_ID, CALLBACK_URL, SCOPE).build();
String authorizationCode = "???"; // At this point, I have to manually go to the authorizeUrl and grab the authorization code from there to paste it in here while in debug mode
GoogleAuthorizationCodeGrant authRequest = new GoogleAuthorizationCodeGrant(TRANSPORT, JSON_FACTORY, CLIENT_ID, CLIENT_SECRET, authorizationCode, CALLBACK_URL);
authRequest.useBasicAuthorization = false;
AccessTokenResponse authResponse = authRequest.execute();
String accessToken = authResponse.accessToken; // gets the correct token
GoogleAccessProtectedResource access = new GoogleAccessProtectedResource(accessToken, TRANSPORT, JSON_FACTORY, CLIENT_ID, CLIENT_SECRET, authResponse.refreshToken);
HttpRequestFactory rf = TRANSPORT.createRequestFactory(access);
AccessProtectedResource accessProtectedResource = new GoogleAccessProtectedResource(accessToken);
HttpTransport transport = AndroidHttp.newCompatibleTransport();
Calendar service = Calendar.builder(transport, new JacksonFactory())
.setApplicationName("My Application's Name")
.setHttpRequestInitializer(accessProtectedResource)
.build();
Events events = service.events().list("primary").execute(); // this works!
10) Finally, my question: I would like to use the account from the AccountManager on the device itself in order to retrieve a working OAuth2 token for use with the Google Calendar API. The second method is not useful for me, because the user will have to manually go to their web browser and get the authorization code, which is not user friendly. Anyone have any ideas? Apologies for the long post, and thanks!
Try adding a JsonHttpRequestInitializer to the builder and setting your key there:
Calendar service = Calendar.builder(transport, new JacksonFactory())
.setApplicationName("My Application's Name")
.setHttpRequestInitializer(accessProtectedResource)
.setJsonHttpRequestInitializer(new JsonHttpRequestInitializer() {
public void initialize(JsonHttpRequest request) {
CalendarRequest calRequest = (CalendarRequest) request;
calRequest.setKey("myCalendarSimpleAPIAccessKey");
}
}).build();
To answer no 10 : I've basically had to do what you had to do working with the TaskSample and then use the Android GData Calendar Sample available here : http://code.google.com/p/google-api-java-client/source/browse/calendar-android-sample/src/main/java/com/google/api/client/sample/calendar/android/CalendarSample.java?repo=samples
to get the AuthToken from the AccountManager itself:
accountManager = new GoogleAccountManager(this);
settings = this.getSharedPreferences(PREF, 0);
gotAccount();
private void gotAccount() {
Account account = accountManager.getAccountByName(accountName);
if (account != null) {
if (settings.getString(PREF_AUTH_TOKEN, null) == null) {
accountManager.manager.getAuthToken(account, AUTH_TOKEN_TYPE,
true, new AccountManagerCallback<Bundle>() {
#Override
public void run(AccountManagerFuture<Bundle> future) {
try {
Bundle bundle = future.getResult();
if (bundle
.containsKey(AccountManager.KEY_INTENT)) {
Intent intent = bundle
.getParcelable(AccountManager.KEY_INTENT);
int flags = intent.getFlags();
flags &= ~Intent.FLAG_ACTIVITY_NEW_TASK;
intent.setFlags(flags);
startActivityForResult(intent,
REQUEST_AUTHENTICATE);
} else if (bundle
.containsKey(AccountManager.KEY_AUTHTOKEN)) {
setAuthToken(bundle
.getString(AccountManager.KEY_AUTHTOKEN));
// executeRefreshCalendars();
}
} catch (Exception e) {
handleException(e);
}
}
}, null);
} else {
// executeRefreshCalendars();
}
return;
}
chooseAccount();
}
private void chooseAccount() {
accountManager.manager.getAuthTokenByFeatures(
GoogleAccountManager.ACCOUNT_TYPE, AUTH_TOKEN_TYPE, null,
ExportClockOption.this, null, null,
new AccountManagerCallback<Bundle>() {
#Override
public void run(AccountManagerFuture<Bundle> future) {
Bundle bundle;
try {
bundle = future.getResult();
setAccountName(bundle
.getString(AccountManager.KEY_ACCOUNT_NAME));
setAuthToken(bundle
.getString(AccountManager.KEY_AUTHTOKEN));
// executeRefreshCalendars();
} catch (OperationCanceledException e) {
// user canceled
} catch (AuthenticatorException e) {
handleException(e);
} catch (IOException e) {
handleException(e);
}
}
}, null);
}
void setAuthToken(String authToken) {
SharedPreferences.Editor editor = settings.edit();
editor.putString(PREF_AUTH_TOKEN, authToken);
editor.commit();
createCalendarService(authToken);
try {
Events events = service.events().list("primary").execute();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void createCalendarService(String authToken) {
accessProtectedResource = new GoogleAccessProtectedResource(authToken);
Log.i(TAG, "accessProtectedResource.getAccessToken() = "
+ accessProtectedResource.getAccessToken());
JacksonFactory jsonFactory = new JacksonFactory();
service = com.google.api.services.calendar.Calendar
.builder(transport, jsonFactory)
.setApplicationName("Time Journal")
.setJsonHttpRequestInitializer(
new JsonHttpRequestInitializer() {
#Override
public void initialize(JsonHttpRequest request) {
CalendarRequest calendarRequest = (CalendarRequest) request;
calendarRequest
.setKey("<YOUR SIMPLE API KEY>");
}
}).setHttpRequestInitializer(accessProtectedResource)
.build();
}

Categories

Resources