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'));
}
}));
Related
I'm trying to set up an instagram feed (just images and links) of a public instagram account for my Nextjs app.
I know I need to use the Instagram Basic Display API and get a Long-Lived Access Token but it expires after 60 days and I don't want to have to manually refresh it. Does anyone know a good, preferably free, way of doing this automatically?
I have looked at instagram-token-agent but that setup uses Heroku and an add-on that costs $30 a month which seems high.
Any ideas or links would be really helpful, thanks!
I eventually ended up using Google Cloud Secret Manager.
Overview: Secret Manager stores long-lived token and every rotation triggers a pub/sub that then triggers a cloud function. The cloud function refreshes the token for a new one and then adds a new version to the secret.
Create New Secret
Name it "instagram-token" and add your long lived token as the secret value. For now leave everything else default and create secret.
Create a service account for secret manager
In your terminal:
gcloud auth login
then
gcloud beta services identity create --service "secretmanager.googleapis.com" --project "YOUR_GCP_PROJECT_ID"
It may ask you to install gcloud beta commands.
IMPORTANT: Make sure you note down the full name of the service account returned in the terminal. If you have lost it, run the same command again.
Create pub/sub topic
Create a new topic and name it "instagram-token-refresh", untick 'add a default subscription'.
Give secret manager permission to publish pub/sub
In your new pub/sub topic go to permissions -> Add Principle.
Search and add the service account name added above. service-{id}#gcp-sa-secretmanager.iam.gserviceaccount.com. Add the new role Pub/Sub Publisher
Add rotation and pub/sub to secret
Go to your "instagram-token" secret and "edit secret".
Rotation -> custom -> every 50 days
Notifications -> Add Topic -> Select "instagram-token-refresh"
Save
Now every 50 days your "instagram-token-refresh" pub/sub will be triggered.
Create Cloud Function
Search cloud functions -> enable -> create cloud function
Function name: "Refresh-Instagram-Token"
Trigger: pub/sub -> Select "instagram-token-refresh"
click next
Entry Point: "refreshInstaToken"
Edit files:
You might need to enable to cloud build API
package.json
{
"name": "refresh-instagram-token",
"version": "0.0.1",
"dependencies": {
"#google-cloud/pubsub": "^0.18.0",
"#google-cloud/secret-manager": "^3.10.1",
"axios": "^0.24.0"
}
}
index.js
// Import the Secret Manager client
const { SecretManagerServiceClient } = require("#google-cloud/secret-manager");
const axios = require('axios');
// name of function is the same as entry point
exports.refreshInstaToken = async (event, context) => {
// check pub/sub message is rotation to prevent infinte looping
const event_type = event && event.attributes.eventType;
//allowing SECRET_VERSION_ENABLE lets you manually trigger this function by disabling the secret and then enabling it (rather than waiting for rotation trigger)
if (event_type != "SECRET_ROTATE" && event_type != "SECRET_VERSION_ENABLE") {
return null;
}
// secret name
const parent = event.attributes.secretId;
const name = parent + "/versions/latest";
// Instantiates a client
const client = new SecretManagerServiceClient();
// get latest secret
const [version] = await client.accessSecretVersion({
name: name,
});
// Extract the payload as a string.
const secret = version.payload.data.toString();
// refresh token
const requesturl = `https://graph.instagram.com/refresh_access_token?grant_type=ig_refresh_token&access_token=${secret}`;
const response = await axios.get(requesturl);
const data = await response.data;
// data = {"access_token", "token_type", "expires_in"}
// check access_token isn't null
if (data && data.access_token) {
// Payload is the plaintext data to store in the secret
const newSecret = Buffer.from(data.access_token, "utf8");
// add new secret version (the refreshed token)
const [newVersion] = await client.addSecretVersion({
parent: parent,
payload: {
data: newSecret,
},
});
console.log(`Added new secret version ${newVersion.name}`);
// get new secret version number
let newVersionN = newVersion.name.split("/");
newVersionN = newVersionN[newVersionN.length - 1];
if (newVersionN > 1) {
// if is a second version delete one before it
const nameToDestroy = parent + "/versions/" + (newVersionN - 1);
const [deletedVersion] = await client.destroySecretVersion({
name: nameToDestroy,
});
console.info(`Destroyed ${deletedVersion.name}`);
}
}
};
Adding/Accessing Secrets Ref
Consume event notifications with Cloud Functions Ref
Give cloud functions permissions to Secret
Go to your secret -> permission
Add -> {project-id}#appspot.gserviceaccount.com
Add role "Secret Manager Admin"
Accessing Secret Manager from service account
Create new service account name "instagram-token".
In new service account -> keys -> add keys -> save to desktop
Go to your secret -> permission -> add -> "instagram-token...gserviceaccount.com" and give the role of "Secret Manager Secret Accessor"
Setup credentials environment variable
create .env.local file in next js root directory
add new empty value GOOGLE_APPLICATION_CREDENTIALS=
Convert JSON file to Base64 key and copy to clipboard MAC
openssl base64 < /Users/{username}/Desktop/service-account.json | tr -d '\n' | pbcopy
Convert JSON file to Base64 WINDOWS
certutil -encode service-account.json encoded.txt
paste to variable so you will have something like GOOGLE_APPLICATION_CREDENTIALS=faGdfdSytDsdcDg...
Authenticating GCP in Next.js
Install #google-cloud/secret-manager npm i #google-cloud/secret-manager
const {
SecretManagerServiceClient
} = require("#google-cloud/secret-manager");
export const getInstagramToken = async() => {
// parse your base 64 env variable to a JSON object
const credentials = JSON.parse(
Buffer.from(process.env.GOOGLE_APPLICATION_CREDENTIALS, "base64").toString()
);
// TO DO -> CHANGE
const projectId = "eleanor-daisy";
const secretId = "instagram-token";
// set up credentials config
const config = {
projectId,
credentials,
};
// init secret manager with credentials
const client = new SecretManagerServiceClient(config);
const secretName = `projects/${projectId}/secrets/${secretId}/versions/latest`;
// Access the secret.
const [accessResponse] = await client.accessSecretVersion({
name: secretName,
});
const instaToken = accessResponse.payload.data.toString("utf8");
return instaToken;
};
Add GOOGLE_APPLICATION_CREDENTIALS and key to vercel when deploying.
Done! I might make a video tutorial on this as there's not much out there, let me know if that would be helpful :)
I want to set up custom claims to a certain number of users let's say 5 users would be admins on my website. I want these 5 users to be able to log in through the login page which would redirect them to the dashboard.
but I still don't fully understand the concept of the custom claims and how to use them and firebase documentation is limited with examples.
In their example they show that I can pass a uid that I want to assign a custom claim to, but how is this supposed to be a variable when i want certain users uid's from my firestore database Users collection to be admins and have a custom claim, in other words, where would I put this code or how would I assign a custom claim to more than one user at a time and how and where would this code be executed.
if anyone can give me an example of how I would make this work.
here is what I did:
created a firebaseAdmin.js file:
var admin = require("firebase-admin");
// lets say for instance i want these two users to be admins
//2jfow4fd3H2ZqYLWZI2s1YdqOPB42
//2jfow4vad2ZqYLWZI2s1YdqOPB42 what am i supposed to do?
admin
.auth()
.setCustomUserClaims(uid, { admin: true })
.then(() => {
// The new custom claims will propagate to the user's ID token the
// next time a new one is issued.
});
I honestly don't know what to do from here.
Custom Claims can only be set from a privileged server environment via the Firebase Admin SDK. The easiest ways are either using a Node.js script (running the Admin SDK) or a Cloud Function (which also uses the Admin SDK).
Let's look at the example of a Callable Cloud Function that you call from your front-end (and in which you could check the UID of the user who is calling it, i.e. a Super Admin).
exports.setAdminClaims = functions.https.onCall(async (data, context) => {
// If necessary check the uid of the caller, via the context object
const adminUIDs = ['2jfow4fd3H2ZqYLWZI2s1YdqOPB42', '767fjdhshd3H2ZqYLWZI2suyyqOPB42'];
await Promise.all(adminUIDs.map(uid => admin.auth().setCustomUserClaims(uid, { admin: true })));
return { result: "Operation completed" }
});
A Node.js script would be similar:
#!/usr/bin/node
const admin = require('firebase-admin');
admin.initializeApp({
credential: admin.credential.cert(".....json") // See remark on the private key below
});
const adminUIDs = ['2jfow4fd3H2ZqYLWZI2s1YdqOPB42', '767fjdhshd3H2ZqYLWZI2suyyqOPB42'];
Promise.all(adminUIDs.map(uid => admin.auth().setCustomUserClaims(uid, { admin: true })))
.then(() => {
console.log("Operation completed")
})
You must generate a private key file in JSON format for your service account , as detailed in the doc.
Then, when the Claims are set, you can access these Claims in your web app, and adapt the UI (or the navigation flow) based on the fact the user has (or not) the admin claim. More detail here in the doc.
I'm new to javascript, node.js (or backend at all). I am trying to create a controller for the login page requests and I am confused about getting data from the MYSQL table and User Authentication and working with JWT package !
In my Controller, I first check if the user input is available in the user table (with a simple stored procedure), then I compare the database password and the user input, after this I want to create a token and with limited time. (I have watched some tutorial videos about JWT and there is no problem with it), my main problem is to figure out how to write a proper controller with this functions?
I have 2 other questions:
1.Is it the right and secure way to get data from MySQL table inside the route? Or should I create a JS class for my controller? (I'm a bit confused and doubtful here)
2.Assuming that comparePassword() returns true, how can I continue coding outside of the db.query callback function scope? Because I have to execute comparePasssword() inside db.query callback
loginController.js :
const { validationResult } = require('express-validator');
const bcrypt = require('bcrypt');
const db = require('../../sqlConnection')
let comparePassword = (dbPass, inputPass) => {
bcrypt.compare(inputPass, dbPass, function(err, result) {
console.log(result)
});
}
// for get request
exports.getController = (req, res) => {
res.send('login')
}
// for post request
exports.postController = (req, res) => {
let errors = validationResult(req)
if(!errors.isEmpty()) {
res.status(422).json({ errors: errors.array() })
}
// find data from MYSQL table
let sql = `CALL findUser(?)`
db.query(sql, [req.body.username], (err, res) => {
if(err) console.log(err)
//console.log(Object.values(JSON.parse(JSON.stringify(res[0]))))
var data = JSON.stringify(res[0])
data = JSON.parse(data).find(x => x)
data ? comparePassword(data.password, req.body.password) : res.status(400).send('cannot find
user')
})
res.send('post login')
}
login.js :
const express = require('express')
const router = express.Router()
const { check } = require('express-validator');
const loginCont = require('../api/controllers/loginController')
router.route('/')
.get(
loginCont.getController
)
.post(
[
check('username').isLength({min: 3}).notEmpty(),
check('password').isLength({min: 4}).notEmpty()
],
loginCont.postController
)
module.exports = router
In my point of view, looks like there is no easy answer for your question so I will try to give you some directions so you can figure out which are the gaps in your code.
First question: MySQL and business logic on controller
In a design pattern like MVC or ADR (please take a look in the links for the flow details) The Controllers(MVC) Or Actions(ADR) are the entry point for the call, and a good practice is to use these entry points to basically:
Instantiate a service/class/domain-class that supports the request;
Call the necessary method/function to resolve what you want;
Send out the response;
This sample project can help you on how to structure your project following a design pattern: https://riptutorial.com/node-js/example/30554/a-simple-nodejs-application-with-mvc-and-api
Second question: db and continue the process
For authentication, I strongly suggest you to take a look on the OAuth or OAuth2 authentication flow. The OAuth(2) has a process where you generate a token and with that token you can always check in your Controllers, making the service a lot easier.
Also consider that you may need to create some external resources/services to solve if the token is right and valid, but it would facilitate your job.
This sample project should give you an example about how to scope your functions in files: https://github.com/cbroberg/node-mvc-api
Summary
You may have to think in splitting your functions into scoped domains so you can work with them in separate instead of having all the logic inside the controllers, then you will get closer to classes/services like: authenticantion, user, product, etc, that could be used and reused amount your controllers.
I hope that this answer could guide you closer to your achievements.
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}
I'm trying to figure out the best way to structure a React / Redux app that will primarily use a swagger client for api access.
The problem is I'm not entirely sure where to store a reference to the swagger client. After logging in and obtaining a JWT auth token, I need to tell all subsequent requests to add the authorize header. With axios this is trivial because it persists it's headers until told otherwise. It doesn't appear the swagger client does this. So ideally, I would create a swagger client once upon login, add the header info and just reference it for all future requests (that way too it only fetches the schema json once in a single page application).
Since I'm doing this in the context of an action, would it be best to store the Swagger client in the Redux store (and how would I accomplish that)? Or would I create a static instance of it outside of Redux?
// app init
const createStoreWithMiddleware = applyMiddleware(promise)(createStore);
const store = createStoreWithMiddleware(reducers);
export const swaggerClient = { instance: authService.createFromState().then(() => {
ReactDOM.render(
<Provider store={store}></Provider>
...
);
});
do some login stuff, create swagger client:
// redux action
import { swaggerClient } from '../index';
// ... do login, get bearerToken
Swagger({
url: 'https://localhost/swagger/v1/swagger.json',
requestInterceptor(req) {
req.headers['Authorization'] = `Bearer ${bearerToken}`;
return req;
}
}).then((client) => {
// store reference for all future ajax calls
swaggerClient.instance = client;
});
and in case the page is refreshed, we need to rebuild the swagger client from the bearerToken in local storage
// authService
import { swaggerClient } from '../index';
function createFromState() {
// if authentication is known from localstorage, we can rebuild
// a swagger client
if(isAuthenticated()) {
const authentication = getAuthentication();
return Swagger({
url: 'https://localhost/swagger/v1/swagger.json',
requestInterceptor(req) {
req.headers['Authorization'] = `Bearer ${authentication.bearerToken}`;
return req;
}
}).then((client) => {
swaggerClient.instance = client;
return client;
});
}
}
I'm a little confused if this is the right direction, probably a newbie question. Having to wait for the swagger client to load while restoring from localstorage seems a kinda crazy way to do this (to prevent race conditions on future calls).