I'm a bit confused on the implementation of the credentials provider and the redirects. The documentation says that the credentials provider doesn't support a callback and its for the OAuth providers. This is fine. However, instead of staying on the page and flashing an error message or even logging in like in this video it redirects to https://localhost/api/auth/callback/[credentials-provider-name]. Which doesn't even include the port I'm working with. If I explicitly set an id it uses that at the end of the url in instead.
This is what I have for the provider
import NextAuth from "next-auth"
import CredentialsProvider from "next-auth/providers/credentials"
export default NextAuth({
// Configure one or more authentication providers
providers: [
CredentialsProvider({
credentials: {
username: { label: "Username", type: "text", placeholder: "someuser69" },
password: { label: "Password", type: "password" },
},
name: "User Pass",
type: "credentials",
async authorize(credentials, req) {
// Add logic here to look up the user from the credentials supplied
return {
id: 2,
name: "user",
email: "user#gmail.com",
}
return null;
}
})
// ...add more providers here
],
callbacks: {
async jwt({ token, account }) {
// Persist the OAuth access_token to the token right after signin
if (account) {
token.accessToken = account.access_token
}
return token
},
async session({ session, token, user }) {
// Send properties to the client, like an access_token from a provider.
session.accessToken = token.accessToken
return session
},
async redirect({ url, baseUrl, }) {
console.log("");
return baseUrl;
},
async signIn({ user, account, profile, email, credentials }) {
return '/home';
}
},
session: {
jwt: true,
maxAge: 30 * 24 * 60 * 60,
},
secret: "CHANGE!!!",
jwt: {
maxAge: 60 * 60 * 24 * 30,
secret: "afdsfi",
},
})
I've looked through the docs and I'm not sure if I'm making some massive oversight, here. But some of my major confusions are:
Where is this callback set and how do I turn in off in the default provider (if possible).
I don't think the authorize function works. If I put a console log in it. It doesn't print to the terminal. So I don't even know if it's being called.
The issue is that I hadn't set the NEXTAUTH_URL variable correctly. The module apparently appends https if the protocol isn't set in the provided address. This is the case whether you are using 127.0.0.1 or localhost. The solution to fixing the callback issues is to pass in the unsecured http protocol if you're using a local address for testing or development purposes like so:
NEXTAUTH_URL='http://127.0.0.1:3001'
Related
I am using next js. I am using next-auth for authentication. it is a MERN stack project.
Question: How can I get jwt token and send it to my backend using next-auth and axios.
(session.jwt) is giving me undefined.
here is my nextauth.js file:
import NextAuth from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';
import { MongoDBAdapter } from '#next-auth/mongodb-adapter';
import clientPromise from '../../../lib/mongodb';
export default NextAuth({
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
],
callbacks: {
session: async ({ session, user }) => {
if (session?.user) {
session.user.id = user.id;
}
return session;
},
},
adapter: MongoDBAdapter(clientPromise),
secret: process.env.JWT_SECRET,
session: {
jwt: true,
maxAge: 30 * 24 * 60 * 60, // the session will last 30 days
},
});
The example in their docs persists the provider (account) access token in the jwt callback and then includes it to the session object:
callbacks: {
async jwt({ token, account }) {
if (account) {
token.accessToken = account.access_token;
}
return token;
},
async session({ session, token, user }) {
if (session?.user) {
session.user.id = user.id;
}
return {
...session,
accessToken: token.accessToken
};
},
}
However, if you want the raw JWT (not the provider access token) and you're server side you can use the getToken function:
export async function getServerSideProps(context) {
const token = await getToken({ req: context.req, raw: true });
// ...
}
Then to use it in your requests, here in the authorization header for example):
const config = {
headers : {
'Authorization' : `Bearer ${token}`
}
}
axios.get('http://web.com/api', config);
I am trying to pass res from my context into a resolver so that I can call context.res.cookie in my signin function and then send an http only cookie. I have the following code which I am not seeing the cookie added on the client but the sign in function is working besides that:
const resolvers = {
Mutation: {
signin: async (_, { email, password }, context) => {
const user = await User.findOne({ email: email });
if (!user) {
throw new Error("No such user found");
}
const valid = bcrypt.compare(password, user.password);
if (!valid) {
throw new Error("Invalid password");
}
const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET,
{
expiresIn: "30m",
});
context.res.cookie("token", token, {
httpOnly: true,
secure: true,
maxAge: 8600,
});
return {
token,
user,
};
},
},
};
I have shortened the above code but originally I am returning the JWT token and mongodb user, I am trying to also add the http cookie of the same token (it will be a different token later when I sepearte access and refresh token).
const server = new ApolloServer({
typeDefs,
resolvers,
context: async ({ req, res }) => {
/* Authentication boiler plate */
return { isAuthenticated, res };
},
});
The above code is just how I am passing the res, not sure if its needed but just in case.
The following is how the function will be called from the front end:
export const Login = () => {
const SIGN_IN = gql`
mutation Signin($email: String!, $password: String!) {
signin(email: $email, password: $password) {
token
user {
id
name
email
}
}
}
`;
const [signIn, { error, loading, data }] = useMutation(SIGN_IN);
const signInFunction = async () => {
signIn({
variables: {
email: email,
password: password,
},
});
};
if (data) {
return <Navigate to="/" />
}
};
So I needed to slightly change both my client and my server to solve my issue. On the client in apollo-client I needed to change my apolloClient from this:
const apolloClient = new ApolloClient({
uri: "http://localhost:3001/graphql",
cache: new InMemoryCache(),
});
to this:
const apolloClient = new ApolloClient({
uri: "http://localhost:3001/graphql",
cache: new InMemoryCache(),
credentials: "include",
});
Now on the server I needed to add cors like this:
const server = new ApolloServer({
typeDefs,
resolvers,
cors: {
origin: "http://localhost:3000",
credentials: true,
},
context: async ({ req, res }) => {
/* insert any boilerplate context code here */
return { isAuthenticated, res };
},
});
Thus passing res to the resolver this way works perfectly fine. However when I was getting the cookie from server now it would get deleted if I refreshed the page thus I needed an explicit expiration date, thus I changed my cookie from:
context.res.cookie("token", token, {
httpOnly: true,
secure: true,
maxAge: 8600,
});
to (24 hour expiration):
context.res.cookie("token", token, {
httpOnly: true,
secure: true,
expires: new Date(Date.now() + 24 * 60 * 60 * 1000),
});
Some notes on this solution: On the client when you add the credentials: "include", you NEED to also add the cors on the backend otherwise nothing will work, however if you remove both they will communicate fine just without cookies. Also if you add the cors and not the include nothing will break but you will not receive the cookies.
Finally this post helped me find the solution, however I did not need to setup express middleware or use apollo-link-http library as you can see above in my solution, however the post may still be helpful.
I'm trying to use ably.io with Angular and Azure Functions using the JWT way of authenticating since it's secure, but I'm having issues with configuring the angular side of it. The use case is for a live auction site to update bids in realtime. There isn't a specific angular tutorial for this so I'm trying to piece it together. Also this code
realtime.connection.once('connected', function () {
console.log('Client connected to Ably using JWT')
alert("Client successfully connected Ably using JWT auth")
});
never throws the alert so I don't think it's working right. I used to have it working where I wasn't using ably JWT, but had the API key on the client-side in a component like this
let api = "<api key>";
let options: Ably.Types.ClientOptions = { key: api };
let client = new Ably.Realtime(options); /* inferred type Ably.Realtime */
let channel = client.channels.get(
"auctions"
);
and I could subscribe to that channel and update auctions accordingly by their id inside ngOnInit()
channel.subscribe(message => {
const auction = this.posts.find(action => {
return action.id === message.data.auctionId;
});
if (auction) {
auction.currentBid = message.data.lastBid;
}
});
but I need to switch this logic for JWT and somehow feed that JWT token into different components as well.
Ably.io JWT tutorial reference
I put the following in my angular login service
login(email: string, password: string) {
const authData: AuthDataLogin = { email: email, password: password };
return this.http
.post<{
token: string;
expiresIn: number;
userId: string;
}>(environment.azure_function_url + "/POST-Login", authData)
.pipe(takeUntil(this.destroy)).subscribe(response => {
//JWT login token. Not Ably JWT Token
const token = response.token;
this.token = token;
if (token) {
console.log('Fetching JWT token from auth server')
var realtime = new Ably.Realtime({
authUrl: "http://localhost:7071/api/AblyAuth"
});
realtime.connection.once('connected', function () {
console.log('Client connected to Ably using JWT')
alert("Client successfully connected Ably using JWT auth")
});
...
}
With my azure function already configured, When I login, the browser console outputs
GET wss://realtime.ably.io/?access_token=<token was here>&format=json&heartbeats=true&v=1.1&lib=js-web-1.1.22
SO it returns my token, but
the alert never happens
I'm not sure how to grab that JWT token that's returned to the browser. I was thinking I could store it in localStorage to share between components and clear out localStorage when user logs out, but I need to be able to subscribe to response and assign the token to a variable, but I didn't see in ably javascript tutorial how to get variable assigned to JWT Token response since it's being called with this syntax.
I appreciate any help with this!
var realtime = new Ably.Realtime({
authUrl: "http://localhost:7071/api/AblyAuth"
});
My azure function looks like
const checkAuth = require('../middleware/check-auth');
var jwt = require("jsonwebtoken")
var appId = '<APP ID>'
var keyId = '<key ID>'
var keySecret = '<key secret>'
var ttlSeconds = 60
var jwtPayload =
{
'x-ably-capability': JSON.stringify({ '*': ['publish', 'subscribe'] })
}
var jwtOptions =
{
expiresIn: ttlSeconds,
keyid: `${appId}.${keyId}`
}
console.log("JwtPayload");
console.log(jwtPayload);
console.log("jwtOptions");
console.log(jwtOptions);
module.exports = function (context, req) {
console.log("INSIDE ABLY AUTH")
// checkAuth(context, req);
console.log('Sucessfully connected to the server auth endpoint')
jwt.sign(jwtPayload, keySecret, jwtOptions, function (err, tokenId) {
if (err) {
console.log("ERR")
console.log(err)
console.trace()
return
}
context.res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate')
context.res.setHeader('Content-Type', 'application/json')
console.log('Sending signed JWT token back to client')
console.log(tokenId)
context.res = {
status: 200,
body: JSON.stringify(tokenId),
headers: {
"Access-Control-Allow-Credentials": "true",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Set-Cookie",
"Access-Control-Max-Age": "86400",
"Vary": "Accept-Encoding, Origin",
"Content-Type": "application/json"
}
};
context.done();
})
}
I'd recommend if you're wanting to intercept the JWT prior to passing it to Ably (so as to verify the contents, and also use the JWT for other components), you make use of authCallback instead of authUrl. You can use a function instead of a direct URL, within which you can call the endpoint, and do anything you like with the response, prior to passing the JWT back to the Ably constructor. I've made a JavaScript example of using the authCallback for normal Token Authentication, but the same principle applies.
As to why you're not seeing the alert, it looks like you're sending an invalid JWT for what Ably is expecting, and thus you're not successfully connecting to Ably. For example, you're specifying 'expiresIn' rather than 'exp'. For a token to be considered valid, it expected certain elements in a very specific structure, see the documentation. I'd recommend for this sort of situation where you're not certain what's breaking that you make use of verbose logging, which you can enable in the connection constructor as "log": 4.
The problem that I am facing is that after setting a Firebase session cookie, I go to another endpoint that needs to be secure, but req cannot find the cookie.
I am following a Firebase tutorial called Manage Session Cookies that describes how to generate a cookie after a user has logged in using the Firebase signInWithEmailAndPassword function. The user idToken is then passed over to a POST request:
First, I am generating a token and sending it to an endpoint:
function signIn() {
var email = document.getElementById('email').value;
var password = document.getElementById('password').value;
firebase.auth().signInWithEmailAndPassword(email, password).catch(function(error) {
var errorCode = error.code;
var errorMessage = error.message;
if (errorCode === 'auth/wrong-password') {
alert('Wrong password.');
} else {
alert(errorMessage);
}
document.getElementById('quickstart-sign-in').disabled = false;
}).then(user => {
// Get the user's ID token as it is needed to exchange for a session cookie.
return user.getIdToken().then(idToken => {
if (firebase.auth().currentUser) {
$.ajax({
method: 'POST',
url: '/login',
data: {'email':firebase.auth().currentUser.email,idToken},
success: function(data) {
//do other things...
}
});
}
});
})
}
The URL endpoint, which is getting the idToken from the prior POST request, creating a session cookie with createSessionCookie, then setting the cookie using res.cookie('session', sessionCookie, options):
exports.postLogin = (req, res, next) => {
// Get the ID token passed
const idToken = req.body.idToken.toString();
// Set session expiration to 14 days.
const expiresIn = 60 * 60 * 24 * 14 * 1000;
var exampleDB = admin.database().ref('exampleDB');
exampleDB.once('value', function(snapshot) {
//unrelated things happening...
//generate randomData...
}).then(function() {
admin.auth().createSessionCookie(idToken, {expiresIn})
.then((sessionCookie) => {
// Set cookie policy for session cookie.
const options = {maxAge: expiresIn, httpOnly: true, secure: true};
res.cookie('session', sessionCookie, options);
res.status(200).send(randomData).end();
}, error => {
res.status(401).send('UNAUTHORIZED REQUEST!');
});
});
};
The problem that I am facing starts here, when I go to another endpoint, /dashboard. The cookie that I had supposedly set cannot be found, and instead I get an error message stating TypeError: Cannot read property 'session' of undefined for my session cookie:
exports.dashboard = (req, res, next) => {
const sessionCookie = req.cookies.session || '';
// a bunch of other code around the session cookie that doesn't even get triggered because of the break in req.cookies.session
}
Am I retrieving the cookie incorrectly? Or have I not set the cookie properly? Or is the cookie somehow not being carried over to this new endpoint, /dashboard, from the page on which the POST to /login happens?
After logging the req to /dashboard I see that I have the following in there, but I don't know if it's from another session for something. If it is from Firebase, I don't know how to access it correctly:
sessionID: 'ublahxARQVljyGRblahPQrei',
session:
Session {
cookie:
{ path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: true },
returnTo: '/dashboard',
flash: {},
_csrfSecret: 'r46blahE+kOzblah5==' },
I believe that you need to use the __session cookie.
When using Firebase Hosting together with Cloud Functions or Cloud
Run, cookies are generally stripped from incoming requests.
source
This has been encountered before in a different form: Firebase Functions : How to store simple cookies to remember an authenticated user
I'm having trouble scraping a website that needs authentication, and is using session cookies. The session requires a request with POST, and the authentication then approves. But when I want to GET the webpage that need authentication, it returns "Unauthorized". I guess I need a way to bring the session cookie with the GET-request, but I don't know how! My dependencies is request-promise(https://www.npmjs.com/package/request-promise).
The code looks like this:
var rp = require("request-promise");
var options = {
method: "POST",
uri: "http://website.com/login",
form: {
username: "user",
password: "pass",
},
headers: {},
simple: false
};
rp(options).then(function(response) {
console.log(response); // --> "Redirecting to login/AuthPage"
request("http://website.com/login/AuthPage", function(err, res, body) {
console.log(body); // --> "Unauthorized"
})
}).catch(function(e) {
console.log(e)
})
I'm guessing you have to put the request in a "Jar" (https://github.com/request/request#requestjar), to be able to reach the next request-URL, but how can I set the request-promise to create a cookie-jar?
Your problem is how to keep the session after authentication.
That means, after logging in by using username and password, the server will return a cookie with an identifier. Then you need to attach that cookie to all your feature requests.
It's simple with request-promise. Just keep tracking session by enabling jar option then use the same request object for all requests.
Let take a look
var request = require("request-promise").defaults({ jar: true });
var options = {
method: "POST",
uri: "http://website.com/login",
form: {
username: "user",
password: "pass",
},
headers: {},
simple: false
};
request(options).then(function(response) {
request("http://website.com/login/AuthPage", function(err, res, body) {
console.log(body);
})
}).catch(function(e) {
console.log(e)
})
Use the following object while making rest calls.
var request = require("request-promise").defaults({jar: true});
To add your own cookies
var tough = require('tough-cookie');
// Easy creation of the cookie - see tough-cookie docs for details
let cookie = new tough.Cookie({
key: "some_key",
value: "some_value",
domain: 'api.mydomain.com',
httpOnly: true,
maxAge: 31536000
});
// Put cookie in an jar which can be used across multiple requests
var cookiejar = rp.jar();
cookiejar.setCookie(cookie, 'https://api.mydomain.com');
// ...all requests to https://api.mydomain.com will include the cookie
var options = {
uri: 'https://api.mydomain.com/...',
jar: cookiejar // Tells rp to include cookies in jar that match uri
};
and then make the call. More details about request-promise:
https://www.npmjs.com/package/request-promise