1
1
import 'dart:async' ;
2
+ import 'dart:convert' ;
3
+ import 'dart:math' ;
2
4
3
5
import 'package:authentication_repository/authentication_repository.dart' ;
4
6
import 'package:cache/cache.dart' ;
5
7
import 'package:firebase_auth/firebase_auth.dart' as firebase_auth;
6
8
import 'package:google_sign_in/google_sign_in.dart' ;
9
+ import 'package:sign_in_with_apple/sign_in_with_apple.dart' ;
7
10
import 'package:meta/meta.dart' ;
11
+ import 'package:crypto/crypto.dart' ;
8
12
9
13
/// Thrown if during the sign up process if a failure occurs.
10
14
class SignUpFailure implements Exception {}
@@ -15,6 +19,9 @@ class LogInWithEmailAndPasswordFailure implements Exception {}
15
19
/// Thrown during the sign in with google process if a failure occurs.
16
20
class LogInWithGoogleFailure implements Exception {}
17
21
22
+ /// Thrown during the sign in with apple process if a failure occurs.
23
+ class LogInWithAppleFailure implements Exception {}
24
+
18
25
/// Thrown during the logout process if a failure occurs.
19
26
class LogOutFailure implements Exception {}
20
27
@@ -27,13 +34,16 @@ class AuthenticationRepository {
27
34
CacheClient ? cache,
28
35
firebase_auth.FirebaseAuth ? firebaseAuth,
29
36
GoogleSignIn ? googleSignIn,
37
+ SignInWithApple ? appleSignIn,
30
38
}) : _cache = cache ?? CacheClient (),
31
39
_firebaseAuth = firebaseAuth ?? firebase_auth.FirebaseAuth .instance,
32
- _googleSignIn = googleSignIn ?? GoogleSignIn .standard ();
40
+ _googleSignIn = googleSignIn ?? GoogleSignIn .standard (),
41
+ _appleSignIn = appleSignIn ?? SignInWithApple ();
33
42
34
43
final CacheClient _cache;
35
44
final firebase_auth.FirebaseAuth _firebaseAuth;
36
45
final GoogleSignIn _googleSignIn;
46
+ final SignInWithApple _appleSignIn;
37
47
38
48
/// User cache key.
39
49
/// Should only be used for testing purposes.
@@ -89,6 +99,56 @@ class AuthenticationRepository {
89
99
}
90
100
}
91
101
102
+ /// Starts the Sign In with Apple Flow.
103
+ ///
104
+ /// Throws a [logInWithApple] if an exception occurs.
105
+ Future <void > logInWithApple () async {
106
+ // To prevent replay attacks with the credential returned from Apple, we
107
+ // include a nonce in the credential request. When signing in with
108
+ // Firebase, the nonce in the id token returned by Apple, is expected to
109
+ // match the sha256 hash of `rawNonce`.
110
+ final rawNonce = generateNonce ();
111
+ final nonce = sha256ofString (rawNonce);
112
+
113
+ try {
114
+ final appleCredential = await SignInWithApple .getAppleIDCredential (
115
+ scopes: [
116
+ AppleIDAuthorizationScopes .email,
117
+ AppleIDAuthorizationScopes .fullName,
118
+ ],
119
+ nonce: nonce,
120
+ );
121
+
122
+ // Create an `OAuthCredential` from the credential returned by Apple.
123
+ final credential = firebase_auth.OAuthProvider ('apple.com' ).credential (
124
+ idToken: appleCredential.identityToken,
125
+ rawNonce: rawNonce,
126
+ );
127
+ print (credential);
128
+
129
+ await _firebaseAuth.signInWithCredential (credential);
130
+ } on Exception {
131
+ throw LogInWithAppleFailure ();
132
+ }
133
+ }
134
+
135
+ /// Generates a cryptographically secure random nonce, to be included in a
136
+ /// credential request.
137
+ String generateNonce ([int length = 32 ]) {
138
+ final charset =
139
+ '0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._' ;
140
+ final random = Random .secure ();
141
+ return List .generate (length, (_) => charset[random.nextInt (charset.length)])
142
+ .join ();
143
+ }
144
+
145
+ /// Returns the sha256 hash of [input] in hex notation.
146
+ String sha256ofString (String input) {
147
+ final bytes = utf8.encode (input);
148
+ final digest = sha256.convert (bytes);
149
+ return digest.toString ();
150
+ }
151
+
92
152
/// Signs in with the provided [email] and [password] .
93
153
///
94
154
/// Throws a [LogInWithEmailAndPasswordFailure] if an exception occurs.
0 commit comments