Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fixed android logout #233

Merged
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
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,13 +117,14 @@ import {OAuth2Client} from "@byteowls/capacitor-oauth2";
'<button (click)="onLogoutClick()">Logout OAuth</button>'
})
export class SignupComponent {
accessToken: string;
refreshToken: string;

onOAuthBtnClick() {
OAuth2Client.authenticate(
oauth2Options
).then(response => {
let accessToken = response["access_token"];
this.accessToken = response["access_token"]; // storage recommended for android logout
this.refreshToken = response["refresh_token"];

// only if you include a resourceUrl protected user values are included in the response!
Expand All @@ -145,7 +146,7 @@ export class SignupComponent {
OAuth2Client.refreshToken(
oauth2RefreshOptions
).then(response => {
let accessToken = response["access_token"];
this.accessToken = response["access_token"]; // storage recommended for android logout
// Don't forget to store the new refresh token as well!
this.refreshToken = response["refresh_token"];
// Go to backend
Expand All @@ -156,7 +157,8 @@ export class SignupComponent {

onLogoutClick() {
OAuth2Client.logout(
oauth2LogoutOptions
oauth2LogoutOptions,
this.accessToken // only used on android
).then(() => {
// do something
}).catch(reason => {
Expand Down Expand Up @@ -363,6 +365,8 @@ android.buildTypes.release.manifestPlaceholders = [
2) "ERR_ANDROID_RESULT_NULL": See [Issue #52](https://github.com/moberwasserlechner/capacitor-oauth2/issues/52#issuecomment-525715515) for details.
I cannot reproduce this behaviour. Moreover there might be situation this state is valid. In other cases e.g. in the linked issue a configuration tweak fixed it.

3) To prevent some logout issues on certain OAuth2 providers (like Salesforce for example), you should provide the `id_token` parameter on the `logout(...)` function. This ensures that not only the cookies are deleted, but also the logout link is called from the Oauth2 provider. Also, it uses the system browser that the plugin uses (and not the user's default browser) to call the logout URL. This additionally ensures that the cookies are deleted in the correct browser.

### Custom OAuth Handler

Some OAuth provider (Facebook) force developers to use their SDK on Android.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import net.openid.appauth.AuthorizationResponse;
import net.openid.appauth.AuthorizationService;
import net.openid.appauth.AuthorizationServiceConfiguration;
import net.openid.appauth.EndSessionRequest;
import net.openid.appauth.EndSessionResponse;
import net.openid.appauth.GrantTypeValues;
import net.openid.appauth.TokenRequest;
import net.openid.appauth.TokenResponse;
Expand Down Expand Up @@ -62,6 +64,9 @@ public class OAuth2ClientPlugin extends Plugin {
private static final String PARAM_RESPONSE_MODE = "response_mode";
private static final String PARAM_LOGS_ENABLED = "logsEnabled";

private static final String PARAM_LOGOUT_URL = "logoutUrl";
private static final String PARAM_ID_TOKEN = "id_token";

private static final String USER_CANCELLED = "USER_CANCELLED";

private static final String ERR_PARAM_NO_APP_ID = "ERR_PARAM_NO_APP_ID";
Expand Down Expand Up @@ -302,9 +307,46 @@ public void logout(final PluginCall call) {
call.reject(ERR_GENERAL, e);
}
} else {
this.disposeAuthService();
this.discardAuthState();
call.resolve();
String idToken = ConfigUtils.getParam(String.class, call.getData(), PARAM_ID_TOKEN);
if (idToken == null) {
this.disposeAuthService();
this.discardAuthState();
call.resolve();
return;
}

oauth2Options = buildAuthenticateOptions(call.getData());

Uri authorizationUri = Uri.parse(oauth2Options.getAuthorizationBaseUrl());
Uri accessTokenUri;
if (oauth2Options.getAccessTokenEndpoint() != null) {
accessTokenUri = Uri.parse(oauth2Options.getAccessTokenEndpoint());
} else {
// appAuth does not allow to be the accessTokenUri empty although it is not used unit performTokenRequest
accessTokenUri = authorizationUri;
}
Uri logoutUri = Uri.parse(oauth2Options.getLogoutUrl());

AuthorizationServiceConfiguration config = new AuthorizationServiceConfiguration(authorizationUri, accessTokenUri);

EndSessionRequest endSessionRequest =
new EndSessionRequest.Builder(config)
.setIdTokenHint(idToken)
.setPostLogoutRedirectUri(logoutUri)
.build();

this.authService = new AuthorizationService(getContext());

try {
Intent endSessionIntent = authService.getEndSessionRequestIntent(endSessionRequest);
this.bridge.saveCall(call);
startActivityForResult(call, endSessionIntent, "handleEndSessionIntentResult");
} catch (ActivityNotFoundException e) {
call.reject(ERR_ANDROID_NO_BROWSER, e);
} catch (Exception e) {
Log.e(getLogTag(), "Unexpected exception on open browser for logout request!");
call.reject(ERR_GENERAL, e);
}
}
}

Expand Down Expand Up @@ -332,6 +374,29 @@ private void handleIntentResult(PluginCall call, ActivityResult result) {
}
}

@ActivityCallback
private void handleEndSessionIntentResult(PluginCall call, ActivityResult result) {
if (result.getResultCode() == Activity.RESULT_CANCELED) {
call.reject(USER_CANCELLED);
} else {
if (result.getData() != null) {
try {
EndSessionResponse resp = EndSessionResponse.fromIntent(result.getData());
JSObject json = new JSObject(resp.jsonSerializeString());

this.disposeAuthService();
this.discardAuthState();

call.resolve(json);
} catch (Exception e) {
Log.e(getLogTag(), "Unexpected exception on handling result for logout request!");
call.reject(ERR_GENERAL, e);
return;
}
}
}
}

void handleAuthorizationRequestActivity(Intent intent, PluginCall savedCall) {
// there are valid situation when the Intent is null, but
if (intent != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ public class OAuth2Options {
private String prompt;
private String responseMode;

private String logoutUrl;


public String getAppId() {
return appId;
Expand Down Expand Up @@ -215,4 +217,8 @@ public void addAdditionalResourceHeader(String key, String value) {
this.additionalResourceHeaders.put(key, value);
}
}

public String getLogoutUrl() {
return logoutUrl;
}
}
3 changes: 2 additions & 1 deletion src/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ export interface OAuth2ClientPlugin {
/**
* Logout from the authenticated OAuth 2 provider
* @param {OAuth2AuthenticateOptions} options Although not all options are needed. We simply reuse the options from authenticate
* @param {String} id_token Optional idToken, only for Android
* @returns {Promise<boolean>} true if the logout was successful else false.
*/
logout(options: OAuth2AuthenticateOptions): Promise<boolean>;
logout(options: OAuth2AuthenticateOptions, id_token?: string): Promise<boolean>;
}

export interface OAuth2RefreshTokenOptions {
Expand Down