Skip to content

Commit

Permalink
AuthTab project example
Browse files Browse the repository at this point in the history
  • Loading branch information
erikrodriguez-se committed Dec 3, 2024
1 parent 653bfd2 commit 843e9c7
Show file tree
Hide file tree
Showing 51 changed files with 751 additions and 4 deletions.
1 change: 1 addition & 0 deletions androidbrowserhelper/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ apply plugin: 'maven-publish'
def VERSION = "2.5.0";

android {
namespace "com.google.androidbrowserhelper"
compileSdkVersion 31

defaultConfig {
Expand Down
8 changes: 7 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,22 @@ buildscript {
repositories {
google()
jcenter()
maven {
url = uri("https://androidx.dev/snapshots/builds/12502757/artifacts/repository")
}
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.1'
classpath 'com.android.tools.build:gradle:8.7.2'
}
}

allprojects {
repositories {
google()
jcenter()
maven {
url = uri("https://androidx.dev/snapshots/builds/12502757/artifacts/repository")
}
}
}

Expand Down
1 change: 1 addition & 0 deletions demos/custom-tabs-authview/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
51 changes: 51 additions & 0 deletions demos/custom-tabs-authview/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

apply plugin: 'com.android.application'

android {
namespace 'com.google.androidbrowserhelper.demos.customtabsauthview'
compileSdkVersion 35
defaultConfig {
applicationId "com.google.androidbrowserhelper.demos.customtabsauthview"
minSdkVersion 26
targetSdkVersion 35
versionCode 1
versionName "1.0"
}

buildTypes {
release {
minifyEnabled false
}
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
}

dependencies {
implementation project(path: ':androidbrowserhelper')
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'androidx.activity:activity:1.9.3'
implementation 'androidx.browser:browser:1.9.0-SNAPSHOT'
implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.annotation:annotation:1.9.1'
implementation 'androidx.constraintlayout:constraintlayout:2.2.0'
}
35 changes: 35 additions & 0 deletions demos/custom-tabs-authview/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.google.androidbrowserhelper.demos.customtabsauthview">
<uses-permission android:name="android.permission.INTERNET" />

<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AuthTab"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

<!-- An intent-filter for fallback to Chrome Custom Tabs -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="auth" />
<data android:host="callback" />
</intent-filter>
</activity>
</application>
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package com.google.androidbrowserhelper.demos.customtabsauthview;

import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;

import androidx.activity.result.ActivityResultLauncher;
import androidx.annotation.NonNull;
import androidx.browser.auth.AuthTabIntent;
import androidx.annotation.OptIn;
import androidx.browser.auth.ExperimentalAuthTab;

import java.io.IOException;
import java.util.UUID;

@OptIn(markerClass = ExperimentalAuthTab.class)
public class AuthManager {
private static final String TAG = "OAuthManager";

private final String mClientId;
private final String mClientSecret;
private final String mAuthorizationEndpoint;
private final String mRedirectScheme;

public interface OAuthCallback {
void auth(String accessToken, String scope, String tokenType);
}

public AuthManager(String clientId, String clientSecret, String authorizationEndpoint,
String redirectScheme) {
mClientId = clientId;
mClientSecret = clientSecret;
mAuthorizationEndpoint = authorizationEndpoint;
mRedirectScheme = redirectScheme;
}

public void authorize(Context context, ActivityResultLauncher<Intent> launcher, String scope) {
// Generate a random state.
String state = UUID.randomUUID().toString();

// Save the state so we can verify later.
SharedPreferences preferences =
context.getSharedPreferences("OAUTH_STORAGE", Context.MODE_PRIVATE);
preferences.edit()
.putString("OAUTH_STATE", state)
.apply();

// Create an authorization URI to the OAuth Endpoint.
Uri uri = Uri.parse(mAuthorizationEndpoint)
.buildUpon()
.appendQueryParameter("response_type", "code")
.appendQueryParameter("client_id", mClientId)
.appendQueryParameter("scope", scope)
.appendQueryParameter("state", state)
.build();

// Open the Authorization URI in a Chrome Custom Auth Tab.
AuthTabIntent authTabIntent = new AuthTabIntent.Builder().build();
authTabIntent.launch(launcher, uri, mRedirectScheme);
}

public void continueAuthFlow(@NonNull Context context, Uri uri, @NonNull OAuthCallback callback) {
String code = uri.getQueryParameter("code");
SharedPreferences preferences =
context.getSharedPreferences("OAUTH_STORAGE", Context.MODE_PRIVATE);
String state = preferences.getString("OAUTH_STATE", "");
Uri tokenUri = Uri.parse("https://github.com/login/oauth/access_token")
.buildUpon()
.appendQueryParameter("client_id", mClientId)
.appendQueryParameter("client_secret", mClientSecret)
.appendQueryParameter("code", code)
.appendQueryParameter("state", state)
.build();

// Run the network request off the UI thread.
new Thread(() -> {
try {
String response = Utils.fetch(tokenUri);
// The response is a query-string. We concatenate with a valid domain to be
// able to easily parse and extract values.
Uri responseUri = Uri.parse("http://example.com?" + response);
String accessToken = responseUri.getQueryParameter("access_token");
String tokenType = responseUri.getQueryParameter("token_type");
String scope = responseUri.getQueryParameter("scope");

// Invoke the callback in the main thread.
new Handler(Looper.getMainLooper()).post(
() -> callback.auth(accessToken, scope, tokenType));

} catch (IOException e) {
Log.e(TAG, "Error requesting access token: " + e.getMessage());
}
}).start();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.google.androidbrowserhelper.demos.customtabsauthview;

import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.util.Collections;
import java.util.Map;

public class GithubApi {
private static final String TAG = "GithubAPI";
private static final String API_ENDPOINT = "https://api.github.com/user";
private static final String AUTH_HEADER_KEY = "Authorization";

public interface UserCallback {
void onUserData(String username);
}

public static void requestGithubUsername(String token, UserCallback callback) {
new Thread(() -> {
try {
Uri uri = Uri.parse(API_ENDPOINT);
Map<String, String> headers =
Collections.singletonMap(AUTH_HEADER_KEY, "token " + token);
String response = Utils.fetch(uri, headers);
JSONObject user = new JSONObject(response);
String username = user.getString("name");

// Invoke the callback in the main thread.
new Handler(Looper.getMainLooper()).post(() -> {
callback.onUserData(username);
});
} catch (IOException | JSONException ex) {
Log.e(TAG, "Error fetching GitHub user: " + ex.getMessage());
}
}).start();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.androidbrowserhelper.demos.customtabsauthview;

import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;

import androidx.activity.result.ActivityResultLauncher;
import androidx.annotation.OptIn;
import androidx.appcompat.app.AppCompatActivity;
import androidx.browser.auth.AuthTabIntent;
import androidx.browser.auth.ExperimentalAuthTab;

@OptIn(markerClass = ExperimentalAuthTab.class)
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";

private static final String AUTHORIZATION_ENDPOINT = "https://github.com/login/oauth/authorize";
private static final String CLIENT_ID = "<github-client-id>";
private static final String CLIENT_SECRET = "<github-client-secret>";
private static final String REDIRECT_SCHEME = "auth";

private static final AuthManager O_AUTH_MANAGER =
new AuthManager(CLIENT_ID, CLIENT_SECRET, AUTHORIZATION_ENDPOINT, REDIRECT_SCHEME);

private final ActivityResultLauncher<Intent> mLauncher =
AuthTabIntent.registerActivityResultLauncher(this, this::handleAuthResult);

private Button mLoginButton;
private TextView mUserText;
private ProgressBar mProgressBar;
private boolean mLoggedIn;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

mLoginButton = findViewById(R.id.login_button);
mUserText = findViewById(R.id.user_text);
mProgressBar = findViewById(R.id.progress_bar);

Intent intent = getIntent();
if (intent != null) {
Uri data = intent.getData();
if (data != null && data.getHost() != null
&& data.getHost().startsWith("callback")) {
mProgressBar.setVisibility(View.VISIBLE);
mLoginButton.setEnabled(false);
completeAuth(data);
}
}
}

public void login(View v) {
if (mLoggedIn) {
mLoginButton.setText(R.string.login);
mUserText.setText(R.string.logged_out);
mLoggedIn = false;
} else {
O_AUTH_MANAGER.authorize(this, mLauncher, "user");
}
}

private void handleAuthResult(AuthTabIntent.AuthResult result) {
if (result.resultCode == AuthTabIntent.RESULT_OK) {
completeAuth(result.resultUri);
}
}

private void completeAuth(Uri uri) {
O_AUTH_MANAGER.continueAuthFlow(this, uri, (accessToken, scope, tokenType) -> {
GithubApi.requestGithubUsername(accessToken, (username -> {
mLoginButton.setText(R.string.logout);
mLoginButton.setEnabled(true);
mProgressBar.setVisibility(View.INVISIBLE);
mUserText.setText(getString(R.string.logged_in, username));
mLoggedIn = true;
}));
});
}
}
Loading

0 comments on commit 843e9c7

Please sign in to comment.