AcquireToken stop working after some time - xamarin.forms

I am working in Xamarin forms application.
I am using below code snippet to get token for AAD sign-in.
IEnumerable<IAccount> accounts = await App.PCA.GetAccountsAsync().ConfigureAwait(false);
try
{
if (btnSignInSignOut.Text == "Sign in")
{
try
{
IAccount firstAccount = accounts.FirstOrDefault();
authResult = await App.PCA.AcquireTokenSilent(App.Scopes, firstAccount)
.ExecuteAsync()
.ConfigureAwait(false);
}
catch (MsalUiRequiredException)
{
try
{
var builder = App.PCA.AcquireTokenInteractive(App.Scopes)
.WithParentActivityOrWindow(App.ParentWindow);
if (Device.RuntimePlatform != "UWP")
{
// on Android and iOS, prefer to use the system browser, which does not exist on UWP
SystemWebViewOptions systemWebViewOptions = new SystemWebViewOptions()
{
iOSHidePrivacyPrompt = true,
};
builder.WithSystemWebViewOptions(systemWebViewOptions);
builder.WithUseEmbeddedWebView(false);
}
authResult = await builder.ExecuteAsync().ConfigureAwait(false);
}
catch (Exception ex2)
{
await DisplayAlert("Acquire token interactive failed. See exception message for details: ", ex2.Message, "Dismiss");
}
}
if (authResult != null)
{
var content = await GetHttpContentWithTokenAsync(authResult.AccessToken);
UpdateUserContent(content);
}
}
else
{
while (accounts.Any())
{
await App.PCA.RemoveAsync(accounts.FirstOrDefault()).ConfigureAwait(false);
accounts = await App.PCA.GetAccountsAsync().ConfigureAwait(false);
}
Device.BeginInvokeOnMainThread(() =>
{
slUser.IsVisible = false;
btnSignInSignOut.Text = "Sign in";
});
}
}
catch (Exception ex)
{
await DisplayAlert("Authentication failed. See exception message for details: ", ex.Message, "Dismiss");
}
But after some time, token expires somehow and not refresh. Due to that it always redirect user to MS login page.
My requirement is, It should automatically take the logged in user's details.
You can take reference from this code.
I have also check other options like to directly call api to get refresh token but didn't found anything helpful.
Let me know if anyone have any idea about it.
Thanks in Advance.

