Skip to content

Commit 7aa9bf3

Browse files
committed
Add end session support
Original-author: Sergiy Mokiyenko <[email protected]>
1 parent e9c6f5a commit 7aa9bf3

28 files changed

+1779
-138
lines changed

README.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,78 @@ authState.performActionWithFreshTokens(service, new AuthStateAction() {
419419
});
420420
```
421421

422+
### Ending current session (Draft)
423+
424+
Given you have a logged in session and you want to end it. In that case you need to get:
425+
- `AuthorizationServiceConfiguration`
426+
- valid Open Id Token that you should get after authentication
427+
- End of session URI that should be provided within you OpenId service config
428+
429+
First you have to build EndSessionRequest
430+
431+
```java
432+
EndSessionRequest endSessionRequest =
433+
new EndSessionRequest.Builder(
434+
authorizationServiceConfiguration,
435+
idToken,
436+
endSessionRedirectUri
437+
).build();
438+
```
439+
This request can then be dispatched using one of two approaches.
440+
441+
a `startActivityForResult` call using an Intent returned from the `AuthorizationService`,
442+
or by calling `performEndSessionRequest` and providing pending intent for completion
443+
and cancelation handling activities.
444+
445+
The startActivityForResult approach is simpler to use but may require more processing of the result:
446+
447+
```java
448+
private void endSession() {
449+
AuthorizationService authService = new AuthorizationService(this);
450+
Intent endSessionItent = authService.getEndSessionRequestIntent(endSessionRequest);
451+
startActivityForResult(endSessionItent, RC_END_SESSION);
452+
}
453+
454+
@Override
455+
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
456+
if (requestCode == RC_END_SESSION) {
457+
EndSessionResonse resp = EndSessionResonse.fromIntent(data);
458+
AuthorizationException ex = AuthorizationException.fromIntent(data);
459+
// ... process the response or exception ...
460+
} else {
461+
// ...
462+
}
463+
}
464+
```
465+
If instead you wish to directly transition to another activity on completion or cancelation,
466+
you can use `performEndSessionRequest`:
467+
468+
```java
469+
AuthorizationService authService = new AuthorizationService(this);
470+
471+
authService.performEndSessionRequest(
472+
endSessionRequest,
473+
PendingIntent.getActivity(this, 0, new Intent(this, MyAuthCompleteActivity.class), 0),
474+
PendingIntent.getActivity(this, 0, new Intent(this, MyAuthCanceledActivity.class), 0));
475+
```
476+
477+
End session flow will also work involving browser mechanism that is described in authorization
478+
mechanism session.
479+
Handling response mechanism with transition to another activity should be as follows:
480+
481+
```java
482+
public void onCreate(Bundle b) {
483+
EndSessionResponse resp = EndSessionResponse.fromIntent(getIntent());
484+
AuthorizationException ex = AuthorizationException.fromIntent(getIntent());
485+
if (resp != null) {
486+
// authorization completed
487+
} else {
488+
// authorization failed, check ex for more details
489+
}
490+
// ...
491+
}
492+
```
493+
422494
### AuthState persistence
423495

424496
Instances of `AuthState` keep track of the authorization and token

app/README-Okta.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ You can create an Okta developer account at [https://developer.okta.com/](https:
1313
| Setting | Value |
1414
| ------------------- | --------------------------------------------------- |
1515
| Application Name | OpenId Connect App *(must be unique)* |
16-
| Redirect URIs | com.oktapreview.yoursubdomain://callback_url|
16+
| Login redirect URIs | com.oktapreview.yoursubdomain://callback_url|
17+
| Logout redirect URIs| com.oktapreview.yoursubdomain://callback_url|
1718
| Allowed grant types | Authorization Code |
1819

1920
1. Click **Finish** to redirect back to the *General Settings* of your application.
@@ -27,6 +28,7 @@ You can create an Okta developer account at [https://developer.okta.com/](https:
2728
{
2829
"client_id": "{{YourClientID}}",
2930
"redirect_uri": "com.oktapreview.{{yourOrg}}:/oauth",
31+
"end_session_uri": "com.oktapreview.{{yourOrg}}:/{logoutCallback}",
3032
"authorization_scope": "openid email profile",
3133
"discovery_uri": "https://{{yourOrg}}.okta.com/.well-known/openid-configuration"
3234
}

app/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,15 @@ The configuration file MUST contain a JSON object. The following properties can
1616
The value specified here should match the value specified for `appAuthRedirectScheme` in the
1717
`build.gradle` (Module: app), so that the demo app can capture the response.
1818

19+
- `end_sesion_uri` (required): The redirect URI to use for receiving the end session response.
20+
This should be a custom scheme URI (com.example.app:/oauth2redirect/example-provider).
21+
Consult the documentation for your authorization server.
22+
23+
The value specified here should match the value specified for `appAuthRedirectScheme` in the
24+
`build.gradle` (Module: app), so that the demo app can capture the response.
25+
26+
NOTE: Scheme of the URI should be the same as `redirect_uri` but callback should be different.
27+
1928
- `authorization_scope` (required): The scope string to use for the authorization request.
2029
For the purposes of the demo, we recommend the value "openid profile email", though any value
2130
understood by your authorization server can be used.

app/java/net/openid/appauthdemo/Configuration.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,11 @@ public final class Configuration {
6161
private String mClientId;
6262
private String mScope;
6363
private Uri mRedirectUri;
64+
private Uri mEndSessionUri;
6465
private Uri mDiscoveryUri;
6566
private Uri mAuthEndpointUri;
6667
private Uri mTokenEndpointUri;
68+
private Uri mEndSessionEndpoint;
6769
private Uri mRegistrationEndpointUri;
6870
private Uri mUserInfoEndpointUri;
6971
private boolean mHttpsRequired;
@@ -141,6 +143,11 @@ public Uri getDiscoveryUri() {
141143
return mDiscoveryUri;
142144
}
143145

146+
@Nullable
147+
public Uri getEndSessionUri() {
148+
return mEndSessionUri;
149+
}
150+
144151
@Nullable
145152
public Uri getAuthEndpointUri() {
146153
return mAuthEndpointUri;
@@ -151,6 +158,11 @@ public Uri getTokenEndpointUri() {
151158
return mTokenEndpointUri;
152159
}
153160

161+
@Nullable
162+
public Uri getEndSessionEndpoint() {
163+
return mEndSessionEndpoint;
164+
}
165+
154166
@Nullable
155167
public Uri getRegistrationEndpointUri() {
156168
return mRegistrationEndpointUri;
@@ -195,6 +207,7 @@ private void readConfiguration() throws InvalidConfigurationException {
195207
mClientId = getConfigString("client_id");
196208
mScope = getRequiredConfigString("authorization_scope");
197209
mRedirectUri = getRequiredConfigUri("redirect_uri");
210+
mEndSessionUri = getRequiredConfigUri("end_session_uri");
198211

199212
if (!isRedirectUriRegistered()) {
200213
throw new InvalidConfigurationException(
@@ -209,6 +222,7 @@ private void readConfiguration() throws InvalidConfigurationException {
209222

210223
mTokenEndpointUri = getRequiredConfigWebUri("token_endpoint_uri");
211224
mUserInfoEndpointUri = getRequiredConfigWebUri("user_info_endpoint_uri");
225+
mEndSessionEndpoint = getRequiredConfigUri("end_session_endpoint");
212226

213227
if (mClientId == null) {
214228
mRegistrationEndpointUri = getRequiredConfigWebUri("registration_endpoint_uri");

app/java/net/openid/appauthdemo/LoginActivity.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ private void initializeAppAuth() {
216216
AuthorizationServiceConfiguration config = new AuthorizationServiceConfiguration(
217217
mConfiguration.getAuthEndpointUri(),
218218
mConfiguration.getTokenEndpointUri(),
219+
mConfiguration.getEndSessionEndpoint(),
219220
mConfiguration.getRegistrationEndpointUri());
220221

221222
mAuthStateManager.replace(new AuthState(config));

app/java/net/openid/appauthdemo/TokenActivity.java

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
package net.openid.appauthdemo;
1616

17+
import android.app.Activity;
1718
import android.content.Intent;
1819
import android.net.Uri;
1920
import android.os.Bundle;
@@ -36,6 +37,7 @@
3637
import net.openid.appauth.AuthorizationService;
3738
import net.openid.appauth.AuthorizationServiceDiscovery;
3839
import net.openid.appauth.ClientAuthentication;
40+
import net.openid.appauth.EndSessionRequest;
3941
import net.openid.appauth.TokenRequest;
4042
import net.openid.appauth.TokenResponse;
4143
import okio.Okio;
@@ -65,6 +67,8 @@ public class TokenActivity extends AppCompatActivity {
6567

6668
private static final String KEY_USER_INFO = "userInfo";
6769

70+
private static final int END_SESSION_REQUEST_CODE = 911;
71+
6872
private AuthorizationService mAuthService;
6973
private AuthStateManager mStateManager;
7074
private final AtomicReference<JSONObject> mUserInfoJson = new AtomicReference<>();
@@ -186,7 +190,7 @@ private void displayAuthorized() {
186190

187191
AuthState state = mStateManager.getCurrent();
188192

189-
TextView refreshTokenInfoView = (TextView) findViewById(R.id.refresh_token_info);
193+
TextView refreshTokenInfoView = findViewById(R.id.refresh_token_info);
190194
refreshTokenInfoView.setText((state.getRefreshToken() == null)
191195
? R.string.no_refresh_token_returned
192196
: R.string.refresh_token_returned);
@@ -230,7 +234,7 @@ private void displayAuthorized() {
230234
viewProfileButton.setOnClickListener((View view) -> fetchUserInfo());
231235
}
232236

233-
((Button)findViewById(R.id.sign_out)).setOnClickListener((View view) -> signOut());
237+
findViewById(R.id.sign_out).setOnClickListener((View view) -> endSession());
234238

235239
View userInfoCard = findViewById(R.id.userinfo_card);
236240
JSONObject userInfo = mUserInfoJson.get();
@@ -380,6 +384,24 @@ private void fetchUserInfo(String accessToken, String idToken, AuthorizationExce
380384
});
381385
}
382386

387+
@Override
388+
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
389+
super.onActivityResult(requestCode, resultCode, data);
390+
if (requestCode == END_SESSION_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
391+
signOut();
392+
finish();
393+
} else {
394+
displayEndSessionCancelled();
395+
}
396+
}
397+
398+
private void displayEndSessionCancelled() {
399+
Snackbar.make(findViewById(R.id.coordinator),
400+
"Sign out canceled",
401+
Snackbar.LENGTH_SHORT)
402+
.show();
403+
}
404+
383405
@MainThread
384406
private void showSnackbar(String message) {
385407
Snackbar.make(findViewById(R.id.coordinator),
@@ -388,6 +410,16 @@ private void showSnackbar(String message) {
388410
.show();
389411
}
390412

413+
@MainThread
414+
private void endSession() {
415+
Intent endSessionEnten = mAuthService.getEndSessionRequestIntent(
416+
new EndSessionRequest.Builder(
417+
mStateManager.getCurrent().getAuthorizationServiceConfiguration(),
418+
mStateManager.getCurrent().getIdToken(),
419+
mConfiguration.getEndSessionUri()).build());
420+
startActivityForResult(endSessionEnten, END_SESSION_REQUEST_CODE);
421+
}
422+
391423
@MainThread
392424
private void signOut() {
393425
// discard the authorization and token state, but retain the configuration and

app/res/raw/auth_config.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"client_id": "",
33
"redirect_uri": "net.openid.appauthdemo:/oauth2redirect",
4+
"end_session_uri":"",
45
"authorization_scope": "openid email profile",
56
"discovery_uri": "",
67
"authorization_endpoint_uri": "",

0 commit comments

Comments
 (0)