I'm fairly new to both SvelteKit & Supabase and have done a bit of experimentation on my own, but I would like to know what is the best way to implement user authentication with both of these technologies.
I currently have the following in my __layout.svelte file so that every page on my web application will have access to the session but I'm not sure how to implement persistent user authentication with a cookie.
If anyone can guide me and future users that would be awesome!
<script>
import supabase from "$lib/api";
import { page, session } from '$app/stores';
import { browser } from '$app/env';
if(browser){
$session = supabase.auth.session();
supabase.auth.onAuthStateChange((event, sesh) => {
$session = sesh;
});
}
</script>
It is definitely not clear how to do this. I tried to make it easy and put it into a user readable store:
export const user = readable<any>(null, (set) => {
set(supabase.auth.user());
const unsubscribe = supabase.auth.onAuthStateChange((_, session) => {
session ? set(session.user) : set(null);
});
return () => {
unsubscribe.data.unsubscribe();
}
});
Now you can just use $user anywhere to get the session, or see if there is a session.
J
The supabase project has released an example repo and a QuickStart Guide which helps by abstracting away some of the boilerplate. There's quite some modifications one needs to add so I won't re-paste all the information here as it wouldn't make sense without looking at the full solution. It's all based on a helper library https://supabase.com/docs/guides/auth/auth-helpers/sveltekit
Related
I'm trying to add a simple password validation step to a dashboard that only me and a couple other people would look at. It's not super sensitive data, but I wanted to add a basic protection so that not just anyone can look at it. It's almost definitely not going to be targeted by hackers or anything, so I'm not too concerned about making this completely impenetrable—just good enough.
So I set a secret environment variable on Vercel with a password, and then in getStaticProps() I'm hashing it, and passing the hash as a prop to the page. Then on the client side, I hash the user's entered password, and if the two hashes match, do / show some other stuff.
I imagine that the biggest flaw here is that someone could easily go into the developer tools and just set whatever flag I use to determine if the hash of the entered password and the real one match to true.
Am I correct about that being the biggest security flaw here?
Are there any simple additional steps of "obfuscation" I could add to this to make it a little bit harder to get through, or is there any "frontend-only" / simple serverless way to accomplish what I want?
I'm a frontend developer, so I'm a bit out of my depth with creating proper backend authentication or anything, and I realize this is probably pretty laughably insecure. All the guides I'm finding are for fully-fledged user account creation and authentication and things like that. And Vercel has an option to password protect a deployment, but it's $150 per month and I don't want to pay that for this tiny project.
This is probably a really stupid approach, but again, this isn't super sensitive data, so even if only web developers or people who would know how to read JavaScript can break in, then I'm probably fine with that for now.
Here's what my /pages/index.js file looks like:
import React, { useState } from "react";
var crypto = require("crypto");
const IndexPage = ({ pwHash }) => {
const [enteredPassword, setEnteredPassword] = useState("");
const [correctPassword, setCorrectPassword] = useState(false);
const handlePassword = (e) => {
e.preventDefault();
setEnteredPassword(e.target.value);
};
const checkPassword = (e) => {
e.preventDefault();
const enteredHash = crypto
.createHash("md5")
.update(enteredPassword)
.digest("hex");
if (pwHash === enteredHash) {
alert("success");
setCorrectPassword(true);
} else {
setEnteredPassword("");
}
};
return (
<div>
{correctPassword && <div>SECRET STUFF</div>}
<div>
<h1>
Testing page
</h1>
<form onSubmit={checkPassword}>
<input
type="password"
name="password"
value={enteredPassword}
onChange={handlePassword}
/>
</form>
</div>
</div>
);
};
export async function getStaticProps() {
const pw = process.env.PW;
const hash = crypto.createHash("md5").update(pw).digest("hex");
return {
props: {
pwHash: hash,
},
};
}
export default IndexPage;
If you have a custom domain for this site (anything other than the default *.vercel.app), Octauthent can help you password protect your web app.
Octauthent is a no-code solution, and it's free (with premium features).
Here is a detailed tutorial for websites hosted on Vercel
In our VueJS application, we are having few API's which are calling each and every time whenever the page reloads. In those API's. few response will never change and very few will rarely change. I planning to cache those API calls response and store it in a variable and use it whenever needed and reduce the number of requests when page reloads.
I am new to vueJS and not having any idea how to implement it. Is there anyway to achieve this in VueJS or Javascript? Any help would be most appreciated.
Sample HTML code,
<div class="col-sm-6">
<span>Is User Available? {{userInfo[is_user_available]}} </span>
<span> User Type : {{userType}} </span>
</div>
API call will be like below,
created: function () {
this.checkForUser();
},
methods: {
checkForUser: function() {
this.api.call('user_info', { username : this.username })
.then((response) => {
if (response) {
this.userInfo = response;
this.userType = this.userInfo['user_type'];
}
})
.catch((error) => {
this.userInfo.length = 0;
})
}
}
If you store the data in a regular Vuex store you will loose it on page refresh unless you use vuex-persistedstate plugin, which saves the store data on the local storage. (here is a working example)
Elaborating on #Mysterywood answer you can simply store it on local storage by yourself.
You can achieve that by simply doing
get:
const userType = window.localStorage.getItem('userInfo')
set:
window.localStorage.setItem('userInfo', response)
and remove:
window.localStorage.removeItem('userInfo')
There are few ways of doing this depending on how deep you want to go:
If you just want state to persists during the SPA session, you can do so:
Vuex if you would like to store globally accessible state/data. This allows your state to persist regardless of whether the components are destroyed/created.
Store it on your root-level Vue instance. If you're using the Vue CLI, this will be in your main.js. You can do something like so:
new Vue({
// ...
data: {
userType: {}
}
})
You can then access it via this.$root.userType. This is fine for small projects, but generally not recommended as things can get messy very quickly.
There's also EventBus, but again, this can get messy very quickly. EventBus is also deprecated in Vue3.
If you want to cache the response and access them again even after the user close their tab/browser, you will want to look into:
Cookies
localStorage
ServiceWorkers
check this, it can help :client-side storage in vuejs
Client-side storage is an excellent way to quickly add performance gains to an application. By storing data on the browser itself, you can skip fetching information from the server every time the user needs it. While especially useful when offline, even online users will benefit from using data locally versus a remote server. Client-side storage can be done with cookies, Local Storage (technically “Web Storage”), IndexedDB, and WebSQL (a deprecated method that should not be used in new projects).
I'm using Actions On Google / Dialogflow, and I'm trying to make a function that will greet a user by their name if they've used the action before, and if not, will ask for their name. I have tried to map this to the "Welcome intent" through fulfillment, but whenever I try to run the action on the simulator, I get this error:
Error 206: Webhook Error
Which Initially would make sense if this was mapped to another intent, but I'm wondering if I'm getting this error because you can't have a fulfillment with the welcome intent?
Here's the code I'm using in the inline editor which may be the problem:
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request,response) => {
const agent = new WebhookClient({ request, response });
function welcome(conv) {
if (conv.user.last.seen) {
conv.ask(`Welcome back ${name}!`);
} else {
conv.ask('Welcome to The app! My name is Atlas, could I get your name?');
}}
let intentMap = new Map();
intentMap.set('Welcome Intent', welcome);
agent.handleRequest(intentMap);
How come this isn't working? Do I need to implement user login? Do I need to use a function that would write to a firestore databbase?
Thanks for the help or suggestions!
Let's clear a few things up to start:
You can have fulfillment with your welcome intent.
You do not need user login. Although using Google Sign In for Assistant can certainly be used, it doesn't fundamentally change your problem here.
You do not need to use a function that writes to the firestore database. Again, you could use it, but this doesn't change your problems.
The specific reason this isn't working is because the conv parameter in this case contains a Dialogflow WebhookClient rather than an actions-on-google Conversation object.
To get the Conversation object with the parameter you have, you can call conv.getConv(), which will give you an object that has a user parameter. So this may look something like
function welcome(conv) {
let aog = conv.getConv();
if (aog.user.last.seen) {
conv.ask(`Welcome back ${name}!`);
} else {
conv.ask('Welcome to The app! My name is Atlas, could I get your name?');
}}
There are, however, still some issues with this. Most notably, it isn't clear where name will come from. I assume you will get it out of the user store object, but you don't seem to have done this.
For anyone who comes across this question in the future and just wants a straight forward answer without having to search through ambiguous answers / documentation, here is what to do step by step:
note: I ended up using the Google Sign in method, but even if this isn't your goal, i'll post the link to the alternative method.
1) Import the actions on google module. What people / tutorials don't to show is you have to import the library like this (for user login):
const {
dialogflow,
Permission,
SignIn
} = require('actions-on-google')
instead of
const dialogflow = require('actions-on-google')
2) Use this code:
const app = dialogflow({
clientId: '<YOUR CLIENT ID from Actions on Google>',
});
app.intent('Start Signin', conv => {
conv.ask(new SignIn('To get your account details'));
});
app.intent('Get Signin', (conv, params, signin) => {
if (signin.status === 'OK') {
const payload = conv.user.profile.payload;
conv.ask(`Welcome back ${payload.name}. What do you want to do next?`);
} else {
conv.ask(`I won't be able to save your data, but what do you want to do next?`);
}
});
This function will ask the user for a login, and next time you invoke the intent, it will say "Welcome back name", because google automatically saves it.
Here's the link to the alternative method:
I'm struggling to initialize my Vuex store with the account details of the logged in user from localStorage.
I've been through as many examples as I can of Auth using Nuxt, and none of them demonstrate how on the client side to pull an authToken from localStorage to re-use with subsequent API requests when the user refreshes the page or navigates to the App
Disclaimer: I'm not using Nuxt in SSR (this might affect your answer).
What is annoying is that I can actually load from localStorage and initialize my state but then it gets overwritten. I'll show you what I mean with this small code example:
buildInitialState () {
if (!process.server) {
// Query the localStorage and return accessToken, refreshToken and account details
return {accessToken: <valid-string>, refreshToken: <valid-string>, account: <valid-json-blob>}
} else {
// Return "empty map", we use the string "INVALID" for demonstration purposes.
return {accessToken: "INVALID", refreshToken: "INVALID", account: "INVALID"}
}
}
export const state = () => buildInitialState()
If I put a breakpoint on buildInitialState I can see that I correctly initialize the state with the value of localStorage, i.e. I get the accessToken and refreshToken, etc.. back.
All seems well.
But then .....in another part of the program I'm using Axois to send requests, and I use an axios interceptor to decorate the request with the accessToken. To do this I have to stick it into a plugin to get access to the store.
Something like so:
export default ({ app, store }) => {
axios.interceptors.request.use((config) => {
const accessToken = _.get(store.state, ['account', 'accessToken'])
if (accessToken) {
config.headers.common['x-auth-token'] = accessToken
}
return config
}, (error) => Promise.reject(error))
}
Here the store is closed over in the arrow function supplied to axios so when I go to send the request it sees if there is a valid accessToken, and if so then use it.
BUT, and here's the kicker, when a request is made, I look at the store.state.account.accessToken and low and behold its been reinitialized back to the value of "INVALID".
What? Que?
It's almost like the store was reinitialized behind the scenes? Or somehow the state in the plugin is "server side state"?? because if I try and log buildInitialState I don't get any messages indicating that the path that produced a map with INVALID is being run.
Basically, I don't thoroughly understand the initialization pathway Nuxt is taking here at all.
If any Nuxt masters could help me out understand this a bit more that would be great, it's turning into a bit of a show stopper for me.
Essentially! All I want to be able to do is save the user so that when they refresh their page we can keep on running without forcing them to re-login again....I thought that would be simple.
Thanks and regards, Jason.
I've solved this with a bit of experimentation and comments from other posters around what is called SSR and SPA.
Firstly, this https://github.com/nuxt/nuxt.js/issues/1500 thread really helped me and the final comment from #jsonberry steered my mind in the right direction, away from fetch and asyncData.
I finally had a bit more of an understanding of how NUXT.js was separating SSR and SPA calls.
I then tried #robyedlin suggestion of putting localStorage initialization in the created() method for my main layout/default.vue page.
While I made progress with that suggestion it turns out created() is also called SSR and I was still trying to initialize my store from credentials that weren't accessible.
Finally, moving the initialization to mounted() did the trick!
So in summary:
My account store is left alone, I don't try and initialize it when it is created (it's just overwritten at some point when the SSR stuff runs)
On mounted() in layout/defualt.vue I read from localStorage and initialize the account store so I can start making API requests with the appropriate accessToken.
That seems to have done the trick.
I'm trying to find out what provider my user is using, and I have no idea how! the language that I use is vanilla javascript.
There's a very similar question:
Firebase Auth - get provider ID
but this only works in swift 3...
As the linked answer and Hareesh said, you will need to loop over the providerData array since a user can be signed in with multiple providers. So:
user.providerData.forEach(function(providerData) {
console.log("Signed in with "+providerData.providerId);
});
For a working example see: https://jsbin.com/vuzafuq/edit?html,js,output (check the JavaScript console for the actual providers)
I have tried multiple providers.
If you want to get all providers user have logged in, you can use user.providerData.
But the order of providerData is not based on the desc order by user log in time which the Swift SDK claimed.
This doesn't apply to Javascript SDK.
If you want to get current provider, you can decode the idToken which contain current logged in provider sign_in_provider.
The format would be something like this
{
.....
"email": ....,
"email_verified": true,
"firebase": {
"identities": {
"google.com": [
....
],
"email": [
....
]
},
"sign_in_provider": "password"
}
}
You could tried to decode by https://jwt.io/ or using npm package jsonwebtoken to decode.
Test on the firebase 7.15.0 and hope this works for you.
Firebase provides the currently signed-in provider information inside the user object like below (I have used the AngularFire Framework Library for the below example).
For the web platform, please refer to the following code:
import { AngularFireAuth } from '#angular/fire/auth';
import 'firebase/auth';
export class AuthenticationService {
constructor(private auth: AngularFireAuth) { }
getCurrentUser(){
this.auth.onAuthStateChanged((user) => {
if (user) {
// User is signed in.
user.getIdTokenResult()
.then((idTokenResult) => {
let currentProvider = idTokenResult.signInProvider;
// The result will look like 'google.com', 'facebook.com', ...
});
} else {
// No user is signed in.
}
});
}
}
For more details, check out the official doc here.
2022 Answer (Firebase v8)
I was looking for a simple little JS snippit and couldn't find one. Maybe this will help you:
// Gets the user's sign-in method
let signInMethod =
firebase.auth().currentUser?.providerData[0]?.providerId;