How to verify user email while using GraphQl and Node.js - javascript

I'm trying to implement user email verification in my pet project with GraphQL and Node.js.
I already have signUp resolver which sends verification token but I've just understood that when a user clicks a link there is no way to send data from an email to the next GraphQL resolver which would use the token and verify the email.
So the question is: shall I make REST endpoint /verify to do the work or there is a way to use /graphql endpoint

If you use a separate /verify endpoint, you'll most likely want to also redirect the user back to your site after processing the request. One approach would be to effectively reverse this flow, linking to your website and then having your page make the necessary GraphQL request.
Alternatively, you can invoke your verify resolver through a link in the email. express-graphql will handle both POST and GET requests. There's a couple of things to keep in mind with this approach though:
It will only work with queries, so your "verify" field will need to be on the Query type
The request will work in a browser context, but will flat out fail if you call it from inside, for example, GraphiQL
Here's a basic example:
const typeDefs = `
type Query {
verify: Boolean # Can be any nullable scalar
}
`
const resolvers = {
Query: {
verify: (root, args, ctx) => {
// Your verification logic
ctx.res.redirect('https://www.google.com')
}
}
}
const schema = makeExecutableSchema({ typeDefs, resolvers })
app.use('/graphql', graphqlHTTP((req, res) => ({
schema: MyGraphQLSchema,
graphiql: false,
// Inject the response object into the context
context: { req, res },
})))
app.listen(4000)
You can then just navigate to this url in your browser:
http://localhost:4000/graphql?query={verify}

Related

Is there a better way of implementing this without multiple fetch request?

