I have set up NextAuth to authenticate with google OAuth, but my middleware doesn't think I am authenticated.
This is my middleware.js file:
import middleware from 'next-auth/middleware';
export const config = {
matcher: ['/testing-page'],
};
export default middleware({
secret: process.env.NEXTAUTH_SECRET,
callbacks: {
authorized({ req, token }) {
console.log('in the authorized');
console.log(token);
console.log(req.cookies);
if (token) return true;
},
},
});
When I log out the req.cookies I see that, when I am logged in via google and have a valid session in my database, I see a property called next-auth.session-token. This cookie isn't present when I am logged out and I navigate to this protected page, so I would assume this is the property that would authenticate me, but token is null when I log it out. I've heard that the token is referring to a JWT, which I don't have, since I authenticated via google. Is there a built in way for this middleware to check the session and authenticate based on that, or what am I missing that would authenticate me?
Any help is appreciated as I just started learning NextAuth yesterday. Thank you!
Related
I am currently building a web app based on a turborepo (monorepo) in which I want to use Discord OAuth login with next-auth. Therefore I have two modules web and api, where api is my express backend with discord.js. The web app is basically a dashboard for a Discord bot.
I figured that next-auth only provides client side authentication. So my question is how can I validate the OAuth session from the client side in the best manner?
My middleware for express currently looks like this:
function throwUnauthorized(res: Response) {
res.status(401).json({ code: 401, message: 'Unauthorized' });
}
export async function isAuthorized(req: Request, res: Response, next: NextFunction) {
try {
const authorization = req.headers.authorization;
if (!authorization) {
return throwUnauthorized(res);
}
// validate token with Discord API
const { data } = await axios.get('https://discord.com/api/oauth2/#me', {
headers: { Authorization: authorization },
});
// protect against token reuse
if (!data || data.application.id !== process.env.TC_DISCORD_CLIENT_ID) {
return throwUnauthorized(res);
}
// map to database user
let user = await User.findOne({ id: data.user.id });
user ??= await User.create({ id: data.user.id });
data.user.permissions = user.permissions;
req.user = data.user;
next();
} catch (error) {
return throwUnauthorized(res);
}
}
In this approach the Discord OAuth Token would be send via the Authorization header and checked before each request that requires Authorization. Which leads to my problem: The token needs to be validated again causing multiple request to Discord API.
Is there a better way to handle this? Because I need to map Discord user profiles to database profiles. I read that you could try decode the jwt session token from next-auth, but this did not work when I tested it.
Maybe there is a whole different project structure suggested for my project. But I thought I should separate the api and web-app since I would have needed a custom express server because it includes the Discord bot and Prometheus logging functions. I am open for suggestions and your thoughts!
I need a sanity check on what I'm trying to do here.
I want to build a webapp with nextjs where people can log in with discord and as a backend API I want to use a aspnetcore web api.
I got next-auth to work with discord pretty quickly in the frontend. However I'm struggling on how to identify my frontend to my backend.
My plan at the moment is to have my backend create another JWT token and save that somewhere and then use it as the Authorization header in calls to the backend api.
next-auth has callbacks where I can edit the session and the token. So what I plan to do at the moment is just call the backendapi/createJwtToken endpoint, save it to the already existing next-auth token and then into the next-auth session.
Then I could access it anywhere and don't have to refresh until the session is gone.
I can do that with next-auth callbacks
callbacks: {
async session({ session, token, user }) {
session.backendApiToken = token.backendApiToken;
return session;
},
async jwt({ token, account }) {
if (account) { // this fires only on sign in
token.backendApiToken = "ABC - get it from backend/createToken";
}
return token;
},
Is this okay? I know how to create and validate tokens in an aspnetcore api.
Is something unsecure or strange about saving an encoded apiToken in the next-auth token? Or is this absolutely normal?
On a server side I have 2 middlewares - protect (is logged in?) and restrictTo (checks user's role). These middlewares stop users or guests from performing certain actions if they are not allowed to
exports.protect = catchAsync(async (req, res, next) => {
let token;
if (
req.headers.authorization && req.headers.authorization.startsWith("Bearer")
) {
token = req.headers.authorization.split(" ")[1];
}
if (!token) {
return next(new AppError("You are not signed in!", 401));
}
const decodedToken = await promisify(jwt.verify)(
token,
process.env.JWT_SECRET
);
const currentUser = await User.findById(decodedToken.id);
if (!currentUser) {
return next(new AppError("User with such token no longer exists"));
}
req.user = currentUser;
next();
});
exports.restrictTo = (...roles) => {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return next(new AppError("No access", 403));
}
next();
};
};
But how do I protect routes on a client side? If I'm not allowed to post a new note then I should be stopped from going to a /newnote page so I can't see and fill the form.
JWT token is stored in cookies with httpOnly flag. So I can't access the token from a Vue router. Store a user's role in Vuex? Then how do I synchronize the token state in cookies and in Vuex? If my token has been destroyed on a server side I still can have it in Vuex until I send a request to a protected endpoint.
Should I request a special auth endpoint for protected routes to check my current role using beforeEach?
Basically, you should add two things:
store the current authenticated user. By default, authUser is null. When someone logs in, authUser is an object with the user’s data. you can store this in Vuex, localStorage, etc.
create an interceptor/middleware in whatever library you are using for your api requests. If at some point you get a 401/403, it means that the current user’s session expired, or he is trying to access a protected area he shouldnt be looking at. Either way, reset the local authUser to null and redirect to login.
In Spa/mobile you dont have to worry too much about this as long as your backend is properly secured. If your authUser logic is correct, then only users with bad intentions will try to reach protected areas, whereas normal users will play by the rules and never hit a page they arent supposed to with their current privileges (assuming the UI is wired up correctly…).
I added authentication for my Express API following this guide and after testing my secret-routes everything seems to work properly. Now my question is how can this be used in an Ember app login page. After receiving the secret token after a successful login how does the browser know you are signed in. How would one log out? How does the ember application know who is signed in? Is there any thing in particular security wise that I should be at tentative to while working on this?
You should use addons to handle most of the heavy lifting for you.
ember-simple-auth-token has setup directions that have you create a login route which will take a username / password and send it to your server for validation. The token response will then be available in your app until the user logs out.
The example looks like
import Controller from '#ember/controller';
import { inject } from '#ember/service';
export default Controller.extend({
session: inject('session'),
actions: {
authenticate: function() {
const credentials = this.getProperties('username', 'password');
const authenticator = 'authenticator:token'; // or 'authenticator:jwt'
this.get('session').authenticate(authenticator, credentials);
}
}
});
You also create the logout route which handles logging out of your app as well as sending any logout request to the server.
If possible you should align your server to the defaults, but you can configure nearly everything.
Authentication Options
ENV['ember-simple-auth-token'] = {
tokenDataPropertyName: 'tokenData'; // Key in session to store token data
refreshAccessTokens: true, // Enables access token refreshing
tokenExpirationInvalidateSession: true, // Enables session invalidation on token expiration
serverTokenRefreshEndpoint: '/api/token-refresh/', // Server endpoint to send refresh request
refreshTokenPropertyName: 'refresh_token', // Key in server response that contains the refresh token
tokenExpireName: 'exp', // Field containing token expiration
refreshLeeway: 0 // Amount of time to send refresh request before token expiration
};
We've been very happy with this addon in production for 3 years and I'd highly recommend it.
I'm working on a MEAN application with authentication using JSON web tokens. basically on every request, I am checking to see if user has a valid token. if so they can go through to the route, otherwise they are returned to login page.
I want to make certain routes /admin/etc... only accessible to logged in users who are also admin. I have set up an isAdmin flag in mongo. I am new to nodejs and wondering what is the best way to check this. Do I do it on the angular side in routes? Or can I somehow create permission-based tokens on authentication? For reference, I am following the code from the MEAN Machine book, in particular here -
https://github.com/scotch-io/mean-machine-code/tree/master/17-user-crm
First, authorization decisions must be done on the server side. Doing it on the client side in Angular.js as you suggested is also a good idea, but this is only for the purpose of improving the user's experience, for example not showing the user a link to something they don't have access to.
With JWTs, you can embed claims about the user inside the token, like this:
var jwt = require('jsonwebtoken');
var token = jwt.sign({ role: 'admin' }, 'your_secret');
To map permissions to express routes, you can use connect-roles to build clean and readable authorization middleware functions. Suppose for example your JWT is sent in the HTTP header and you have the following (naive) authorization middleware:
// Naive authentication middleware, just for demonstration
// Assumes you're issuing JWTs somehow and the client is including them in headers
// Like this: Authorization: JWT {token}
app.use(function(req, res, next) {
var token = req.headers.authorization.replace(/^JWT /, '');
jwt.verify(token, 'your_secret', function(err, decoded) {
if(err) {
next(err);
} else {
req.user = decoded;
next();
}
});
})
With that, you can enforce your authorization policy on routes, like this:
var ConnectRoles = require('connect-roles');
var user = new ConnectRoles();
user.use('admin', function(req) {
return req.user && req.user.role === 'admin';
})
app.get('/admin', user.is('admin'), function(req, res, next) {
res.end();
})
Note that there are much better options for issuing & validating JWTs, like express-jwt, or using passport in conjunction with passort-jwt