Skip to content

Commit 02d7f79

Browse files
committed
feat(apple_login):Implement firebase Apple Auth
1 parent 924b47e commit 02d7f79

File tree

5 files changed

+100
-1
lines changed

5 files changed

+100
-1
lines changed

examples/flutter_firebase_login/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Example Flutter app built with `flutter_bloc` to implement login using Firebase.
77
## Features
88

99
- Sign in with Google
10+
- Sign in with Apple
1011
- Sign up with email and password
1112
- Sign in with email and password
1213

examples/flutter_firebase_login/lib/login/cubit/login_cubit.dart

+12
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,16 @@ class LoginCubit extends Cubit<LoginState> {
5252
emit(state.copyWith(status: FormzStatus.pure));
5353
}
5454
}
55+
56+
Future<void> logInWithApple() async {
57+
emit(state.copyWith(status: FormzStatus.submissionInProgress));
58+
try {
59+
await _authenticationRepository.logInWithApple();
60+
emit(state.copyWith(status: FormzStatus.submissionSuccess));
61+
} on Exception {
62+
emit(state.copyWith(status: FormzStatus.submissionFailure));
63+
} on NoSuchMethodError {
64+
emit(state.copyWith(status: FormzStatus.pure));
65+
}
66+
}
5567
}

examples/flutter_firebase_login/lib/login/view/login_form.dart

+24
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ class LoginForm extends StatelessWidget {
3838
_LoginButton(),
3939
const SizedBox(height: 8.0),
4040
_GoogleLoginButton(),
41+
const SizedBox(height: 8.0),
42+
_AppleLoginButton(),
4143
const SizedBox(height: 4.0),
4244
_SignUpButton(),
4345
],
@@ -139,6 +141,28 @@ class _GoogleLoginButton extends StatelessWidget {
139141
}
140142
}
141143

144+
class _AppleLoginButton extends StatelessWidget {
145+
@override
146+
Widget build(BuildContext context) {
147+
final theme = Theme.of(context);
148+
return ElevatedButton.icon(
149+
key: const Key('loginForm_appleLogin_raisedButton'),
150+
label: const Text(
151+
'SIGN IN WITH Apple',
152+
style: TextStyle(color: Colors.white),
153+
),
154+
style: ElevatedButton.styleFrom(
155+
shape: RoundedRectangleBorder(
156+
borderRadius: BorderRadius.circular(0.0),
157+
),
158+
primary: Colors.black,
159+
),
160+
icon: const Icon(FontAwesomeIcons.apple, color: Colors.white),
161+
onPressed: () => context.read<LoginCubit>().logInWithApple(),
162+
);
163+
}
164+
}
165+
142166
class _SignUpButton extends StatelessWidget {
143167
@override
144168
Widget build(BuildContext context) {

examples/flutter_firebase_login/packages/authentication_repository/lib/src/authentication_repository.dart

+61-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import 'dart:async';
2+
import 'dart:convert';
3+
import 'dart:math';
24

35
import 'package:authentication_repository/authentication_repository.dart';
46
import 'package:cache/cache.dart';
57
import 'package:firebase_auth/firebase_auth.dart' as firebase_auth;
68
import 'package:google_sign_in/google_sign_in.dart';
9+
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
710
import 'package:meta/meta.dart';
11+
import 'package:crypto/crypto.dart';
812

913
/// Thrown if during the sign up process if a failure occurs.
1014
class SignUpFailure implements Exception {}
@@ -15,6 +19,9 @@ class LogInWithEmailAndPasswordFailure implements Exception {}
1519
/// Thrown during the sign in with google process if a failure occurs.
1620
class LogInWithGoogleFailure implements Exception {}
1721

22+
/// Thrown during the sign in with apple process if a failure occurs.
23+
class LogInWithAppleFailure implements Exception {}
24+
1825
/// Thrown during the logout process if a failure occurs.
1926
class LogOutFailure implements Exception {}
2027

@@ -27,13 +34,16 @@ class AuthenticationRepository {
2734
CacheClient? cache,
2835
firebase_auth.FirebaseAuth? firebaseAuth,
2936
GoogleSignIn? googleSignIn,
37+
SignInWithApple? appleSignIn,
3038
}) : _cache = cache ?? CacheClient(),
3139
_firebaseAuth = firebaseAuth ?? firebase_auth.FirebaseAuth.instance,
32-
_googleSignIn = googleSignIn ?? GoogleSignIn.standard();
40+
_googleSignIn = googleSignIn ?? GoogleSignIn.standard(),
41+
_appleSignIn = appleSignIn ?? SignInWithApple();
3342

3443
final CacheClient _cache;
3544
final firebase_auth.FirebaseAuth _firebaseAuth;
3645
final GoogleSignIn _googleSignIn;
46+
final SignInWithApple _appleSignIn;
3747

3848
/// User cache key.
3949
/// Should only be used for testing purposes.
@@ -89,6 +99,56 @@ class AuthenticationRepository {
8999
}
90100
}
91101

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+
92152
/// Signs in with the provided [email] and [password].
93153
///
94154
/// Throws a [LogInWithEmailAndPasswordFailure] if an exception occurs.

examples/flutter_firebase_login/packages/authentication_repository/pubspec.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ dependencies:
1717
google_sign_in: ^5.0.0
1818
meta: ^1.3.0
1919
very_good_analysis: ^2.0.0
20+
sign_in_with_apple: ^3.0.0
21+
crypto: ^3.0.0
2022

2123
dev_dependencies:
2224
flutter_test:

0 commit comments

Comments
 (0)