Skip to content

Commit d9ca30f

Browse files
Add openid code
1 parent 3b2b483 commit d9ca30f

18 files changed

Lines changed: 1709 additions & 204 deletions

lib/solid_auth_client.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,13 @@ import 'package:flutter/widgets.dart';
3636
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
3737
import 'package:fast_rsa/fast_rsa.dart';
3838
import 'package:http/http.dart' as http;
39-
// import 'package:openid_client/openid_client.dart';
40-
// import 'package:openid_client/openid_client_io.dart' as oidc_mobile;
4139
import 'package:url_launcher/url_launcher.dart';
4240
import 'package:uuid/uuid.dart';
4341

4442
import 'package:solid_auth/platform_info.dart';
4543
import 'package:solid_auth/src/auth_manager/auth_manager_abstract.dart';
44+
import 'package:solid_auth/src/openid/openid_client.dart';
45+
import 'package:solid_auth/src/openid/openid_client_io.dart' as oidc_mobile;
4646

4747
/// Set port number to be used in localhost
4848

lib/src/auth_manager/auth_manager_abstract.dart

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,9 @@
2828
library;
2929

3030
// import just for the client class. Not used anywhere else.
31-
// import 'package:openid_client/openid_client.dart';
32-
33-
import 'package:openid_client/openid_client.dart';
34-
35-
import 'auth_manager_stub.dart' if (dart.library.html) 'web_auth_manager.dart';
31+
import 'package:solid_auth/src/openid/src/openid.dart';
32+
import 'package:solid_auth/src/auth_manager/auth_manager_stub.dart'
33+
if (dart.library.html) 'web_auth_manager.dart';
3634

