I have created an app using Feathers. I've been using this app for a while. It successfully hosts a blog and some other web pages. However, I've now reached a point where I need to protect some of my routes. For example, I want to have a route for my administrative activitivies (/admin), but I only want specific users to have access.
I know that I need to use the authentication and authorization components. However, at this time, I'm stuck on the authorization. My goal is to authenticate using OAuth via Google. However, to get past my authentication challenge, I'd be happy with just using a hard-coded username / password just to get the /admin route locked down (no, it's not deployed).
At the moment, I have
const app = feathers();
const routes = require('./routes');
app.configure(configuration(path.join(__dirname, '..')));
app.use(compress())
.options('*', cors())
.use(cors())
.use(favicon( path.join(app.get('public'), 'favicon.ico') ))
.use('/public', serveStatic(app.get('public'), staticFileSettings ))
.use(bodyParser.json())
.use(bodyParser.urlencoded({ extended: true }))
.configure(routes)
.configure(hooks())
.configure(rest())
.configure(socketio())
.configure(services)
.configure(middleware)
.configure(authentication())
;
// Setup the authentication strategy.
app.authenticate({
type: 'local',
'email': 'admin#feathersjs.com',
'password': 'admin'
}).then(function(result){
console.log('Authenticated!', result);
}).catch(function(error){
console.error('Error authenticating!', error);
});
My problem is, as soon as I add the block of code with the app.authenticate stuff, I get an error when I start my app. The error says:
TypeError: app.authenticate is not a function
If I remove app.authenticate(...); My app starts fine, but nothing is locked down. In my ./routes/index.js file, I have:
app.use('/admin', function(req, res) {
res.render('admin/index.html', {});
});
Which, renders just fine. It's just not restricted to an authenticated and authorized user. What am I missing? At a bare minimize I'm trying to understand how to get past the app.authenticate error.
In order to protect a route from unauthorized access you need to follow the documented usage of express middleware provided by the feathers-authentication package that is installed when you do feathers generate authentication.
Here's an example of authenticating the /admin route.
const auth = require('feathers-authentication');
app.use(
'/admin',
auth.express.authenticate('jwt'), // <-- this is a strategy, can local/jwt... etc
(req, res, next) => {
console.log("Request for '/admin'...");
res.render('admin');
}
);
Related
I'm trying to add basic authentication to my app using the express-basic-auth. I'm following the instructions on this page, which also says "If a request is found to not be authorized, it will respond with HTTP 401" but my app simply replies with status code 200 when I do not pass any credentials
It seems I've incorrectly configured express-basic-auth. What have I done wrong?
const express = require('express')
const app = express()
const port = 3001
const basicAuth = require('express-basic-auth')
app.use(basicAuth({
users: { 'admin': 'supersecret' },
}))
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
Your code works. I confirmed with a nodejs client app that the basic auth works when given the right username and password and gives back a 401 when not given the right username and password or given no username and password.
The likely explanation is that you're not actually running your latest code and there's some previous server still running that doesn't require the basic auth.
You can verify that you are hitting the right server by adding
app.use((req, res, next) => {
console.log("got new server");
next();
});
before your basic auth middleware and verify that you see this log in the server console when you try your request. If you don't see this new log in the console, then you aren't hitting the newest code.
I'm trying to work on a Nuxt SSR frontend that uses a Django backend with Session Authentication.
I would like to have some SSR pages as well as client rendered pages in my frontend, so i'm using Universal mode.
The problem is that i did not find a working approach to check if a user is authenticated before loading a page, so i can't restrict pages to anonymous users. In order to check if a user is authenticated, Django will check if the request's headers contain a cookie, and according to that return if the user is authenticated or not.
Here is what i tried:
1) Middleware
export default async function ({context, redirect}) {
axios.defaults.withCredentials = true;
return axios({
method: 'get',
url: 'http://127.0.0.1:8000/checkAuth',
withCredentials: true,
}).then(function (response) {
//Redirect if user is authenticated
}).catch(function (error) {
console.log(error)
});
}
Here i'm sending a request to my backend to check if the user is authenticated. The problem is that the middleware is executed from server side, which means there will never be any cookie in the request, even if the user is authenticated. This means that every time i refresh the page, according to the middleware the user is always anonymous, even when the user is authenticated.
2) Plugin
export default function (context, inject) {
if (process.client){
console.log('client')
return axios({
method: 'get',
url: 'http://127.0.0.1:8000/checkAuth',
withCredentials: true,
}).then(function (response) {
//IF AUTHENTICATED, REDIRECT
context.redirect('/')
}).catch(function (error) {
console.log(error)
});
} else {
console.log('server')
}
}
Here i'm trying the same but with a plugin, and i'm "forcing" the plugin to check if the user is authenticated on the backend only when the plugin executes from client side. This works, cookies are sent in the headers and Django receives the cookie, but the problem with this solution is that Nuxt doesn't allow redirecting to other pages from a plugin (https://github.com/nuxt/nuxt.js/issues/4491).
3) Using beforeMount() in Vue
I tried to do that using beforeMount() from my Vue pages, but the problem is that since it will execute AFTER idration, the page will be loaded and after 1/2 seconds the redirect happens, which is kind of ugly.
Is it possible that there isn't a way to do this? Any kind of advice is appreciated
EDIT: the problem is not that i don't know how to code this, the problem is that when Nuxt sends a request to my backend from the server side middleware, the request will not contain any cookie, and because of this my Django backend cannot check the session cookie, which means that the backend cannot check whether or not the user is authenticated. The same code works when the middleware is executed from client side (if i navigate directly to the page instead of refreshing), because the request will contain the cookies.
I'm trying to understand if this is normal or not, but this could be an issue with Nuxt.
I know this a year old question and it was probably about nuxt 2, now nuxt 3 is out and running and I found my self with the same problem and here is how I solved it, just in case someone stumble here just like I did.
With Nuxt 3 server side you can use useFetch with the options headers: useRequestHeaders(['cookie'])
const { data, error, pending, refresh } = await useFetch(api.auth,
{
credentials: "include",
headers: useRequestHeaders(['cookie'])
}
);
There are a few issues you need to be aware of:
_ The cache, if you perform the same request with the same parameters it will return the same cached response (it won't even call the end point API). Try to use the key option with different values or the returned refresh method and check the doc "Data fetching" for more info.
_ The cookies, any cookie generate server side won't be shared with the client side, this means if the API generate a new token or session cookie on server side the browser won't receive those cookies and may generate new ones, this may get you in some 400 - bad request if you use session with CSRF, check this issue for more info.
I do have a working middleware with this
export default ({ redirect, store }) => {
if (store?.$auth?.$state?.loggedIn) {
redirect('https://secure.url')
} else {
redirect('https://login.please')
}
})
Context:
I just split up my existing Next.js/Express app using Lerna, separating the Next.js server (serving the front end on localhost:3000) and my express server (serving the API on localhost:4000). As a result, I had to create an axios instance with the default {baseUrl: localhost:4000}, so my relative path calls will continue to work (ie. axios.post('/api/auth/login', {...})). This was all working before, when the server was serving both the API and the Nextjs app.
Problem:
After this switch, my Authentication flow no longer works properly. After logging in as usual, a page refresh will erase any memory of the user session. As far as I can tell, the Cookie is not being set. I cant see it in my dev-tools, and when the app makes a subsequent call, no cookies are present.
When my app mounts, it makes a call to api/me to see if the user is logged in. If they are, it will respond with the user information to hydrate the app with the users info. If they aren't, it wont do anything special. This request to /api/me no longer contains the persistent cookie set in place by the login call. (dev-tools shows the Set-Cookie header being returned as expected from the original login request)
Possibly Useful information:
Depending on how I run my app, (debugging in VSCode vs running both yarn dev commands in terminal) I will get a CORS error. I am using the cors package with no configuration: app.use(cors())
The call made to /api/me when the application mounts looks like this:
// API.js
`export default axios.create({baseURL: 'http://localhost:4000'})`
// app.js
import API from 'API'
API({
method: 'get',
url: '/api/me'
})
.then(r => {
//...
})
I am setting the cookie using this function
function setCookie(res, token, overwrite, sameSite = true) {
res.cookie(cookieName, token, {
httpOnly: true,
sameSite: sameSite,
secure: process.env.NODE_ENV === 'production',
expires: new Date(Date.now() + cookieExpiration),
overwrite: !!overwrite
})
}
Suspicions
It is some cors policy I'm not familiar with
It is because of sameSite (thought it wouldn't matter)
I create an application that client (vue) runs on localhost:3000 and server (express) on localhost: 8080.
I use passport-google-oauth20 strategy to log in using google and only JWT token.
My Question is:
How do I redirect to a client in a callback strategy so that the receives information about the logged-in user on client side? I'm using in passport jwt, which i send on local-login strategy and there everything works.
At the moment, it looks like this to me:
this.router.get('/v1/auth/google',
passport.authenticate('google', { session: false, scope: ['email', 'profile']})
);
this.router.get('/v1/auth/google/callback',
passport.authenticate('google', { session: false }), (req, res) => {
// let token = sign(req.user);
res.redirect(`http://localhost:3000`);
// how to redirect to the CLIENT,
// who will receive data about the logged-in user?
// how get user data, catch this situation?
});
I read a lot of articles and watched a lot of videos about Laravel passport but still can not understand some things.
I have an application which works with Laravel 5.5 + vueJs. All requests to the back-end are sent via axios. All my routes are located in api.php
Route::middleware('api')->group(function(){
Route::get('/prepare/', 'CompgenApiController#prepareDefault');
Route::post('/replace/', 'CompgenApiController#replaceImage');
Route::get('/replaceall/', 'CompgenApiController#replaceAllImages');
Route::get('/collage/', 'CompgenApiController#collage'); //#todo переделать на POST
Route::get('/generate/', 'CompgenApiController#generate');
Route::post('/upload/', 'CompgenApiController#userUpload');
Route::post('/reupload/', 'CompgenApiController#moderationReupload');
});
Also I have a VK bot that sends requests to the same routes.
At the moment I have some difficulties. For some routes, I need to check that the user is authorized (but this is an optional condition) and if it is true I need to write user id to the database. For me it was a surprise that
Auth :: check
returned false though I was authorized. After some searches I learned that the session that starts after authorization is not connected with the API and I was recommended to use Passport. I can not understand the following things
Do I need to use a passport if requests are sent from my application from vueJs?
How to register users? Do I have to issue my token for each new user?
How can I verify that the user is authorized?
In some cases I need to check that the user is authorized but if it is not so then do not interfere with the request. How can I do that?
Maybe in my case I do not need a passport at all?
Passport is an oAuth2 server implementation, essentially, it allows you to authenticate users by passing a token with each request. If you do not want to authenticate a user, then you do not need to pass the token and passport doesn't get involved.
In terms of a Laravel app, if you are consuming your API from your own frontend, you probably just want to use the implicit grant. Here's how you set that up:
1) Install passport and add it the PassportServiceProvider to config/app.php
2) php artisan migrate to setup the passport migrations
3) php artisan passport:install - to set up your oAuth server
4) In the User model add the Laravel\Passport\HasApiTokens trait, like so:
namespace App;
use Laravel\Passport\HasApiTokens;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use HasApiTokens, Notifiable;
// Other model methods
}
5) Setup the passport routes by adding the following to the boot method of your app\Providers\AuthServiceProviders.php:
\Laravel\Passport\Passport::routes();
6) Change your api driver in config/auth.php to token:
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],
That sets up passport, now all you need to do to allow your app to consume your api is to add the CreateFreshApiToken Middleware to web in app/Http/Kernel.php, which handles all the token logic:
'web' => [
// ..Other middleware...
\Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
],
Now all you need to do to protect a route is to use the auth:api middleware on that route:
Route::middleware('auth:api')->get('/profile','Api\UsersController#edit');
That should all work fine, but you'll also want to register a new user. All you do is add the auth routes as normal to web.php:
Route::post('/login', 'Auth\LoginController#login');
Route::post('/logout', 'Auth\LoginController#logout');
Route::post('/register', 'Auth\RegisterController#register');
You can then simply maks a post request to those routes and Laravel will handle all the token stuff for you.
That handles api routes that require authentication, however, you also mentioned that you want to check if a user is authenticated, but not necessarily lock the route, to do that you can simply use:
Auth::guard('api')->user();
Which will get the authenticated user, so you could do something like:
public function getGreeting(){
$user = Auth::guard('api')->user();
if($user !== null) {
return response()->json(["message" => "Hello {$user->name}"], 200);
}
return response()->json(["message" => "Hello Guest"], 200);
}
That's it. Hopefully, I've covered everything there.