How do I refresh information in NextAuth session? - session

I am currently playing around and building SaaS web application with NextJS, NextAuth, MongoDB and Stripe. My issue is when I change subscribtion of the current user in the database it isn't reflected in the session and I have to logout and login again to see the changes.
import { compare } from 'bcryptjs';
import NextAuth from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
import connectMongo from '../../../database/connection';
import Users from '../../../models/Schema';
export default NextAuth({
providers: [
CredentialsProvider({
name: 'Credentials',
async authorize(credentials, req) {
connectMongo().catch((error) => {
error: 'Connection Failed...!';
});
// check user existance
const result = await Users.findOne({ email: credentials.email });
if (!result) {
throw new Error('No user Found with Email Please Sign Up...!');
}
// compare()
const checkPassword = await compare(
credentials.password,
result.password
);
// incorrect password
if (!checkPassword || result.email !== credentials.email) {
throw new Error('Username or Password does not match');
}
return result;
},
}),
],
session: {
jwt: true,
},
jwt: {
secret: process.env.JWT_SECRET,
},
callbacks: {
async jwt({token, user}) {
if(user) {
token.stripeCustomerId = user.stripeCustomerId;
token.subscribtionRole = user.subscribtionRole;
}
return token;
},
async session({session, token}) {
session.user.stripeCustomerId = token.stripeCustomerId;
session.user.subscribtionRole = token.subscribtionRole;
return session;
}
}
});
I've tried searching the documentation and various articles, but I haven't found anything that helps. I'm new to this and just learning, so there are a few things I don't understand and don't have that much knowledge, so any advice would be appreciated.

Related

Nextjs getServerSideProps does not pass session to component

Good morning. I am using NextJS and get the session in the getServerSideProps block.
I am passing multiple parameters to the return object, however, the session does not pass. All of the other key value pairs works, but when I try to log the session, It comes undefined... I don't understand...
const DashboardPage = ({ session, secret }) => {
const [loading, setloading] = useState(false);
//This does not work
console.log('Session: ', session);
return (
<section className="border-4 border-orange-800 max-w-5xl mx-auto">
<CreateListModal userId={session?.userId} loading={loading} setloading={setloading} />
</section>
)
}
export const getServerSideProps = async context => {
// get sessions with added user Id in the session object
const session = await requireAuthentication(context);
// This works
console.log(session);
if (!session) {
return {
redirect: {
destination: '/signup',
permanent: false
}
}
}
else {
return {
props: {
session: session,
secret: 'Pasarika este dulce'
}
}
}
}
export default DashboardPage;
The purpose of the requireAuthentication function is to create a new session object where I will insert another key value pair that will contain the user id and the session that will be used in the entire app.
In that function I get the user by email that is returned by the session and get the Id from the db. I than return the new session object that looks like this:
{
user: {
name: 'daniel sas',
email: 'email#gmail.com',
image: 'https://lh3.googldeusercontent.com/a/A6r44ZwMyONqcfJORNnuYtbVv_LYbab-wv5Uyxk=s96-c',
userId: 'clbcpc0hi0002sb1wsiea3q5d'//This is the required thing in my app
},
expires: '2022-12-23T08:04:08.263Z'
}
The following function is used to get the data from the database
import { getSession } from "next-auth/react";
import prisma from './prisma'
// This function get the email and returns a new session object that includes
// the userId
export const requireAuthentication = async context => {
const session = await getSession(context);
// If there is no user or there is an error ret to signup page
if (!session) return null
// If the user is not found return same redirect to signup
else {
try {
const user = await prisma.user.findUnique({where: { email: session.user.email }});
if (!user) return null;
// Must return a new session here that contains the userId...
else {
const newSession = {
user: {
...session.user,
userId: user.id
},
expires: session.expires
};
return newSession;
}
}
catch (error) {
if (error) {
console.log(error);
}
}
}
}

Supabase third party oAuth providers are returning null?

