I am making an application where the user can sign in whether their email is verified or not. However, if their email is not verified, they will only have access to one page and if it is verified they will have access to the entire application (AppView).
So far with the code I have, I am able to create a user and sign them in, giving them access to the full application. But I'm not sure how to show the other view if their email is not verified.
Here is my code for checking if a user is signed in:
func listen () {
// monitor authentication changes using firebase
handle = Auth.auth().addStateDidChangeListener { (auth, user) in
if let user = user {
// if we have a user, create a new user model
print("Got user: \(user)")
self.isLoggedIn = true
self.session = User(
uid: user.uid,
displayName: user.displayName, email: "")
} else {
// if we don't have a user, set our session to nil
self.isLoggedIn = false
self.session = nil
}
}
}
And here is where I choose which view it should show:
struct ContentView: View {
#EnvironmentObject var session: SessionStore
func getUser () {
session.listen()
}
var body: some View {
Group {
if (session.session != nil) {
// I need to check here if the user is verified or not
AppView() //full application view
} else {
OnBoardingView()
}
}.onAppear(perform: getUser)
}
}
You can see if the user is verified by checking the User.isEmailVerified property. The main problem you may encounter is that this property is not immediately refreshed after you click the link in the email, as the iOS has no way of knowing that you clicked the link.
The client will automatically pick up the new value for isEmailVerified when it refreshes the token, which automatically happens every hour, when the user signs in explicitly again, or when you explicitly force it to get a new ID token.
Common ways to make this flow run smoothly for your users are to:
Force a ID token refresh when the iOS regains focus.
Force a Id token refresh periodically, while on a "waiting until you verified your email address" screen.,
Show a button to the user "I verified my email address" and then refresh the ID token.
Alternatively you can sign the user out after sending them the email, and then check when they sign in again. While this is simpler to implement, the above flow is more user friendly.
Related
I'm trying to work on Forgot Password but without using firebase's sendPasswordResetEmail method..
Is it possible to reset firebase user password without logged-in. I am implementing forget-password screen where the user enter their email address and will send OTP to user email address, once OTP is verified then they can reset their password from flutter app itself.
Is there anything I can do to get the user's information while the user is Not Signed In.
Obviously it is returning Null because the user's is not signed in
I tried to get the current user information
Future validatePassword() async {
print("Inside Validate Password");
try {
var auth = FirebaseAuth.instance;
User currentUser = auth.currentUser!;
print("Current User $currentUser");
if (currentUser != null) {
print(currentUser.email);
}
} catch (e) {
print("Current user is Not Found");
}
}
I am on xcode 11.4, Swift 4. The goal is to:
sign up a new user in Cognito User Pool, and then save an associated user record using Amplify GraphQL.
CRUD the user's record after signing in with Cognito User Pool.
The problem is I do not know how to associate Cognito with Amplify GraphQL. For example, in Google Firebase auth and Firestore, I would get a unique user id UID after signing up, then I would create an associated user record in Firestore with the key as this UID. Then on user signin/authentication, I can get this UID from firebase auth and find the associated record in firestore.
Currently with the AWS stack, I created a user model in schema.graphql as:
type User #model #auth(rules: [{ allow: owner, ownerField: "id", operations: [create, update, delete]}]){
id: ID!
firstName : String
lastName : String
handle : String
email : String!
}
So that only authenticated user can create, update and delete. Next somewhere in SignUpController I create a new user:
AWSMobileClient.default().signUp( username: email
, password: password
, userAttributes: ["email": email]) { (signUpResult, error) in
if let signUpResult = signUpResult {
switch(signUpResult.signUpConfirmationState) {
case .confirmed:
self.showAlert(msg: "You already have an account. Please go back and press log in")
case .unconfirmed:
break
case .unknown:
self.showAlert(msg: "Network error")
}
} else if let error = error { ... }
And then confirm the user w/ code:
AWSMobileClient.default().confirmSignUp(username: email, confirmationCode: code) { (signUpResult, error) in
if let signUpResult = signUpResult {
switch(signUpResult.signUpConfirmationState) {
case .confirmed:
// This is where I need to create an associated user account
break
case .unconfirmed:
self.showAlert(title: "Error", msg: "User is not confirmed and needs verification via \(signUpResult.codeDeliveryDetails!.deliveryMedium) sent at \(signUpResult.codeDeliveryDetails!.destination!)")
case .unknown:
self.showAlert(title: "Error", msg: "Network error")
}
} else { //if let error = error {
self.showAlert(title: "Error", msg: "Network error")
}
Right now my solution in case .confirmed is to sign in immediately, and then fetch the user's client token via:
class CognitoPoolProvider : AWSCognitoUserPoolsAuthProviderAsync {
/// this token may not be what you want ...
func getLatestAuthToken(_ callback: #escaping (String?, Error?) -> Void) {
AWSMobileClient.default().getTokens { (token, error) in
if let error = error {
callback(nil,error)
}
callback(token?.accessToken?.tokenString, error)
}
}
}
This turns out to be the wrong solution, since the user's client token changes all the time.
Overall, this is a standard hello-world problem, and there should be a standard out of box solution provided by AWS. I search the docs and github, but cannot find a satisfactory answer.
The right way is DON'T TRUST CLIENT for creating associate user information from Cognito, you have to do it at server side.
You should create a new Lambda Post Confirmation Trigger for Cognito and code it to create an associate account. You can use event.userName or create custom attribute uuid type likes custom:id to link your associate account.
Ref: https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-post-confirmation.html
I made the following login function:
#objc func handleSignIn() {
guard let email = emailField.text else { return }
guard let pass = passwordField.text else { return }
Auth.auth().signIn(withEmail: email, password: pass) { user, error in
if error == nil && user != nil && (user!.user.isEmailVerified){
self.dismiss(animated: false, completion: nil)
}; if user != nil && !(user?.user.isEmailVerified)! {
self.lblStatus.text = "Please Verify Your Email"
}
else {
self.lblStatus.text = "Error logging in: \(error!.localizedDescription)"
resetForm()
}
}
Yet the user can still log in without verifying their email despite my attempts to prevent this with the && (user!.user.isEmailVerified) stipulation. What am I missing here?
The completion of a sign in just means that the user identified themselves, and you know which account represents their chosen identity. A sign-in does not imply authorization to do anything with that account, other than to update its profile information.
You'll notice there's a bit of a chicken and egg problem here. A sign-in has to complete successfully in order to get a User object, and that user object has the property which indicates if their email has been verified. So you have to allow a sign-in if you want read that property.
You could, in theory, sign the user out immediately if they haven't verified, but all that does is prevent them from requesting another email verification, in case they deleted the first one, or it wasn't able to be delivered. So now you just have an upset user who can't take any action to resolve the issue.
If you want to prevent the user from doing anything in other Firebase products until they're verified, you should use security rules to prevent whatever read and write access shouldn't be allowed. You can check the email verification flag in your rules. Look into using auth.token.emailVerified. Example for Realtime Database here.
I been looking for control what a kind of user can see in my app, this is a scholar project. I'm using Swift and Firebase Authentication. I have two kinds of users: Model and Client. In my app I have some views for the Model and other for the Client. What I want to do is that once they log in, in to the app show just the views for their kind of user. I don't know how to verify if the user that is trying to sign in is a Model or a Client.
#IBAction func signInTapped(_ sender: UIButton) {
if validateFields(){
Auth.auth().signIn(withEmail: emailTxt.text!, password: passTxt.text!, completion:{
(user, error) in
if let u = user {
//User is found
}else{
//Error
}
})
}
}
I know that the code need to be where is the comment "User is found" but I don't know if I need to modify something into the the Firebase Console
Create a Firebase Database or Firestore to your project.
Now when you authenticate a user you should also create a userobject in your databse. In this object you can create a field to store whether your user is a model or a client.
Now once the user has signed in, you can download this userobject from the database, check whether the user is a model or client, and send the user to their corresponding views.
You can use custom claims.
You set them using the Admin SDK
// Javascript
admin.auth().setCustomUserClaims(uid, {model: true}).then(() => {
// The new custom claims will propagate to the user's ID token the
// next time a new one is issued.
});
Then in client SDK just read the claim.
user.getIDTokenResult(completion: { (result, error) in
guard let model = result?.claims?["model"] as? NSNumber else {
// Something
}
if model.boolValue {
// Show something else
} else {
// Something else again
}
})
Shamelessly copied from Firebase Docs
I want to add a push token to an user in my application. I have the push token, I have the user, but I can't add the token to the user. How I can add the push token to this user?
Here is the code:
var io = Ionic.io();
username = localStorage.getItem('username');
var signupSuccess = function(user) {
// The user was authenticated; you can get the authenticated user
console.log(user);
};
var signupFailure = function(errors) {
for (var err in errors) {
// Check the error and provide an appropriate message
// for your application.
user = Ionic.User.current();
}
};
var details = {
'email': 'email#gmail.com',
'password': 'pass2',
'username': 'username'
}
Ionic.Auth.signup(details).then(signupSuccess, signupFailure);
var push = new Ionic.Push();
var user = Ionic.User.current();
var callback = function(pushToken) {
alert('TOKEN: ' + pushToken.token);
user.addPushToken(pushToken);
user.save(); // You NEED to call a save after you add the token
}
push.register(callback);
It's no longer user.addPushToken(pushToken);
Instead you need:
push.register(function(token) {
push.saveToken(token);
});
This will automatically add the token to the currently logged in Ionic user.
See this example from Ionic documentation
N.B. as you're adding this at the point that the user is signing in, you also need to add the above code to register for push inside your $ionicPlatform.ready function:
$ionicPlatform.ready(function() {
var push = new Ionic.Push();
push.register(function(token) {
push.saveToken(token);
});
});
Or more likely, create one function to register for push which is called both from within $ionicPlatform.ready and also from within your signup / signin functions.
Otherwise, if Push has not been registered inside $ionicPlatform.ready, it won't add the token to your user when you call push.saveToken after signup / signin.
I can suggest one thing. You can ask the backend or server to team add a user token in the database or else.
You can manually add the following thing if it is an JSON object:
user["token"]=value