3735
abstract class AuthManager {
3836
// some generic methods to be exposed.

lib/src/auth_manager/auth_manager_stub.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
/// Authors: Anushka Vidanage
2828
library;
2929

30-
import 'auth_manager_abstract.dart';
30+
import 'package:solid_auth/src/auth_manager/auth_manager_abstract.dart';
3131

3232
AuthManager getAuthManager() => throw UnsupportedError(
3333
'Cannot create a keyfinder without the packages dart:html or package:shared_preferences',

lib/src/auth_manager/web_auth_manager.dart

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,11 @@
2727
/// Authors: Anushka Vidanage
2828
library;
2929

30-
import 'package:openid_client/openid_client_browser.dart';
3130
import 'package:openidconnect_web/openidconnect_web.dart';
3231
import 'package:web/web.dart' hide Client;
3332

34-
// Project imports:
35-
import 'auth_manager_abstract.dart';
33+
import 'package:solid_auth/src/openid/openid_client_browser.dart';
34+
import 'package:solid_auth/src/auth_manager/auth_manager_abstract.dart';
3635

3736
late Window windowLoc;
3837

lib/src/openid/openid_browser.dart

Lines changed: 0 additions & 100 deletions
This file was deleted.

lib/src/openid/openid_client.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Copyright (c) 2017, rbellens. All rights reserved. Use of this source code
2+
// is governed by a BSD-style license that can be found in the LICENSE file.
3+
4+
library openid_client;
5+
6+
export 'src/openid.dart';
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import 'dart:js_interop';
2+
3+
import 'openid_client.dart';
4+
import 'package:web/web.dart' hide Credential, Client;
5+
import 'dart:async';
6+
export 'openid_client.dart';
7+
8+
/// A wrapper around [Flow] that handles the browser-specific parts of
9+
/// authentication.
10+
///
11+
/// The constructor takes a [Client] and a list of scopes. It then
12+
/// creates a [Flow] and uses it to generate an authentication URI.
13+
///
14+
/// The [authorize] method redirects the browser to the authentication URI.
15+
///
16+
/// The [logout] method redirects the browser to the logout URI.
17+
///
18+
/// The [credential] property returns a [Future] that completes with a
19+
/// [Credential] after the user has signed in and the browser is redirected to
20+
/// the app. Otherwise, it completes with `null`.
21+
///
22+
/// The state is not persisted in the browser, so the user will have to sign in
23+
/// again after a page refresh. If you want to persist the state, you'll have to
24+
/// store and restore the credential yourself. You can listen to the
25+
/// [Credential.onTokenChanged] event to be notified when the credential changes.
26+
class Authenticator {
27+
/// The [Flow] used for authentication.
28+
///
29+
/// This will be a flow of type [FlowType.implicit].
30+
final Flow flow;
31+
32+
/// A [Future] that completes with a [Credential] after the user has signed in
33+
/// and the browser is redirected to the app. Otherwise, it completes with
34+
/// `null`.
35+
final Future<Credential?> credential;
36+
37+
Authenticator._(this.flow) : credential = _credentialFromUri(flow);
38+
39+
// Authenticator(Client client,
40+
// {Iterable<String> scopes = const [], String? device, String? prompt})
41+
// : this._(Flow.implicit(client,
42+
// device: device,
43+
// state: window.localStorage.getItem('openid_client:state'),
44+
// prompt: prompt)
45+
// ..scopes.addAll(scopes)
46+
// ..redirectUri = Uri.parse(window.location.href).removeFragment());
47+
48+
// With PKCE flow
49+
Authenticator(
50+
Client client, {
51+
Iterable<String> scopes = const [],
52+
popToken = '',
53+
}) : this._(
54+
Flow.authorizationCodeWithPKCE(
55+
client,
56+
state: window.localStorage.getItem('openid_client:state'),
57+
)
58+
..scopes.addAll(scopes)
59+
..redirectUri = Uri.parse(
60+
window.location.href.contains('#/')
61+
? window.location.href.replaceAll('#/', 'callback.html')
62+
: '${window.location.href}callback.html',
63+
).removeFragment()
64+
..dPoPToken = popToken,
65+
);
66+
67+
/// Redirects the browser to the authentication URI.
68+
void authorize() {
69+
_forgetCredentials();
70+
window.localStorage.setItem('openid_client:state', flow.state);
71+
window.location.href = flow.authenticationUri.toString();
72+
}
73+
74+
/// Redirects the browser to the logout URI.
75+
void logout() async {
76+
_forgetCredentials();
77+
var c = await credential;
78+
if (c == null) return;
79+
var uri = c.generateLogoutUrl(
80+
redirectUri: Uri.parse(window.location.href).removeFragment());
81+
if (uri != null) {
82+
window.location.href = uri.toString();
83+
}
84+
}
85+
86+
void _forgetCredentials() {
87+
window.localStorage.removeItem('openid_client:state');
88+
window.localStorage.removeItem('openid_client:auth');
89+
}
90+
91+
static Future<Credential?> _credentialFromUri(Flow flow) async {
92+
var uri = Uri.parse(window.location.href);
93+
var iframe = uri.queryParameters['iframe'] != null;
94+
uri = Uri(query: uri.fragment);
95+
var q = uri.queryParameters;
96+
if (q.containsKey('access_token') ||
97+
q.containsKey('code') ||
98+
q.containsKey('id_token')) {
99+
window.history.replaceState(''.toJS, '',
100+
Uri.parse(window.location.href).removeFragment().toString());
101+
window.localStorage.removeItem('openid_client:state');
102+
103+
var c = await flow.callback(q.cast());
104+
if (iframe) window.parent!.postMessage(c.response?.toJSBox, '*'.toJS);
105+
return c;
106+
}
107+
return null;
108+
}
109+
110+
/// Tries to refresh the access token silently in a hidden iframe.
111+
///
112+
/// The implicit flow does not support refresh tokens. This method uses a
113+
/// hidden iframe to try to get a new access token without the user having to
114+
/// sign in again. It returns a [Future] that completes with a [Credential]
115+
/// when the iframe receives a response from the authorization server. The
116+
/// future will timeout after [timeout] if the iframe does not receive a
117+
/// response.
118+
Future<Credential> trySilentRefresh(
119+
{Duration timeout = const Duration(seconds: 20)}) async {
120+
var iframe = HTMLIFrameElement();
121+
var url = flow.authenticationUri;
122+
window.localStorage.setItem('openid_client:state', flow.state);
123+
iframe.src = url.replace(queryParameters: {
124+
...url.queryParameters,
125+
'prompt': 'none',
126+
'redirect_uri': flow.redirectUri.replace(queryParameters: {
127+
...flow.redirectUri.queryParameters,
128+
'iframe': 'true',
129+
}).toString(),
130+
}).toString();
131+
iframe.style.display = 'none';
132+
document.body!.append(iframe);
133+
var event = await window.onMessage.first.timeout(timeout).whenComplete(() {
134+
iframe.remove();
135+
});
136+
137+
var data = event.data?.dartify();
138+
if (data is Map) {
139+
var current = await credential;
140+
if (current == null) {
141+
return flow.client.createCredential(
142+
accessToken: data['access_token'],
143+
expiresAt: data['expires_at'] == null
144+
? null
145+
: DateTime.fromMillisecondsSinceEpoch(
146+
int.parse(data['expires_at'].toString()) * 1000),
147+
refreshToken: data['refresh_token'],
148+
expiresIn: data['expires_in'] == null
149+
? null
150+
: Duration(seconds: int.parse(data['expires_in'].toString())),
151+
tokenType: data['token_type'],
152+
idToken: data['id_token'],
153+
);
154+
} else {
155+
return current..updateToken(data.cast());
156+
}
157+
} else {
158+
throw Exception('$data');
159+
}
160+
}
161+
}

0 commit comments

Comments
 (0)