This is login component from a react application and I am trying to do a simple authentication login with Firebase as my real time database.
My first approach is to execute a fetch request (GET) to see if there is any existing user. Afterwhich, if the password do match with the user, I want to update the "isLoggedIn" field to true. In order to achieve the following, another fetch request (PATCH) was made.
Therefore, I am wondering if it is a bad practice to have multiple fetch request in one function and would like to know if there is a more efficient or better way of implementing this simple application?
const loginHandler = async (userName, password) => {
const url = `insert_url_link_here/users/${userName}.json`;
try {
const response = await fetch(url);
const user = await response.json();
if (user.password === password) {
await fetch(url, {
method: "PATCH",
body: JSON.stringify({ isLoggedIn: true }),
});
}
} catch (error) {
console.log(error);
}
This is the schema of my database.
--users:
eric:
isLoggedIn: false
password: "321"
test:
isLoggedIn: false
password: "222"
You're accessing the Firebase Realtime Database through its REST API, which implements simple CRUD operations, and there's no way to combine a read and a write operation into a single operation there.
The only other relevant operation is the conditional write, which you can use to only write data if it hasn't been modified since you read it. You can use this to implement an optimistic compare-and-set like transaction mechanism, but given that you're toggling a boolean it doesn't seem to apply here.
If you're on an environment that supports it, you may want to consider using the Firebase SDK for that platform, as the SDKs typically use a web socket to communicate with the server, which often ends up being a lot more efficient than performing multiple individual HTTP requests.
I suggest using a Firebase client implementation and looking at the Firebase documentation. There are well described and explicit examples in various languages:
https://firebase.google.com/docs/auth/web/start
Besides, I don't see the point of storing passwords or login state on your side since Firebase Auth already does this for you in a secure way.

Error using AWS Cognito for authentication with Hasura

i'm having some problems using lambda enviroment.
Looking to set a function that make a mutation to Hasura so I can relate Auth users of Cognito with my app information.
I set the following function Post Authentication in Lamba but it does not work.
function Add(event, context, callback) {
const userId = event.user_id;
const hasuraAdminSecret = "xxx";
const url = "xxx";
const upsertUserQuery = `
mutation($userId: String!){
insert_RegistroAnimal_users(objects: [{ id: $userId }], on_conflict: { constraint: users_pkey, update_columns: [] }) {
affected_rows
}
}`
const graphqlReq = { "query": upsertUserQuery, "variables": { "userId": userId } }
request.post({
headers: {'content-type' : 'application/json', 'x-hasura-admin-secret': hasuraAdminSecret},
url: url,
body: JSON.stringify(graphqlReq)
}, function(error, response, body){
console.log(body);
callback(null, user, context);
});
}
Followed this tutorial : https://hasura.io/docs/latest/graphql/core/guides/integrations/aws-cognito.html#introduction
What do you think is wrong with the code?
I don't think anything is wrong with the code, but to make it work with Cognito you'd need to provide your Hasura setup with a JWT claims function as shown in that same guide, https://hasura.io/docs/latest/graphql/core/guides/integrations/aws-cognito.html#create-a-lambda-function-to-add-claims-to-the-jwt. If you'd like to do it as the guide suggests, you need to create a lambda function like so;
exports.handler = (event, context, callback) => {
event.response = {
"claimsOverrideDetails": {
"claimsToAddOrOverride": {
"https://hasura.io/jwt/claims": JSON.stringify({
"x-hasura-user-id": event.request.userAttributes.sub,
"x-hasura-default-role": "user",
// do some custom logic to decide allowed roles
"x-hasura-allowed-roles": ["user"],
})
}
}
}
callback(null, event)
}
You then need to pick this function as the PreTokenGeneration trigger from your user pool settings. Then AWS Cognito will trigger this function before token generation, allowing you to add Hasura required claims to your token.
The next step is to tell Hasura where to lookup for the JWT claims by providing HASURA_GRAPHQL_JWT_SECRET during the setup, which is essentially an URL pointing to your Cognito setup, generated using the pool id.
Finally, you can obtain the idToken from your user session after a successful login, and pass that token as an Authentication header for your Hasura requests. Described here.
All of these steps were actually described in the guide you linked, but may not be as clear. I believe the reason your current setup does not work is that your Hasura setup is missing the HASURA_GRAPHQL_ADMIN_SECRET, which needs to be the same as the x-hasura-admin-secret you're using in your requests.
Mind you, if you use x-hasura-admin-secret in your app and expose it to your users which gives them admin access, that creates a potential security issue and anyone with that secret can wipe up your data. x-hasura-admin-secret should be reserved for your admin tasks and not used in an app where AWS Cognito authentication is planned to be used.

Approach to modify the database without logging the user?

I have a React frontend with a Node + MySQL backend, I'm sending an email to an user with two buttons to accept or decline a quote. What I'm trying to achieve is to make the buttons in the email modify the database securely without the user having to log into his account. My idea is to have two routes, one that sends the email containing the buttons which will have a url to my website with the jwt token on its parameters, and another for verifying said token and making the changes to the db. Here's some pseudo-code:
app.post("/email-quote", async function (req, res) {
const payload = {
uid: req.body.user.id,
quoteId: req.body.quote.id,
accepted: // true for the accept button, false for the decline button
}
const secret = ?
const token = jwt.sign(payload, secret);
// ...
// Generate and send email with buttons containing the url + the token
});
When the user clicks one of the buttons, I re-direct him to my website and there I can extract the token and verify its validity:
app.get("/verify-email-quote/:token", async function (req, res) {
const decodedJwt = jwt.decode(req.params.token);
const secret = ?
const verifiedJwt = jwt.verify(req.params.token, secret);
if (verifiedJwt) {
// Make the changes to the db
}
});
I wasn't able to find any examples trying to achieve something similar on the web, so I have these questions:
Would a jwt token be a good approach to achieve this?
If yes, what secret should I use to create the token?
If no, what other solutions could I look into?
Yes, you can do it this way.
The secret does not matter. As long as the secret is secret
It doesn't need to be a jwt token. It can just be a normal token. The incentive to using jwt is that you can embed a payload into the token. For your case, it looks like it is exclusively for verification purposes. It doesn't matter in the grand scheme of things, but if you don't have jwt already implemented, there's no need to go through all that extra work just for this use case.

nestjs firebase authentication

I have nestjs application which uses typeorm and mysql. Now I would like to add firebase for authentication handling, i.e for signup, signin, email verification, forgot password etc.
Plans is create user first in firebase, then same user details will be added into mysql user table for further operaiton. So for this I am using customized middleware
#Injectable()
export class FirebaseAuthMiddleware implements NestMiddleware {
async use(req: Request, _: Response, next: Function) {
const { authorization } = req.headers
// Bearer ezawagawg.....
if(authorization){
const token = authorization.slice(7)
const user = await firebase
.auth()
.verifyIdToken(token)
.catch(err => {
throw new HttpException({ message: 'Input data validation failed', err }, HttpStatus.UNAUTHORIZED)
})
req.firebaseUser = user
next()
}
}
}
Full code is available in Github
Problem with above code is that, it always looks for auth token, const { authorization } = req.headers const token = authorization.slice(7)
However, when user first time access application, authorization header always be null.
example if user access signup page we cannot pass auth header.
please let me know how can I modify above code when user access signup page, it allows user create user firebase, then same details can be stored in database.
We can exclude the routes for which you don't want this middleware.
consumer
.apply(LoggerMiddleware)
.exclude(
{ path: 'cats', method: RequestMethod.GET },
{ path: 'cats', method: RequestMethod.POST },
'cats/(.*)',
)
.forRoutes(CatsController);
You can just next() to skip this middleware if there is no authorization. so it s okay to access sign up api when no authorization

Feathers JS authentication with no email

Im looking for an authentication system where the user submits to an enpoint and a jwt is generated at this endpoint, im not sure how to implement this, my client side application does not make use of email address or stored information, it is in fact a dApp. I just need an endpoint that will calculate a value from a supplied seed phrase and a password if the processing of these values goes well ( and it nearly always will unless someone sends junk to the endpoint) then a jwt will be issued.. so far the out of box functionality with feathers cli means that i need to use local strategy and need an email address, I cant find any demos out there on this.. anyone got any pointers ? so far my auth is pretty default
const authentication = require('#feathersjs/authentication');
const jwt = require('#feathersjs/authentication-jwt');
const local = require('#feathersjs/authentication-local');
module.exports = function (app) {
const config = app.get('authentication');
// Set up authentication with the secret
app.configure(authentication(config));
app.configure(jwt());
app.configure(local());
// The `authentication` service is used to create a JWT.
// The before `create` hook registers strategies that can be used
// to create a new valid JWT (e.g. local or oauth2)
app.service('authentication').hooks({
before: {
create: [
authentication.hooks.authenticate(config.strategies)
],
remove: [
authentication.hooks.authenticate('jwt')
]
}
});
};
and heres my service:
// Initializes the `aerAuth` service on path `/userauthendpoint`
const createService = require('feathers-memory');
const hooks = require('./userauthendpoint.hooks');
module.exports = function (app) {
const paginate = app.get('paginate');
const options = {
name: 'userauthendpoint',
paginate
};
// Initialize our service with any options it requires
app.use('/userauthendpoint', createService(options) );
// Get our initialized service so that we can register hooks and filters
const service = app.service('userauthendpoint');
service.hooks(hooks);
};
I am relatively new to feathers but not to building auth systems (in PHP)
The Custom authentication strategy guide and the feathers-authentication-custom plugin probably allow to do what you are looking for.
It also depends on how you want to implement this. You can either use the custom strategy for every service (as in the case of the API key which has to be sent in the header with every request) or just before the /authentication service to allow creating a JWT (the issue here is that it needs to reference a userId or other entityId that exists in the database which you don't have).
The easiest way would be to go with the first options and a custom header (X-DAP-PASSWORD) which could look like this:
const custom = require('feathers-authentication-custom');
app.configure(authentication(settings));
app.configure(custom((req, done) => {
const password = req.headers['x-dap-password'];
if(checkPassword(req.app.get('seedPassphrase'), password)) {
// implement your own custom logic for loading and verifying the user
done(null, user);
} else {
done(new Error('Invalid passphrase'));
}
}));

Categories

Resources