Skip to content

Commit 5754fde

Browse files
committed
feat(dart): add signIn, signOut, PKCE methods
add signIn, signOut, PKCE methods
1 parent bda0a93 commit 5754fde

14 files changed

+222
-17
lines changed

lib/interfaces/logto_config.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
class LogtoConfig {
2+
final String endpoint;
3+
final String appId;
4+
final String? appSecret;
5+
final List<String>? scopes;
6+
final List<String>? resources;
7+
8+
const LogtoConfig(
9+
{required this.appId,
10+
required this.endpoint,
11+
this.appSecret,
12+
this.resources,
13+
this.scopes});
14+
}

lib/interfaces/logto_interfaces.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export 'logto_config.dart';
2+
export 'oidc_provider_config.dart';
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import 'package:jose/src/util.dart';
2+
3+
class OidcProviderConfig extends JsonObject {
4+
Uri get authorizationEndpoint => getTyped('authorization_endpoint')!;
5+
Uri get tokenEndpoint => getTyped('token_endpoint')!;
6+
Uri get issuer => getTyped('issuer')!;
7+
Uri get jwksUri => getTyped('jwks_uri')!;
8+
Uri get userinfoEndpoint => getTyped('userinfo_endpoint')!;
9+
Uri get revocationEndpoint => getTyped('revocation_endpoint')!;
10+
Uri get endSessionEndpoint => getTyped('end_session_endpoint')!;
11+
12+
OidcProviderConfig.fromJson(Map<String, dynamic> json) : super.from(json);
13+
}

lib/logto_client.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import '/interfaces/logto_config.dart';
2+
3+
// Logto SDK
4+
class Logto {
5+
final LogtoConfig config;
6+
7+
Logto(this.config);
8+
}

lib/logto_core.dart

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import 'interfaces/logto_interfaces.dart';
2+
import 'utilities/utils.dart';
3+
4+
class Core {
5+
static const String codeChallengeMethod = 'S256';
6+
static const String responseType = 'code';
7+
static const String prompt = 'consent';
8+
9+
static Uri generateSignInUri(
10+
{required String authorizationEndpoint,
11+
required clientId,
12+
required Uri redirectUri,
13+
required String codeChallenge,
14+
required String state,
15+
List<String> scopes = const [],
16+
List<String>? resources,
17+
String prompt = Core.prompt}) {
18+
var signInUri = Uri.parse(authorizationEndpoint);
19+
20+
Map<String, dynamic> queryParameters = {
21+
'client_id': clientId,
22+
'redirect_uri': redirectUri.toString(),
23+
'code_challenge': codeChallenge,
24+
'code_challenge_method': Core.codeChallengeMethod,
25+
'state': state,
26+
'scope': withReservedScopes(scopes).join(' '),
27+
'response_type': Core.responseType,
28+
'prompt': prompt,
29+
};
30+
31+
if (resources != null && resources.isNotEmpty) {
32+
queryParameters.addAll({'resources': resources});
33+
}
34+
35+
return addQueryParameters(signInUri, queryParameters);
36+
}
37+
38+
static Uri generateSignOutUri(
39+
{required String endSessionEndpoint,
40+
required String idToken,
41+
required Uri postLogoutRedirectUri}) {
42+
var signOutUri = Uri.parse(endSessionEndpoint);
43+
44+
return addQueryParameters(signOutUri, {
45+
'id_token_hint': idToken,
46+
'post_logout_redirect_uri': postLogoutRedirectUri.toString()
47+
});
48+
}
49+
}

lib/logto_dart_sdk.dart

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
11
library logto_dart_sdk;
22

3-
/// A Calculator.
4-
class Calculator {
5-
/// Returns [value] plus 1.
6-
int addOne(int value) => value + 1;
7-
}
3+
export 'logto_client.dart';
4+
export 'logto_core.dart';

lib/utilities/constants.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Set<String> reservedScopes = {'openid', 'offline_access'};

lib/utilities/pkce.dart

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import 'dart:convert';
2+
3+
import 'package:crypto/crypto.dart';
4+
5+
import 'utils.dart' as utils;
6+
7+
class PKCE {
8+
final String codeVerifier;
9+
final String codeChallenge;
10+
11+
const PKCE._(this.codeVerifier, this.codeChallenge);
12+
13+
factory PKCE.generate() {
14+
String codeVerifier = PKCE.generateCodeVerifier();
15+
String codeChallenge = PKCE.generateCodeChallenge(codeVerifier);
16+
17+
return PKCE._(codeVerifier, codeChallenge);
18+
}
19+
20+
static String generateCodeVerifier() => utils.generateRandomString(64);
21+
22+
static String generateCodeChallenge(String codeVerifier) => base64Url
23+
.encode(sha256.convert(ascii.encode(codeVerifier)).bytes)
24+
.replaceAll('=', '');
25+
}

lib/utilities/utils.dart

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import 'dart:convert';
2+
import 'dart:math';
3+
4+
import 'constants.dart';
5+
6+
Uri addQueryParameters(Uri url, Map<String, dynamic> parameters) => url.replace(
7+
queryParameters: Map.from(url.queryParameters)..addAll(parameters));
8+
9+
String generateRandomString(int length) {
10+
Random random = Random.secure();
11+
12+
return base64UrlEncode(List.generate(length, (_) => random.nextInt(256)))
13+
.split('=')[0];
14+
}
15+
16+
List<String> withReservedScopes(List<String> scopes) {
17+
var scopeSet = scopes.toSet();
18+
scopeSet.addAll(reservedScopes);
19+
20+
return scopeSet.toList();
21+
}

pubspec.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ environment:
88
flutter: ">=1.17.0"
99

1010
dependencies:
11+
crypto: ^3.0.2
1112
flutter:
1213
sdk: flutter
14+
jose: ^0.3.2
1315

1416
dev_dependencies:
1517
flutter_test:

0 commit comments

Comments
 (0)