For now, MSAL already caches your authorization and can log you in silently if it’s still valid. So, as a user, you don’t need to sign in every time you use the app.
You could use SecureStorage.SetAsync to store the access token.
Sign-in:
public async Task<bool> SignInAsync()
{
try
{
var accounts = await _pca.GetAccountsAsync();
var firstAccount = accounts.FirstOrDefault();
var authResult = await _pca.AcquireTokenSilent(Scopes, firstAccount).ExecuteAsync();
// Store the access token securely for later use.
await SecureStorage.SetAsync("AccessToken", authResult?.AccessToken);
return true;
}
catch (MsalUiRequiredException)
{
try
{
// This means we need to login again through the MSAL window.
var authResult = await _pca.AcquireTokenInteractive(Scopes)
.WithParentActivityOrWindow(ParentWindow)
.ExecuteAsync();
// Store the access token securely for later use.
await SecureStorage.SetAsync("AccessToken", authResult?.AccessToken);
return true;
}
catch (Exception ex2)
{
Debug.WriteLine(ex2.ToString());
return false;
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
return false;
}
}

Just for clarification, MSAL does not return, issue the token, and does not expire the token. When your token expireS then MSAL will automatically refresh your token when calling the AcquireTokenSilentAsync (so you don't have to refresh your token). The reason why your application is redirecting to the login page is because when you call the authenticated API then this API is returning the response 401 (Unauthorized) which means while calling the API you are not sending the token with the request. In return when the server returns 401 (Unauthorized) response, then your application is redirecting the user to the login page.
Read more about MSAL token expiration here.
Update your code accordingly:
await SecureStorage.SetAsync("accessToken", authResult.AccessToken);

Related

Uncaught Reference error: Toastify is not defined flutter web

I am new to flutter web. I have implemented firebase login functionality in my flutter web application. This functionality works correctly in local. But When i deploy the website on my own server, if i enter correct credentials, it works correctly on live, but whenever i enter wrong password, at that time it gives me exception,
Uncaught ReferenceError: Toastify is not defined
I have used fluttertoast library to dispaly toast messages, i m not sure what is causing the issue, is this error related to toast message or related to firebase. Please see attached screenshot of error
Is this issue related to fluttertoast library or related to firebase? How to resolve this issue, do we need to do any configuration related to domain in firebase?
I am using following code to signin User
Future<void> _signInWithEmailPassword() async {
UtilityHelper.showToast(message: "Login clicked");
_formkey.currentState?.save();
bool _isValid = _formkey.currentState?.validate() ?? false;
FocusScope.of(context).requestFocus(FocusNode());
if (_isValid) {
setState(() {
_loginType = LoginType.normal;
_isLoading = true;
});
final authProvider = Provider.of<AuthProvider>(context, listen: false);
final hasResponse = await authProvider.singInUser(loginReqModel);
redirectToHome(hasResponse, authProvider);
}
}
late UserModel _user;
UserModel get user => _user;
bool get isChangePasswordButtonShown =>
_authRepo.isUserLoggedInUsingPasssword;
String errorMsg = '';
Future<bool> singInUser(LoginReqModel reqModel) async {
try {
final response = await _authRepo.singInUser(reqModel);
if (response != null) {
_user = response;
notifyListeners();
return true;
}
notifyListeners();
return false;
} catch (error) {
print(error);
errorMsg = UtilityHelper.getErrorMessage(error);
return false;
}
}
Any help would be appreciated.
This issue was related to flutter toast library that i was using to display toast message, it did not worked with live domain.
So replacing that library with oktoast library solved the issue

facebookAuthCredential.idToken is null in Flutter

I successfully integrated facebook login in android and ios, I also getting facebook access token on login but getting null in token id. Below is the code
Future<UserCredential> signInWithFacebook() async {
// Trigger the sign-in flow
final LoginResult loginResult = await FacebookAuth.instance.login();
// Create a credential from the access token
final OAuthCredential facebookAuthCredential =
FacebookAuthProvider.credential(loginResult.accessToken!.token);
print(facebookAuthCredential.idToken); //Here getting null
// Once signed in, return the UserCredential
return FirebaseAuth.instance.signInWithCredential(facebookAuthCredential);
}
You won't get an idToken at this point with Facebook authentication flow, only accessToken. Use the following code skeleton to manage Facebook sign-in and evaluate the results at specific lines with breakpoints:
Future<UserCredential> signInWithFacebook() async {
final LoginResult loginResult = await FacebookAuth.instance.login();
if (loginResult.status == LoginStatus.success) {
final AccessToken accessToken = loginResult.accessToken!;
final OAuthCredential credential =
FacebookAuthProvider.credential(accessToken.token);
try {
return await FirebaseAuth.instance.signInWithCredential(credential);
} on FirebaseAuthException catch (e) {
// manage Firebase authentication exceptions
} catch (e) {
// manage other exceptions
}
} else {
// login was not successful, for example user cancelled the process
}
}
Then you can call this function with await, and once the future is completed, you can access user data:
final userCredential = await signInWithFacebook();
if (userCredential != null) {
// here you will have your Firebase user in:
// userCredential.user
final idToken = userCredential.user!.getIdToken();
}

How to sign up and in with OAuth credentials in firebase and flutter?

I was reading in the firebase Auth Rest Api and found this OAuth Credentials that contains a lot of information about a user that signs in including if the email is verified, So I tried to do it in my flutter app but looks like I am missing sth called Request URI I am new to flutter and firebase so I need your help.
My code:
static Future signin(String email, String password) async {
try {
http.Response response = await http.post(
'https://identitytoolkit.googleapis.com/v1/accounts:signInWithIdp?key=AIzaSyD3KOx9KSfK1lhyWRCt4_-dXLkKDNq-0hU',
body: {
'email': email,
'password': password,
'requestUri': ?? 'Sth of Type String',
},
);
if (response.statusCode == 200) {
print(response.statusCode);
print(json.decode(response.body));
} else {
print(response.statusCode);
}
String jsonsDataString = response.body.toString();
var _data = jsonDecode(jsonsDataString);
print(_data.toString());
print({email, password});
if (response.body.contains('EMAIL_EXISTS')) {
print('email exists');
} else if (response.body.contains('WEAK_PASSWORD')) {
print('weak password');
}
} catch (e) {
print(e);
}
}
Your are using the API for signing in with via oAuth and the requestUri ist the redirect URL of the auth flow.
Check this link: https://www.oauth.com/oauth2-servers/redirect-uris/
What you could do is use the sign-in with email api instead.
Check https://firebase.google.com/docs/reference/rest/auth and search sign in with email / passwort.

How to check if phone number is already registered in firebase authentication using flutter

So i am making a simple sign up and login screens in flutter application that uses phone authentication of firebase. For sign up im able to register new user, as the user provides his phone number and gets OTP. But for login i wanna check if the entered number is already registered. If so he gets otp and logs in or if not registered then asks to sign up first.
Firebase admin SDK supports this. Here's how to set up firebase admin (documentation). After you set up admin, you can use cloud_functions package to call APIs from the firebase admin SDK and the API we'll be using is one that allows us to get a user by phone number (documentation). If the API response is a user record, we know a phone exists.
In this example, I'm using node.js. In functions/index.js:
exports.checkIfPhoneExists = functions.https.onCall((data, context) => {
const phone = data.phone
return admin.auth().getUserByPhoneNumber(phone)
.then(function(userRecord){
return true;
})
.catch(function(error) {
return false;
});
});
In your dart code:
final HttpsCallable callable = CloudFunctions.instance.getHttpsCallable(functionName: 'checkIfPhoneExists');
dynamic resp = await callable.call({'phone': _phone});
if (resp.data) {
// user exists
}
Once the OTP is sent to the user you can verify if the user is a new user or an existing one in verify OTP function
verifyOtp(String input, context) async {
String retVal = "error";
OurUser _user = OurUser();
print(input);
final AuthCredential credential = PhoneAuthProvider.credential(
verificationId: _verificationId, smsCode: input);
try {
// await _auth.signInWithCredential(credential);
UserCredential _authResult = await _auth.signInWithCredential(credential);
// Here i have to save the details of the user in the database
if (_authResult.additionalUserInfo.isNewUser) {
currentUser.uid = _authResult.user.uid;
currentUser.phone = _inputText;
currentUser.type = "Customer";
retVal = await OurDatabase().createUser(currentUser);
} else {
// get the information of the user from the database this already exists
currentUser = await OurDatabase().getUserInfo(_authResult.user.uid);
if(currentUser!= null) {
Navigator.pushNamedAndRemoveUntil(
context, "/homescreen", (route) => false);
}
}
print("End of the await");
// when signup with the otp
if (retVal == "success") {
print("why not inside this mane");
Navigator.pushNamedAndRemoveUntil(
context, "/homescreen", (route) => false);
}
saveAllData();
} catch (e) {
print(e);
print("Something went wrong");
//prin
}
}
Now this is when you want to verify OTP from the user and after the top is verified you can know if the user was indeed a new user or an old one but what if you wanted to know that beforehand then the best possible solution would be to create a new collection in the firestore that would have only one document(so you are charged only for one document read) that would just contain all the numbers of the users that are registered within your application,
I used a simple straight forward way and it worked just fine.
First, add the mobile number to the firebase database in a separate node when the user creates the account.
await dbref.child("RegisteredNumbers").push().set({
"phoneNo": FirebaseAuth.instance.currentUser!.phoneNumber,
});
whenever a user tries to log in or signup check in this node if the provided number is available in It or not.
Future<bool> checkNumberIsRegistered({required String number}) async {
bool isNumberRegistered = false;
try {
await dbref.child("RegisteredNumbers").once().then((data) {
for (var i in data.snapshot.children) {
String data = i.child("phoneNo").value.toString();
if (number == data) {
isNumberRegistered = true;
return isNumberRegistered;
} else {
isNumberRegistered = false;
}
}
});
return isNumberRegistered;
} catch (e) {
return false;
}
}
Hope it helps

Firebase + Flutter: can't lock access to unverified email accounts

I'd like to block out people who didn't verify their email so i figured out this code for sign up:
// sign up
Future signUp(String email, String password) async {
try {
await _auth.createUserWithEmailAndPassword(
email: email, password: password);
} catch (e) {
print('An error has occured by creating a new user');
print(
e.toString(),
);
}
try {
final FirebaseUser _user = await _auth.currentUser();
await _user.sendEmailVerification();
} catch (error) {
print("An error occured while trying to send email verification");
print(error.toString());
}
try {
await _auth.signOut();
} catch (err) {
print(err);
}
}
and this for sign in:
//Sign In with Email and Pass
Future signInWithEmailAndPassword(String email, String password) async {
FirebaseUser _user = await FirebaseAuth.instance.currentUser();
if (_user != null && _user.isEmailVerified == true) {
try {
await _auth.signInWithEmailAndPassword(
email: email, password: password);
return _user;
} catch (e) {
return null;
}
} else {
return null;
}
}
_auth is just an instance of FirebaseAuth.
The problem is that i can login even if i didnt verify the email.
Firebase Auth doesn't stop accounts from signing in if the user hasn't verified their email address yet. You can check that property _user.isEmailVerified to find out the state of that validation after the user signs in, and you can determine from there what the user should see.
isEmailVerified can be a little bit of trouble to get working correctly.
Make sure you are calling
await FirebaseAuth.instance.currentUser()..reload();
before your are calling isEmailVerified also in my own experience and I don't know if this is just something I was doing wrong but this did not work from my Auth class this did not start working until I put the code directly in initState() of my widget that checks whether the user is verified. Like I said that part might have been something I did wrong. Like stated this will not listen for change you must check yourself either periodically or at a point that you know email is verified.
Future(() async {
_timer = Timer.periodic(Duration(seconds: 10), (timer) async {
await FirebaseAuth.instance.currentUser()
..reload();
var user = await FirebaseAuth.instance.currentUser();
if (user.isEmailVerified) {
timer.cancel();
Navigator.of(context).popAndPushNamed(HearingsScreen.routeName);
}
});
});
So it checks every 10 seconds to see if the user has verified their email not the most elegant solution. The page I have this on just displays a message 'Please verify your email' so its not like this is interrupting other code. If your app is performing other tasks this might not be an option for you. If you want to play around with isEmailVerified go ahead but i spent a week of headaches until i settled on this.

Resources