Supabase auth.update() email_refresh_token workflow - vue-router

When I want to let an authenticated user change their email address I use auth.update(), found here https://supabase.com/docs/reference/javascript/auth-update
I'm using just a magic link auth, btw.
Right now my project is set up for only one email confirmation.
Im using vue 3 with vu-router v4
When I run this function
async handleUpdateUser(newEmail: string) {
try {
const { user, error } = await supabase.auth.update({
email: newEmail,
});
if (error) throw error;
} catch (error) {
console.log(error);
}
},
The onAuthStateChange that I log shows as USER_UPDATED, the users.auth table on the server shows the new stuff, and a new_email prop is seen on the user object in the console
My question is how do I make the new_emial into just email.
the link that is sent to the new email is as so
https://correct_info_is_here.supabase.co/auth/v1/verify?token=there_is_a_token_here&type=email_change&redirect_to=http://localhost:3000/
When I click the link, it redirects to a new window just like it does when an initial sign up happens, but other than that nothing changes.
I found this answer,
https://github.com/supabase/supabase/discussions/1763
but I have no idea how to implement that procedure. The only way figured out how to get that token is by an rpc function on the client , but I don't know what to do with that token after I receive it.
Also I might add when I use vue-router, I log the to and from properties of the beforeEach like so
router.beforeEach(async (to, from) => {
console.log("to", to);
console.log("from", from);...
When I load the confirmation link, it doesn't show anything useful like it does when its redirected form the original sign up link.
Any help would greatly be appreciated.

I ran into a similar issue, and in my case, it came down to the "Double confirm email changes" setting in Supabase Studio.
From the Supabase docs for supabase.auth.update:
Email updates will send an email to both the user's current and new email with a confirmation link by default. To toggle this behavior off and only send a single confirmation link to the new email, toggle "Double confirm email changes" under "Authentication" -> "Settings" off.
So what may be happening is that when you request the email change, the user receives two emails: one at the old email address, and one at the new email address. The user must click both links in order for that user's email column to actually be updated.
At least, this was the issue in my case — I hope it helps!

Related

Auth0 change password using custom login

In auth0 with the custom login form, I wanted to set it up in a way that when the user clicks on Forgot Password link it should redirect to send email page. And from there when the email is entered and clicked on submit a reset link should be sent to the email whereas clicking on the reset link should redirect to the change password page in auth0. With auth0 lock its already been setup but with custom login we have do it creating send email page where email would be entered and calling the change password function. Can someone help me out with how do I achieve it. Also should I have the change password function in the custom login page or in the send email page and should be called when send email button is hit. Have included auth0 changepassword function below.
function resetPassword (e) {
e.preventDefault();
var email = document.getElementById('email').value;
if(!email)
{
alert("Please enter your email.");
return;
}
webAuth.changePassword({
connection: databaseConnection ,
email: email
}, function (err, resp) {
if(err){
console.log(err.message);
alert(err.message);
}else{
console.log(resp);
alert(resp);
}});
}
So I found the answer to this question. In fact default auth0 custom login page is single page application. There we have to hide the elements and display other elements using javascript is preferable.

Vue,js used with Supabase - can't update signIn button after logging in with Oauth

async handleSignInSignOutButtonClick() {
if (!this.isSignedIn) {
supabase.auth.signIn({ provider: "google" });
this.$store.commit("signIn", supabase.auth.session());
window.location.reload();
return;
}
await this.$store.commit("signOut");
supabase.auth.signOut();
window.location.reload();
},
The above function is triggered by a sign-in button, which is supposed to become a sign-out button and the icon of the user after logging in.
When The function fires, supabase redirects me to Google OAuth consent screen. However, after logging in and redirecting back to my app, the sign-in button stays there until I manually refresh the page.
What is wrong with my code...
There are a couple of things going on that you need to be aware of. For starters you are reloading your page when you don't need to in the handleSignInSignOutButtonClick() function.
When the authentication process begins, your app will be redirected to Google OAuth consent screen as you have discovered. Once the authentication is complete, you will be redirected back to your app and the reload occurs automatically.
The second point is that you can make use of the supabase.auth.onAuthStateChange() event to help you. My suggestion would be to listen for this event when you create your supabase client so it listens for the duration of your app instance. During that event handling, you can assign the user to the store (or anywhere you want to save the user data) based upon the state change. Your app can be reactive to state changes.
In your supabase client setup code:
const supabaseUrl = process.env.SUPABASE_URL // your supabaseUrl
const supabaseAnonKey = process.env.SUPABASE_ANON_KEY // your supabaseKey
const supabase = createClient(supabaseUrl, supabaseAnonKey)
/**
* Set up the authentication state change listener
*
* #param {string} event The event indicates what state changed (SIGN_IN, SIGN_OUT etc)
* #param {object} session The session contains the current user session data or null if there is no user
*/
supabase.auth.onAuthStateChange((event, session) => {
const user = session?.user || null;
// Save your user to your desired location
$store.commit("user", user);
});
Now you have your user data being saved whenever the user logs in and a null being set for the user data when the user logs out. Plus any page refreshes are handled by the change state event listener or any other instance that might change the user state. For example, you could have other login or logout buttons and the single listener would pick them up.
Next is to deal with the actual process of logging in or out. In your component Vue file (from your example):
async handleSignInSignOutButtonClick() {
if ($store.state.user === null) {
await supabase.auth.signIn(
{ provider: "google" },
{ redirectTo: "where_to_go_on_login" }))
} else {
await supabase.auth.signOut()
$router.push("your_logged_out_page")
}
}
Finally for your button change state to indicate logged in or logged out, you can simply observe the store user state.
<button v-if="user">Sign Out</button>
<button v-else>Sign In</button>
This way your button will update whenever the user state changes. The user state changes whenever a user logs in or out, and your code is much more compact and readable.
Once final observation that you may already be doing anyway. I would recommend that you put all of your authentication code into a single file and expose the log in and log out functions for your app use as an export to use in component files. This way everything to do with login and logout is handled in a single location and this code is abstracted away from the component file. If you ever wanted to switch from Supabase you could easily update one or two files and everything else would just keep working.

Auto Sign In with Cognito / Amplify Authenticator Components?

I'm using the Amplify authenticator component (described here) with Cognito User Pools.
Right now, adding the basic authenticator to html takes the user through the following process automatically:
1) Sign up
2) Enter verification code sent to email
3) Sign-in: Re-enter user name and password
This is based on just adding to html:
<amplify-authenticator></amplify-authenticator>
So new users sign up, and then right away need to sign in. It would be better if they were automatically signed-in, so that upon entering their verification code they went right into the app. This would be a common authentication flow.
Is there a way to have automatic sign-in like this while still using the authenticator components?
I see a github discussion about this topic here, but it is not resolved.
I have figured it out for my purposes. The key is that the Amplify components (authenticator, as well as the individual components) give out information about the state after the user takes action. You can listen for this info, and then programmatically sign in the user once you get the "signIn" state.
These are the states, as described here (I am using Angular):
'signUp' //when you want to show signUp page
'confirmSignUp' //the user has probably just submitted info for signing up, and now you want to show the "confirm" page--like the page where they can enter a verification code
'signIn' //this is the key for these purposes. This is when you want to show the sign in page. In the basic authenticator structure, this happens just after the user has been signed up. So this is where you can automatically sign in the user with Auth.signIn, like below.
'confirmSignIn' //I assume this is after user has submitted the sign in form and you want a confirm page to show
'signedIn' //presumably after user has been fully signed in (not confirmed)
'forgotPassword' //presumably when the forgotPassword form loads (not confirmed)
'requireNewPassword' //presumably when user clicks 'reset password' (not confirmed)
So, using Authenticator, you can listen for the 'signIn' event and automatically sign in the user then. Note however that, while this will sign in the user automatically, Authenticator may still show the sign in page while this action is being processed. You can hide this page, or just use the individual Amplify components, like I show in #2 below.
Even better would be if Amplify/Cognito could just have an option for the developer to select automatic sign in after sign up when they set up Cognito. Automatic sign in on sign up would be in line with modern authentication practice, and would be better for user flow. I do not find this option anywhere. If anyone at Amplify/Cognito is listening, please consider adding this feature.
Note: There seem to be some issues that can arise when using these components. For example--with the authenticator, if the user submits info to sign up, but then leaves the page before entering their verification code: the next time they try to access the app they may have trouble, because they can't go through the sign up process again (a user has already been created), but they also can't sign in (because they've never verified). Some more discussion on that is here.
Additionally, I have found error messages to be an issue. These Amplify components automatically show error messages, and some of them from Cognito are technical messages I would never want a user to see (stuff that talks about "user id" and lambda functions, depending on the random error). There may be a way to customize these, like described here, but be sure you test a lot of different scenarios to see what might happen in terms of the error messages.
If these issues prove problematic for you (like they have for me), you may want to use your own forms instead, and then use Auth.signIn() and related Amplify methods, instead of these components.
But, for auto-signing-in with Amplify components, here is code that worked for me:
1. Using Amplify's Authenticator component:
html
<amplify-authenticator></amplify-authenticator>
ts:
import { AmplifyService } from 'aws-amplify-angular';
import Auth from '#aws-amplify/auth';
Export class AuthComponent implements OnInit {
state: any;
newUser: any;
username: any;
password: any;
constructor(public amplifyService: AmplifyService){
this.amplifyService.setAuthState(this.authState) //may be not required
this.amplifyService.authStateChange$ //listening for state changes
.subscribe(authState => {
this.state = authState.state
if (this.state === 'confirmSignUp'){
console.log('user just signed up, now on verify code form')
this.newUser = authState.user
this.password = this.newUser.username
let checkPassword = this.newUser.username.password
if (checkPassword != 'undefined') {
this.password = checkPassword
/*Note here: I have coded it this way because it looks like the authenticator runs two events when the user hits "sign up". In the first event, you can get the user's password, to be used in the Auth.signIn() function below. In the second event, you can only get the user's username (and password would show up as undefined if you grabbed it there). So we need a way to get the password from the first event.*/
}
}
if ((this.newUser) && (this.state === 'signIn')){//this.newUser included because otherwise this event will fire anytime the sign in page loads--even for returning users trying to sign in (who you would not want to sign in automatically)
console.log('user has just finished signing up)
Auth.signIn(this.username, this.password).then(()=>{//there might be more parameters, like email, first name and last name, phone number, etc. here--depends on your Cognito settings
console.log('should be signed in now! You can navigate away from this page')
}).catch((error)=>{
console.log('error here = ' + error.message + ', error code = ' + error.code)
})
}
})
}
}
Like I mentioned above, this will show the "sign in" page while the Auth.signIn() function is processing. To avoid that, you could have an *ngIf, saying hide the page when this.state = "signIn".
2. Using Individual Amplify Auth Components:
In the below, the page will load with the sign up form.
Once the user enters their details and clicks sign up, the page will show the "confirm sign up" form, which is where the user enters a verification code that Cognito has sent to him/her (depending on your Cognito settings).
Then, once the user is signed up, you can get the "signIn" state like above, and automatically sign the user in:
html:
<amplify-auth-sign-up [authState]="authState" *ngIf="showSignUp"></amplify-auth-sign-up>
<amplify-auth-confirm-sign-up [authState]="authState" *ngIf="showVerify"></amplify-auth-confirm-sign-up>
ts:
import { AmplifyService } from 'aws-amplify-angular';
import { AuthState } from 'aws-amplify-angular/dist/src/providers';
import Auth from '#aws-amplify/auth';
Export class AuthComponent implements OnInit {
public authState: AuthState
public newUser: any
public username: any
public password: any
public state: any
public showSignUp = true
public showVerify = false
constructor(public amplifyService: AmplifyService) {
this.authState ={//the individual Amplify components require a state be set
user: null,
state: 'signUp'
}
this.amplifyService.setAuthState(this.authState) //this might not be required
this.amplifyService.authStateChange$ //listening for state changes.
.subscribe(authState => {
this.state = authState.state
if (this.state === 'confirmSignUp'){//get change in state
this.newUser = authState.user
this.username = this.newUser.username
let checkPassword = this.newUser.username.password
if (checkPassword != 'undefined') {
this.password = checkPassword
/*Note here: I have coded it this way because it looks like the authenticator runs two events when the user hits "sign up". In the first event, you can get the user's password, to be used in the Auth.signIn() function below. In the second event, you can only get the user's username (and password would show up as undefined if you grabbed it there). So we need a way to get the password from the first event.*/
}
this.authState ={
user: authState.user,
state: 'confirmSignUp'
}
this.showSignUp = false
this.showVerify = true
}
if ((this.newUser) && (this.state === 'signIn')){//this.newUser included because otherwise this event will fire anytime the sign in page loads--even for returning users trying to sign in (who you would not want to sign in automatically)
console.log('user has just finished signing up)
Auth.signIn(this.username, this.password).then(()=>{//there might be more parameters, like email, first name and last name, phone number, etc. here--depends on your Cognito settings
console.log('should be signed in now! You can navigate away from this page')
}).catch((error)=>{
console.log('error here = ' + error.message + ', error code = ' + error.code)
})
}
})
}
}

Initial authentication check in ReactJS and Redux

Background: I have an app that is showing a list of movies, but what I want to do is to show the list only for users who are authorized (by checking token stored on the local-storage).
so the flow I want to achieve is:
user enters the app main page
check if has token in local storage
if yes check if it is authorized
if authorized = show list, else don't show
so what I do right now is in my main (wrapper) component right after I create my store:
const store = configureStore();
var token = localStorage.get(authConstants.LOCAL_STORAGE_TOKEN_KEY);
if(token){
store.dispatch(actions.checkInitialAuth(token));
}
checkInitialAuth actions is:
export function checkInitialAuth(token){
return dispatch => {
dispatch(requestLogin());
return fetch(authConstants.API_USER_DETAILS, {headers: { 'Authorization' : `Bearer ${token}`}})
.then(function(response){
if(!response.ok){
throw Error(response.statusText);
}
return response.json();
})
.then(function(user){
localStorage.set(authConstants.LOCAL_STORAGE_TOKEN_KEY, token);
dispatch(receiveLogin(user)); // <==========
dispatch(setTopMovies()); // <==========
})
.catch(function(err){
// TODO: handle errors
console.log(err);
});
}
}
so the question is, is it the right place to invoke the initial auth check right after the creating store and right before the element creation?
and now if I have to invoke more actions only if the user is authorized do I have to invoke them in the then inside the checkInitialAuth action? is it the right place to make all the action dispatch calls?
and last one, when the auth is wrong (I changed manually the token to be wrong on the local storage) the console.log(err) is logging as expected but I have also this annoying 401 error in the console, can I somehow avoid it?
thanks a lot!
is it the right place to invoke the initial auth check right after the creating store and right before the element creation?
Yes. Usually this is where all the initialization happens before store is passed to Provider.
is it the right place to make all the action dispatch calls?
This depends a lot on your application logic. You component can check if a user is authenticated itself to display the correct content for 'topMovies'. But nothing stops you from dispatch more actions here.
but I have also this annoying 401 error in the console, can I somehow avoid it?
It is the network request error. It is the response from the server your fecth is contacting. You can hide the error by change your Chrome Dev console filter if you really want to hide them (but why?).

How to get data into React when doing server-side rendering?

I have a web app where users can log in.
After users log in, they are authenticated by using Express and Passport.
The code after successfully authenticating users is as the following.
router.get("/", function(req, res) {
res.render("index", { user: req.user });
});
Now, user's data is stored in user, so if I want to render user's email on the index page, I can do it by user.email. There is no problem so far.
I use React.js as my choice of Javascript library, and the problem arises here.
Let's say that I want to show user's email through React.js on the index page after they logged in, so my code ended up like the following.
var HelloMessage = React.createClass({
render: function() {
return (
<p>
Your current email is {this.props.email}.
</p>
);
}
});
ReactDOM.render(<HelloMessage email={user.email} />, document.getElementById('hello'));
My intention here is that I can retrieve user's email the same way I retrieve on index page without React.js. So, I put user.email as my prop for email, but this clearly didn't work. It ended up returning error message saying Uncaught ReferenceError: user is not defined. Does anyone know how to retrieve data for the email using React.js?