11package com .codetrixstudio .capacitor .GoogleAuth ;
22
3+ import android .accounts .Account ;
4+ import android .accounts .AccountManager ;
5+ import android .accounts .AccountManagerFuture ;
36import android .content .Intent ;
7+ import android .os .Bundle ;
8+ import android .util .Log ;
9+
410import androidx .activity .result .ActivityResult ;
511
612import com .codetrixstudio .capacitor .GoogleAuth .capacitorgoogleauth .R ;
1824import com .google .android .gms .common .api .Scope ;
1925import com .google .android .gms .tasks .Task ;
2026
21- import org .json .JSONArray ;
2227import org .json .JSONException ;
28+ import org .json .JSONObject ;
29+
30+ import java .io .BufferedInputStream ;
31+ import java .io .BufferedReader ;
32+ import java .io .IOException ;
33+ import java .io .InputStream ;
34+ import java .io .InputStreamReader ;
35+ import java .net .HttpURLConnection ;
36+ import java .net .URL ;
37+ import java .util .concurrent .ExecutorService ;
38+ import java .util .concurrent .Executors ;
2339
2440@ CapacitorPlugin ()
2541public class GoogleAuth extends Plugin {
42+ private final static String VERIFY_TOKEN_URL = "https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=" ;
43+ private final static String FIELD_TOKEN_EXPIRES_IN = "expires_in" ;
44+ private final static String FIELD_ACCESS_TOKEN = "accessToken" ;
45+ private final static String FIELD_TOKEN_EXPIRES = "expires" ;
46+ public static final int KAssumeStaleTokenSec = 60 ;
47+
2648 private GoogleSignInClient googleSignInClient ;
2749
2850 @ Override
@@ -55,7 +77,6 @@ public void load() {
5577
5678 @ PluginMethod ()
5779 public void signIn (PluginCall call ) {
58- saveCall (call );
5980 Intent signInIntent = googleSignInClient .getSignInIntent ();
6081 startActivityForResult (call , signInIntent , "signInResult" );
6182 }
@@ -69,22 +90,36 @@ protected void signInResult(PluginCall call, ActivityResult result) {
6990 try {
7091 GoogleSignInAccount account = completedTask .getResult (ApiException .class );
7192
72- JSObject authentication = new JSObject ();
73- authentication .put ("idToken" , account .getIdToken ());
74-
75- JSObject user = new JSObject ();
76- user .put ("serverAuthCode" , account .getServerAuthCode ());
77- user .put ("idToken" , account .getIdToken ());
78- user .put ("authentication" , authentication );
79-
80- user .put ("displayName" , account .getDisplayName ());
81- user .put ("email" , account .getEmail ());
82- user .put ("familyName" , account .getFamilyName ());
83- user .put ("givenName" , account .getGivenName ());
84- user .put ("id" , account .getId ());
85- user .put ("imageUrl" , account .getPhotoUrl ());
86-
87- call .resolve (user );
93+ // The accessToken is retrieved by executing a network request against the Google API, so it needs to run in a thread
94+ ExecutorService executor = Executors .newSingleThreadExecutor ();
95+ executor .execute (() -> {
96+ try {
97+ JSONObject accessTokenObject = getAuthToken (account .getAccount (), true );
98+
99+ JSObject authentication = new JSObject ();
100+ authentication .put ("idToken" , account .getIdToken ());
101+ authentication .put (FIELD_ACCESS_TOKEN , accessTokenObject .get (FIELD_ACCESS_TOKEN ));
102+ authentication .put (FIELD_TOKEN_EXPIRES , accessTokenObject .get (FIELD_TOKEN_EXPIRES ));
103+ authentication .put (FIELD_TOKEN_EXPIRES_IN , accessTokenObject .get (FIELD_TOKEN_EXPIRES_IN ));
104+
105+ JSObject user = new JSObject ();
106+ user .put ("serverAuthCode" , account .getServerAuthCode ());
107+ user .put ("idToken" , account .getIdToken ());
108+ user .put ("authentication" , authentication );
109+
110+ user .put ("displayName" , account .getDisplayName ());
111+ user .put ("email" , account .getEmail ());
112+ user .put ("familyName" , account .getFamilyName ());
113+ user .put ("givenName" , account .getGivenName ());
114+ user .put ("id" , account .getId ());
115+ user .put ("imageUrl" , account .getPhotoUrl ());
116+
117+ call .resolve (user );
118+ } catch (Exception e ) {
119+ e .printStackTrace ();
120+ call .reject ("Something went wrong while retrieving access token" , e );
121+ }
122+ });
88123 } catch (ApiException e ) {
89124 call .reject ("Something went wrong" , e );
90125 }
@@ -106,4 +141,61 @@ public void initialize(final PluginCall call) {
106141 call .resolve ();
107142 }
108143
144+ // Logic to retrieve accessToken, see https://github.com/EddyVerbruggen/cordova-plugin-googleplus/blob/master/src/android/GooglePlus.java
145+ private JSONObject getAuthToken (Account account , boolean retry ) throws Exception {
146+ AccountManager manager = AccountManager .get (getContext ());
147+ AccountManagerFuture <Bundle > future = manager .getAuthToken (account , "oauth2:profile email" , null , false , null , null );
148+ Bundle bundle = future .getResult ();
149+ String authToken = bundle .getString (AccountManager .KEY_AUTHTOKEN );
150+ try {
151+ return verifyToken (authToken );
152+ } catch (IOException e ) {
153+ if (retry ) {
154+ manager .invalidateAuthToken ("com.google" , authToken );
155+ return getAuthToken (account , false );
156+ } else {
157+ throw e ;
158+ }
159+ }
160+ }
161+
162+ private JSONObject verifyToken (String authToken ) throws IOException , JSONException {
163+ URL url = new URL (VERIFY_TOKEN_URL + authToken );
164+ HttpURLConnection urlConnection = (HttpURLConnection ) url .openConnection ();
165+ urlConnection .setInstanceFollowRedirects (true );
166+ String stringResponse = fromStream (new BufferedInputStream (urlConnection .getInputStream ()));
167+ /* expecting:
168+ {
169+ "issued_to": "xxxxxx-xxxxxxxxxxxxxxx.apps.googleusercontent.com",
170+ "audience": "xxxxxx-xxxxxxxxxxxxxxxx.apps.googleusercontent.com",
171+ "user_id": "xxxxxxxxxxxxxxxxxxxx",
172+ "scope": "https://www.googleapis.com/auth/userinfo.email openid https://www.googleapis.com/auth/userinfo.profile",
173+ "expires_in": 3220,
174+ 175+ "verified_email": true,
176+ "access_type": "online"
177+ }
178+ */
179+
180+ Log .d ("AuthenticatedBackend" , "token: " + authToken + ", verification: " + stringResponse );
181+ JSONObject jsonResponse = new JSONObject (stringResponse );
182+ int expires_in = jsonResponse .getInt (FIELD_TOKEN_EXPIRES_IN );
183+ if (expires_in < KAssumeStaleTokenSec ) {
184+ throw new IOException ("Auth token soon expiring." );
185+ }
186+ jsonResponse .put (FIELD_ACCESS_TOKEN , authToken );
187+ jsonResponse .put (FIELD_TOKEN_EXPIRES , expires_in + (System .currentTimeMillis () / 1000 ));
188+ return jsonResponse ;
189+ }
190+
191+ private static String fromStream (InputStream is ) throws IOException {
192+ BufferedReader reader = new BufferedReader (new InputStreamReader (is ));
193+ StringBuilder sb = new StringBuilder ();
194+ String line ;
195+ while ((line = reader .readLine ()) != null ) {
196+ sb .append (line ).append ("\n " );
197+ }
198+ reader .close ();
199+ return sb .toString ();
200+ }
109201}
0 commit comments