Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ public class MainActivity extends AppCompatActivity {
private View mChangePassword;
private View mDeleteAccount;
private View mFetchUserInfo;
private View mRefreshAccessToken;
private View mShowAuthTime;
private View mLogout;
private MainViewModel viewModel;
Expand Down Expand Up @@ -104,6 +105,7 @@ protected void onCreate(Bundle savedInstanceState) {
mChangePassword = findViewById(R.id.changePassword);
mDeleteAccount = findViewById(R.id.deleteAccount);
mFetchUserInfo = findViewById(R.id.fetchUserInfo);
mRefreshAccessToken = findViewById(R.id.refreshAccessToken);
mShowAuthTime = findViewById(R.id.showAuthTime);
mLogout = findViewById(R.id.logout);
mUseWebKitWebView = findViewById(R.id.useWebKitWebView);
Expand Down Expand Up @@ -167,6 +169,7 @@ protected void onCreate(Bundle savedInstanceState) {
mChangePassword.setOnClickListener(view -> viewModel.openChangePassword());
mDeleteAccount.setOnClickListener(view -> viewModel.openDeleteAccount());
mFetchUserInfo.setOnClickListener(view -> viewModel.fetchUserInfo());
mRefreshAccessToken.setOnClickListener(view -> viewModel.refreshAccessToken());
mShowAuthTime.setOnClickListener(view -> viewModel.showAuthTime(this));
mLogout.setOnClickListener(view -> viewModel.logout());

Expand Down Expand Up @@ -264,7 +267,10 @@ public void onNothingSelected(AdapterView<?> parent) {
mApp2AppstateField.setVisibility(app2appEnabled ? View.VISIBLE : View.GONE);
});

viewModel.sessionState().observe(this, sessionState -> mSessionState.setText(viewModel.sessionState().getValue().toString()));
viewModel.sessionState().observe(this, sessionState -> {
mSessionState.setText(viewModel.sessionState().getValue().toString());
updateButtonDisabledState(viewModel);
});

viewModel.isLoading().observe(this, isLoading -> updateButtonDisabledState(viewModel));
viewModel.isBiometricEnabled().observe(this, isEnabled -> updateButtonDisabledState(viewModel));
Expand All @@ -280,6 +286,16 @@ public void onNothingSelected(AdapterView<?> parent) {
builder.create().show();
});

viewModel.refreshAccessTokenResult().observe(this, message -> {
if (message == null) return;
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Refresh Access Token If Needed");
builder.setMessage(message);
builder.setPositiveButton("OK", (dialogInterface, i) -> {
});
builder.create().show();
});

viewModel.error().observe(this, e -> {
if (e == null) return;
AlertDialog.Builder builder = new AlertDialog.Builder(this);
Expand Down Expand Up @@ -358,6 +374,7 @@ private void updateButtonDisabledState(MainViewModel viewModel) {
mChangePassword.setEnabled(!isLoading && isConfigured && isLoggedIn);
mDeleteAccount.setEnabled(!isLoading && isConfigured && isLoggedIn);
mFetchUserInfo.setEnabled(!isLoading && isConfigured && isLoggedIn);
mRefreshAccessToken.setEnabled(!isLoading && isConfigured && isLoggedIn);
mShowAuthTime.setEnabled(!isLoading && isConfigured && isLoggedIn);
mLogout.setEnabled(!isLoading && isConfigured && isLoggedIn);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import com.oursky.authgear.CancelException;
import com.oursky.authgear.ColorScheme;
import com.oursky.authgear.CustomTabsUIImplementation;
import com.oursky.authgear.OAuthException;
import com.oursky.authgear.OnAuthenticateAnonymouslyListener;
import com.oursky.authgear.OnAuthenticateBiometricListener;
import com.oursky.authgear.OnAuthenticateListener;
Expand All @@ -43,6 +44,7 @@
import com.oursky.authgear.OnOpenURLListener;
import com.oursky.authgear.OnPromoteAnonymousUserListener;
import com.oursky.authgear.OnReauthenticateListener;
import com.oursky.authgear.OnRefreshAccessTokenIfNeededListener;
import com.oursky.authgear.OnRefreshIDTokenListener;
import com.oursky.authgear.OnWechatAuthCallbackListener;
import com.oursky.authgear.OpenAuthorizationURLOptions;
Expand Down Expand Up @@ -99,6 +101,7 @@ public class MainViewModel extends AndroidViewModel implements AuthgearDelegate
final private MutableLiveData<UserInfo> mUserInfo = new MutableLiveData<>(null);
final private MutableLiveData<SessionState> mSessionState = new MutableLiveData<>(SessionState.UNKNOWN);
final private MutableLiveData<Throwable> mError = new MutableLiveData<>(null);
final private MutableLiveData<String> mRefreshAccessTokenResult = new MutableLiveData<>(null);
private Intent pendingApp2AppIntent = null;
final private MutableLiveData<Boolean> mAuthgearConfigured = new MutableLiveData<>(false);
final private MutableLiveData<ConfirmationViewModel> mApp2AppConfirmation = new MutableLiveData<>(null);
Expand Down Expand Up @@ -239,6 +242,10 @@ public LiveData<UserInfo> userInfo() {
public LiveData<Throwable> error() {
return mError;
}

public LiveData<String> refreshAccessTokenResult() {
return mRefreshAccessTokenResult;
}
public LiveData<ConfirmationViewModel> app2appConfirmation() { return mApp2AppConfirmation; }

public void configure(
Expand Down Expand Up @@ -347,8 +354,16 @@ public void onWechatAuthCallbackFailed(Throwable throwable) {
}

@Override
public void onSessionStateChanged(Authgear container, SessionStateChangeReason reason) {
Log.d(TAG, "Session state=" + container.getSessionState() + " reason=" + reason);
public void onSessionStateChanged(Authgear container, SessionStateChangeReason reason, @Nullable Throwable error) {
Log.d(TAG, "Session state=" + container.getSessionState() + " reason=" + reason + " error=" + error);
if (error instanceof OAuthException) {
String oauthError = ((OAuthException) error).getError();
if ("invalid_grant".equals(oauthError)) {
Log.d(TAG, "onSessionStateChanged: error is invalid_grant");
} else if ("invalid_dpop_proof".equals(oauthError)) {
Log.d(TAG, "onSessionStateChanged: error is invalid_dpop_proof");
}
}
this.mSessionState.setValue(container.getSessionState());
}

Expand Down Expand Up @@ -847,6 +862,26 @@ public void onFetchingUserInfoFailed(@NonNull Throwable throwable) {
});
}

// Unlike fetchUserInfo(), this does NOT chain any follow-up request
// after refresh, so the refresh result (e.g. invalid_grant vs
// invalid_dpop_proof) is not masked by a subsequent request made with a
// stale/missing access token.
public void refreshAccessToken() {
mAuthgear.refreshAccessTokenIfNeeded(new OnRefreshAccessTokenIfNeededListener() {
@Override
public void onFinished() {
mRefreshAccessTokenResult.setValue(
"Refreshed access token successfully.\nsessionState: " + mAuthgear.getSessionState()
);
}

@Override
public void onFailed(Throwable throwable) {
setError(throwable);
}
});
}

public void showAuthTime(FragmentActivity activity) {
Date nullable = mAuthgear.getAuthTime();
if (nullable != null) {
Expand Down
9 changes: 9 additions & 0 deletions javasample/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,15 @@
android:text="Fetch user info"
tools:ignore="HardcodedText" />

<Button
android:id="@+id/refreshAccessToken"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_gravity="center_horizontal"
android:text="Refresh Access Token If Needed"
tools:ignore="HardcodedText" />

<Button
android:id="@+id/showAuthTime"
android:layout_width="wrap_content"
Expand Down
16 changes: 8 additions & 8 deletions sdk/src/main/java/com/oursky/authgear/AuthgearCore.kt
Original file line number Diff line number Diff line change
Expand Up @@ -564,12 +564,12 @@ internal class AuthgearCore(
clearSession(SessionStateChangeReason.CLEAR)
}

private fun updateSessionState(state: SessionState, reason: SessionStateChangeReason) {
private fun updateSessionState(state: SessionState, reason: SessionStateChangeReason, error: Throwable? = null) {
// TODO: Add re-entry detection
sessionState = state
val handler = Handler(Looper.getMainLooper())
handler.post {
this.delegate?.onSessionStateChanged(this.authgear, reason)
this.delegate?.onSessionStateChanged(this.authgear, reason, error)
}
}

Expand Down Expand Up @@ -670,7 +670,7 @@ internal class AuthgearCore(
)
} catch (e: Exception) {
handleInvalidGrantError(e)
if (e is OAuthException && e.error == "invalid_grant") {
if (e is OAuthException && (e.error == "invalid_grant" || e.error == "invalid_dpop_proof")) {
return
}
throw e
Expand Down Expand Up @@ -709,7 +709,7 @@ internal class AuthgearCore(
}
}

internal fun clearSession(changeReason: SessionStateChangeReason) {
internal fun clearSession(changeReason: SessionStateChangeReason, error: Throwable? = null) {
tokenStorage.deleteRefreshToken(name)
sharedStorage.onLogout(name)
storage.deleteApp2AppDeviceKeyId(name)
Expand All @@ -718,7 +718,7 @@ internal class AuthgearCore(
refreshToken = null
idToken = null
expireAt = null
updateSessionState(SessionState.NO_SESSION, changeReason)
updateSessionState(SessionState.NO_SESSION, changeReason, error)
}
}

Expand Down Expand Up @@ -876,11 +876,11 @@ internal class AuthgearCore(
}

private fun handleInvalidGrantError(e: Exception) {
if (e is OAuthException && e.error == "invalid_grant") {
clearSession(SessionStateChangeReason.INVALID)
if (e is OAuthException && (e.error == "invalid_grant" || e.error == "invalid_dpop_proof")) {
clearSession(SessionStateChangeReason.INVALID, e)
return
} else if (e is ServerException && e.reason == "InvalidGrant") {
clearSession(SessionStateChangeReason.INVALID)
clearSession(SessionStateChangeReason.INVALID, e)
return
}
}
Expand Down
5 changes: 4 additions & 1 deletion sdk/src/main/java/com/oursky/authgear/AuthgearDelegate.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package com.oursky.authgear

interface AuthgearDelegate {
fun onSessionStateChanged(container: Authgear, reason: SessionStateChangeReason) {}
// error is non-null when reason is INVALID, i.e. the session was cleared
// because a request failed with an error such as invalid_grant or
// invalid_dpop_proof. It is null for all other reasons.
fun onSessionStateChanged(container: Authgear, reason: SessionStateChangeReason, error: Throwable?) {}

fun sendWechatAuthRequest(state: String) {}
}
Loading