I'm trying to implement Facebook, Google and Twitter authentication. So far, I've set up the apps within the respective developer platforms, added those keys/secrets to my Supabase console, and created this graphql resolver:
/* eslint-disable #typescript-eslint/explicit-module-boundary-types */
import camelcaseKeys from 'camelcase-keys';
import { supabase } from 'lib/supabaseClient';
import { LoginInput, Provider } from 'generated/types';
import { Provider as SupabaseProvider } from '#supabase/supabase-js';
import Context from '../../context';
import { User } from '#supabase/supabase-js';
export default async function login(
_: any,
{ input }: { input: LoginInput },
{ res, req }: Context
): Promise<any> {
const { provider } = input;
// base level error object
const errorObject = {
__typename: 'AuthError',
};
// return error object if no provider is given
if (!provider) {
return {
...errorObject,
message: 'Must include provider',
};
}
try {
const { user, session, error } = await supabase.auth.signIn({
// provider can be 'github', 'google', 'gitlab', or 'bitbucket'
provider: 'facebook',
});
console.log({ user });
console.log({ session });
console.log({ error });
if (error) {
return {
...errorObject,
message: error.message,
};
}
const response = camelcaseKeys(user as User, { deep: true });
return {
__typename: 'LoginSuccess',
accessToken: session?.access_token,
refreshToken: session?.refresh_token,
...response,
};
} catch (error) {
return {
...errorObject,
message: error.message,
};
}
}
I have three console logs set up directly underneath the signIn() function, all of which are returning null.
I can also go directly to https://<your-ref>.supabase.co/auth/v1/authorize?provider=<provider> and auth works correctly, so it appears to have been narrowed down specifically to the signIn() function. What would cause the response to return null values?
This is happening because these values are not populated until after the redirect from the OAuth server takes place. If you look at the internal code of supabase/gotrue-js you'll see null being returned explicitly.
private _handleProviderSignIn(
provider: Provider,
options: {
redirectTo?: string
scopes?: string
} = {}
) {
const url: string = this.api.getUrlForProvider(provider, {
redirectTo: options.redirectTo,
scopes: options.scopes,
})
try {
// try to open on the browser
if (isBrowser()) {
window.location.href = url
}
return { provider, url, data: null, session: null, user: null, error: null }
} catch (error) {
// fallback to returning the URL
if (!!url) return { provider, url, data: null, session: null, user: null, error: null }
return { data: null, user: null, session: null, error }
}
}
The flow is something like this:
Call `supabase.auth.signIn({ provider: 'github' })
User is sent to Github.com where they will be prompted to allow/deny your app access to their data
If they allow your app access, Github.com redirects back to your app
Now, through some Supabase magic, you will have access to the session, user, etc. data

OAuth2 Plugin to login using refresh tokens

I have a mobile application where I am trying to authenticate the user using the nativescript-oauth2 plugin with custom providers using Azure B2C. My requirement is that I want to make the user login for the first time to the application using their credentials. After the user has successfully logged in, I need to store the refresh token of the user and use these stored refresh token for authentication when next time the user is logging in to the mobile application. Using the refresh tokens, I want to generate all the tokens again.
I have tried using the refreshTokenWithCompletion() method provided by the plugin but as the document states that this is for refreshing the access tokens(OAuth 2 Plugin for NativeScript).
I too have an issue with fetching fresh token results using "refreshTokenWithCompletion".
below is my code
import { TnsOAuthClient, ITnsOAuthTokenResult, configureTnsOAuth } from 'nativescript-oauth2';
import { AuthProvider } from './auth-provider.service';
import { TnsOaUnsafeProviderOptions } from 'nativescript-oauth2/providers/providers';
#Injectable({
providedIn: 'root'
})
export class AuthService {
private client: TnsOAuthClient = null;
private customProvider: AuthProvider = null;
private customProviderOptions: TnsOaUnsafeProviderOptions = {
openIdSupport: "oid-none",
clientId: "",
redirectUri: "https://b2clogin.com/te/XXXXXXXXXXXXXX/XXXXXX_XXXXX_XXXXX/oauth2/authresp",
scopes: ["openid https://XXXXXXXXXXXXXX.onmicrosoft.com/mobileapi/user_impersonation offline_access"],
clientSecret: "",
customQueryParams: {
"p": "XXXXXX_XXXXX_XXXXX",
"nonce": "defaultNonce",
"response_mode": "query",
"prompt": "login"
}
};
constructor() {
this.customProvider = new AuthProvider(this.customProviderOptions);
configureTnsOAuth([this.customProvider]);
}
public Login(providerType): Promise<ITnsOAuthTokenResult> {
console.log('In Login');
this.client = new TnsOAuthClient(providerType);
return new Promise<ITnsOAuthTokenResult>((resolve, reject) => {
this.client.loginWithCompletion((tokenResult: ITnsOAuthTokenResult, error) => {
if (error) {
console.error("back to main page with error: ");
console.error(error);
// reject(error);
} else {
console.log("back to main page with access token: ");
console.log(tokenResult);
// resolve(tokenResult);
}
});
});
}
public Logout(): Promise<any> {
return new Promise<any>((resolve, reject) => {
if (this.client) {
this.client.logoutWithCompletion((error) => {
if (error) {
console.error("back to main page with error: ");
console.error(error);
reject(error);
} else {
console.log("back to main page with success");
resolve();
}
});
}
else {
console.log("back to main page with success");
resolve();
}
});
}
public RefreshAccess() {
this.client.refreshTokenWithCompletion((tokenResult: ITnsOAuthTokenResult, error) => {
if (error) {
console.error("Unable to refresh token with error: ");
console.error(error);
} else {
console.log("Successfully refreshed access token: ");
console.log(tokenResult);
}
});
}
}
Please let me know if there is any correction,
Error: Uncaught (in promise): URIError: URI malformed
I am using Azure AD B2C for identity management.

Apollo client QUERIES not sending headers to server but mutations are fine

I hooked up a front end to a graphql server. Most if not all the mutations are protected while all the queries are not protected. I have an auth system in place where if you log in, you get an access/refresh token which all mutations are required to use. And they do which is great, backend receives the headers and everything!
HOWEVER. There is one query that needs at least the access token to distinguish the current user! BUT the backend does not receive the two headers! I thought that the middlewareLink I created would be for all queries/mutations but I'm wrong and couldn't find any additional resources to help me out.
So here's my setup
apollo-client.js
import { InMemoryCache } from "apollo-cache-inmemory"
import { persistCache } from "apollo-cache-persist"
import { ApolloLink } from "apollo-link"
import { HttpLink } from "apollo-link-http"
import { onError } from "apollo-link-error"
import { setContext } from "apollo-link-context"
if (process.browser) {
try {
persistCache({
cache,
storage: window.localStorage
})
} catch (error) {
console.error("Error restoring Apollo cache", error)
}
}
const httpLink = new HttpLink({
uri: process.env.GRAPHQL_URL || "http://localhost:4000/graphql"
})
const authMiddlewareLink = setContext(() => ({
headers: {
authorization: localStorage.getItem("apollo-token") || null,
"x-refresh-token": localStorage.getItem("refresh-token") || null
}
}))
const afterwareLink = new ApolloLink((operation, forward) =>
forward(operation).map(response => {
const context = operation.getContext()
const {
response: { headers }
} = context
if (headers) {
const token = headers.get("authorization")
const refreshToken = headers.get("x-refresh-token")
if (token) {
localStorage.setItem("apollo-token", token)
}
if (refreshToken) {
localStorage.setItem("refresh-token", refreshToken)
}
}
return response
})
)
const errorLink = onError(({ graphQLErrors, networkError }) => {
...
// really long error link code
...
})
let links = [errorLink, afterwareLink, httpLink]
if (process.browser) {
links = [errorLink, afterwareLink, authMiddlewareLink, httpLink]
}
const link = ApolloLink.from(links)
export default function() {
return {
cache,
defaultHttpLink: false,
link
}
}
Is there a way to target ALL mutations/queries with custom headers not just mutations? Or apply some headers to an individual query since I could probably put that as an app middleware?
edit: Haven't solved the SSR portion of this yet.. will re-edit with the answer once I have.

Apollo Server - Apply Authentication to Certain Resolvers Only with Passport-JWT

I currently have a Node.js back-end running Express with Passport.js for authentication and am attempting to switch to GraphQL with Apollo Server. My goal is to implement the same authentication I am using currently, but cannot figure out how to leave certain resolvers public while enabling authorization for others. (I have tried researching this question extensively yet have not been able to find a suitable solution thus far.)
Here is my code as it currently stands:
My JWT Strategy:
const opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
opts.secretOrKey = JWT_SECRET;
module.exports = passport => {
passport.use(
new JwtStrategy(opts, async (payload, done) => {
try {
const user = await UserModel.findById(payload.sub);
if (!user) {
return done(null, false, { message: "User does not exist!" });
}
done(null, user);
} catch (error) {
done(err, false);
}
})
);
}
My server.js and Apollo configuration:
(I am currently extracting the bearer token from the HTTP headers and passing it along to my resolvers using the context object):
const apollo = new ApolloServer({
typeDefs,
resolvers,
context: async ({ req }) => {
let authToken = "";
try {
if (req.headers.authorization) {
authToken = req.headers.authorization.split(" ")[1];
}
} catch (e) {
console.error("Could not fetch user info", e);
}
return {
authToken
};
}
});
apollo.applyMiddleware({ app });
And finally, my resolvers:
exports.resolvers = {
Query: {
hello() {
return "Hello world!";
},
async getUserInfo(root, args, context) {
try {
const { id } = args;
let user = await UserModel.findById(id);
return user;
} catch (error) {
return "null";
}
},
async events() {
try {
const eventsList = await EventModel.find({});
return eventsList;
} catch (e) {
return [];
}
}
}
};
My goal is to leave certain queries such as the first one ("hello") public while restricting the others to requests with valid bearer tokens only. However, I am not sure how to implement this authorization in the resolvers using Passport.js and Passport-JWT specifically (it is generally done by adding middleware to certain endpoints, however since I would only have one endpoint (/graphql) in this example, that option would restrict all queries to authenticated users only which is not what I am looking for. I have to perform the authorization in the resolvers somehow, yet not sure how to do this with the tools available in Passport.js.)
Any advice is greatly appreciated!
I would create a schema directive to authorized query on field definition and then use that directive wherever I want to apply authorization. Sample code :
class authDirective extends SchemaDirectiveVisitor {
visitObject(type) {
this.ensureFieldsWrapped(type);
type._requiredAuthRole = this.args.requires;
}
visitFieldDefinition(field, details) {
this.ensureFieldsWrapped(details.objectType);
field._requiredAuthRole = this.args.requires;
}
ensureFieldsWrapped(objectType) {
// Mark the GraphQLObjectType object to avoid re-wrapping:
if (objectType._authFieldsWrapped) return;
objectType._authFieldsWrapped = true;
const fields = objectType.getFields();
Object.keys(fields).forEach(fieldName => {
const field = fields[fieldName];
const {
resolve = defaultFieldResolver
} = field;
field.resolve = async function (...args) {
// your authorization code
return resolve.apply(this, args);
};
});
}
}
And declare this in type definition
directive #authorization(requires: String) on OBJECT | FIELD_DEFINITION
map schema directive in your schema
....
resolvers,
schemaDirectives: {
authorization: authDirective
}
Then use it on your api end point or any object
Query: {
hello { ... }
getuserInfo():Result #authorization(requires:authToken) {...}
events():EventResult #authorization(requires:authToken) {...}
};

Resources