MagnusEffect
MagnusEffect

Reputation: 3925

How to add bearer token in the API called in getServerSideProps() in nextjs?

Intro

I am developing an login application using Nextjs on frontend and springboot on backend.

Problem

I am able to login from the frontend which calls the loginAPI named /authenticate developed in springboot and it is successfully returning the authToken.After login I got redirects to home (/allcoupons) page.

In login , I am passing the value of token as response.data like this

 const handleSubmit = async (e) => {
    // e.preventDefault();

    let requestbody = {
      username: credentials.username,
      password: credentials.password,
    };

    try {
      const response = await axios({
        method: "post",
        headers: {},
        url: "http://localhost:8081/authenticate",
        data: requestbody,
      });

      //console.log("credentials for login = ", credentials);
      //console.log("response from api = ", response);
      if (response.status === 200) {
        router.push(
          {
            pathname: "/allcoupons",
            query: { auth: JSON.stringify(response.data) },
          },
          undefined,
          {
            shallow: true
          }
        );

      } else alert("invalid credentials");
    } catch (error) {
      setTimeout(() => {
        setShowAlert(true);
      }, 2000);
      setShowAlert(false);
    }
  };

Then I redirects to /allcoupons component In this component I am using getServerSideProps(). I am able to access the authtoken value from context.query.auth but now I am unable to send this authtoken value as bearer token with another API /allcoupons in the headers.

home.js

import React, { Fragment, useState, useEffect } from "react";
import { useRouter } from "next/router";
import Footer from "./components/footer";
import { Alert } from "flowbite-react";
import axios from "axios";



const Allcoupons = ({ datafromAPI }) => {
  const router = useRouter();

  //console.log("datafromAPI in components = ", datafromAPI.coupons)
  //let data = datafromAPI.coupons;
const val = router.query.auth ? JSON.parse(router.query.auth) : {};
  console.log("authtoken form login = ", val);

  const [showModal, setShowModal] = useState(false);
  .
  .


  return (
    <>
      .
      .
      .
    </>
);
};

export async function getServerSideProps(context) {
  //Fetch data from get API
  //console.log("context = ", context.query.auth);
  const token = context.query.auth;
  console.log("token = ",context.query.auth)
  const res = await axios.get(
    `http://localhost:8081/couponstore/v1.0/coupons`,
     //using like this
    { headers: { Authorization: `Bearer ${token}` } }
  );
  const datafromAPI = res.data;
  console.log("data from API server = ",datafromAPI);

  return { props: { datafromAPI } };
}

Observation

But as soon as I redirects to this page , it endpoint change from /allcoupons to /allcoupons?auth={authtokenvalue} .

Logs/StackTrace

Also this log comes in the terminal

token =  "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhcnVuYWJoIiwiZXhwIjoxNjY0NTQwODYwLCJpYXQiOjE2NjQ1MzM2NjB9.Ib3X-6TMyQ3YfoGz-PuS4hehzuHq-N6XHwzLF6cXD2U"
error - [AxiosError: Request failed with status code 403] {
  code: 'ERR_BAD_REQUEST',
  config: {
    transitional: {
      silentJSONParsing: true,
      forcedJSONParsing: true,
      clarifyTimeoutError: false
    },
    adapter: [Function: httpAdapter],
    transformRequest: [ [Function: transformRequest] ],
    transformResponse: [ [Function: transformResponse] ],
    timeout: 0,
    xsrfCookieName: 'XSRF-TOKEN',
    xsrfHeaderName: 'X-XSRF-TOKEN',
    maxContentLength: -1,
    maxBodyLength: -1,
    env: { FormData: [Function] },
    validateStatus: [Function: validateStatus],
    headers: {
      Accept: 'application/json, text/plain, */*',
      Authorization: 'Bearer "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhcnVuYWJoIiwiZXhwIjoxNjY0NTQwODYwLCJpYXQiOjE2NjQ1MzM2NjB9.Ib3X-6TMyQ3YfoGz-PuS4hehzuHq-N6XHwzLF6cXD2U"',
      'User-Agent': 'axios/0.27.2'
    },
    method: 'get',
    url: 'http://localhost:8081/couponstore/v1.0/coupons',
    data: undefined
  },
  request: <ref *1> ClientRequest {
    _events: [Object: null prototype] {
      abort: [Function (anonymous)],
      aborted: [Function (anonymous)],
      connect: [Function (anonymous)],
      error: [Function (anonymous)],
      socket: [Function (anonymous)],
      timeout: [Function (anonymous)],
      prefinish: [Function: requestOnPrefinish]
    },
    _eventsCount: 7,
    _maxListeners: undefined,
    outputData: [],
    outputSize: 0,
    writable: true,
    destroyed: false,
    _last: true,
    chunkedEncoding: false,
    shouldKeepAlive: false,
    maxRequestsOnConnectionReached: false,
    _defaultKeepAlive: true,
    useChunkedEncodingByDefault: false,
    sendDate: false,
    _removedConnection: false,
    _removedContLen: false,
    _removedTE: false,
    _contentLength: 0,
    _hasBody: true,
    _trailer: '',
    finished: true,
    _headerSent: true,
    _closed: false,
    socket: Socket {
      connecting: false,
      _hadError: false,
      _parent: null,
      _host: 'localhost',
      _readableState: [ReadableState],
      _events: [Object: null prototype],
      _eventsCount: 7,
      _maxListeners: undefined,
      _writableState: [WritableState],
      allowHalfOpen: false,
      _sockname: null,
      _pendingData: null,
      _pendingEncoding: '',
      server: null,
      _server: null,
      parser: null,
      _httpMessage: [Circular *1],
      write: [Function: writeAfterFIN],
      [Symbol(async_id_symbol)]: 285969,
      [Symbol(kHandle)]: [TCP],
      [Symbol(lastWriteQueueSize)]: 0,
      [Symbol(timeout)]: null,
      [Symbol(kBuffer)]: null,
      [Symbol(kBufferCb)]: null,
      [Symbol(kBufferGen)]: null,
      [Symbol(kCapture)]: false,
      [Symbol(kSetNoDelay)]: false,
      [Symbol(kSetKeepAlive)]: true,
      [Symbol(kSetKeepAliveInitialDelay)]: 60,
      [Symbol(kBytesRead)]: 0,
      [Symbol(kBytesWritten)]: 0,
      [Symbol(RequestTimeout)]: undefined
    },
    _header: 'GET /couponstore/v1.0/coupons HTTP/1.1\r\n' +
      'Accept: application/json, text/plain, */*\r\n' +
      'Authorization: Bearer "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhcnVuYWJoIiwiZXhwIjoxNjY0NTQwODYwLCJpYXQiOjE2NjQ1MzM2NjB9.Ib3X-6TMyQ3YfoGz-PuS4hehzuHq-N6XHwzLF6cXD2U"\r\n' +
      'User-Agent: axios/0.27.2\r\n' +
      'Host: localhost:8081\r\n' +
      'Connection: close\r\n' +
      '\r\n',
    _keepAliveTimeout: 0,
    _onPendingData: [Function: nop],
    agent: Agent {
      _events: [Object: null prototype],
      _eventsCount: 2,
      _maxListeners: undefined,
      defaultPort: 80,
      protocol: 'http:',
      options: [Object: null prototype],
      requests: [Object: null prototype] {},
      sockets: [Object: null prototype],
      freeSockets: [Object: null prototype] {},
      keepAliveMsecs: 1000,
      keepAlive: false,
      maxSockets: Infinity,
      maxFreeSockets: 256,
      scheduling: 'lifo',
      maxTotalSockets: Infinity,
      totalSocketCount: 1,
      [Symbol(kCapture)]: false
    },
    socketPath: undefined,
    method: 'GET',
    maxHeaderSize: undefined,
    insecureHTTPParser: undefined,
    path: '/couponstore/v1.0/coupons',
    _ended: true,
    res: IncomingMessage {
      _readableState: [ReadableState],
      _events: [Object: null prototype],
      _eventsCount: 4,
      _maxListeners: undefined,
      socket: [Socket],
      httpVersionMajor: 1,
      httpVersionMinor: 1,
      httpVersion: '1.1',
      complete: true,
      rawHeaders: [Array],
      rawTrailers: [],
      aborted: false,
      upgrade: false,
      url: '',
      method: null,
      statusCode: 403,
      statusMessage: '',
      client: [Socket],
      _consuming: false,
      _dumped: false,
      req: [Circular *1],
      responseUrl: 'http://localhost:8081/couponstore/v1.0/coupons',
      redirects: [],
      [Symbol(kCapture)]: false,
      [Symbol(kHeaders)]: [Object],
      [Symbol(kHeadersCount)]: 18,
      [Symbol(kTrailers)]: null,
      [Symbol(kTrailersCount)]: 0,
      [Symbol(RequestTimeout)]: undefined
    },
    aborted: false,
    timeoutCb: null,
    upgradeOrConnect: false,
    parser: null,
    maxHeadersCount: null,
    reusedSocket: false,
    host: 'localhost',
    protocol: 'http:',
    _redirectable: Writable {
      _writableState: [WritableState],
      _events: [Object: null prototype],
      _eventsCount: 3,
      _maxListeners: undefined,
      _options: [Object],
      _ended: true,
      _ending: true,
      _redirectCount: 0,
      _redirects: [],
      _requestBodyLength: 0,
      _requestBodyBuffers: [],
      _onNativeResponse: [Function (anonymous)],
      _currentRequest: [Circular *1],
      _currentUrl: 'http://localhost:8081/couponstore/v1.0/coupons',
      [Symbol(kCapture)]: false
    },
    [Symbol(kCapture)]: false,
    [Symbol(kNeedDrain)]: false,
    [Symbol(corked)]: 0,
    [Symbol(kOutHeaders)]: [Object: null prototype] {
      accept: [Array],
      authorization: [Array],
      'user-agent': [Array],
      host: [Array]
    },
    [Symbol(kUniqueHeaders)]: null
  },
  response: {
    status: 403,
    statusText: '',
    headers: {
      'x-content-type-options': 'nosniff',
      'x-xss-protection': '1; mode=block',
      'cache-control': 'no-cache, no-store, max-age=0, must-revalidate',
      pragma: 'no-cache',
      expires: '0',
      'x-frame-options': 'DENY',
      'content-length': '0',
      date: 'Fri, 30 Sep 2022 10:27:40 GMT',
      connection: 'close'
    },
    config: {
      transitional: [Object],
      adapter: [Function: httpAdapter],
      transformRequest: [Array],
      transformResponse: [Array],
      timeout: 0,
      xsrfCookieName: 'XSRF-TOKEN',
      xsrfHeaderName: 'X-XSRF-TOKEN',
      maxContentLength: -1,
      maxBodyLength: -1,
      env: [Object],
      validateStatus: [Function: validateStatus],
      headers: [Object],
      method: 'get',
      url: 'http://localhost:8081/couponstore/v1.0/coupons',
      data: undefined
    },
    request: <ref *1> ClientRequest {
      _events: [Object: null prototype],
      _eventsCount: 7,
      _maxListeners: undefined,
      outputData: [],
      outputSize: 0,
      writable: true,
      destroyed: false,
      _last: true,
      chunkedEncoding: false,
      shouldKeepAlive: false,
      maxRequestsOnConnectionReached: false,
      _defaultKeepAlive: true,
      useChunkedEncodingByDefault: false,
      sendDate: false,
      _removedConnection: false,
      _removedContLen: false,
      _removedTE: false,
      _contentLength: 0,
      _hasBody: true,
      _trailer: '',
      finished: true,
      _headerSent: true,
      _closed: false,
      socket: [Socket],
      _header: 'GET /couponstore/v1.0/coupons HTTP/1.1\r\n' +
        'Accept: application/json, text/plain, */*\r\n' +
        'Authorization: Bearer "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhcnVuYWJoIiwiZXhwIjoxNjY0NTQwODYwLCJpYXQiOjE2NjQ1MzM2NjB9.Ib3X-6TMyQ3YfoGz-PuS4hehzuHq-N6XHwzLF6cXD2U"\r\n' +
        'User-Agent: axios/0.27.2\r\n' +
        'Host: localhost:8081\r\n' +
        'Connection: close\r\n' +
        '\r\n',
      _keepAliveTimeout: 0,
      _onPendingData: [Function: nop],
      agent: [Agent],
      socketPath: undefined,
      method: 'GET',
      maxHeaderSize: undefined,
      insecureHTTPParser: undefined,
      path: '/couponstore/v1.0/coupons',
      _ended: true,
      res: [IncomingMessage],
      aborted: false,
      timeoutCb: null,
      upgradeOrConnect: false,
      parser: null,
      maxHeadersCount: null,
      reusedSocket: false,
      host: 'localhost',
      protocol: 'http:',
      _redirectable: [Writable],
      [Symbol(kCapture)]: false,
      [Symbol(kNeedDrain)]: false,
      [Symbol(corked)]: 0,
      [Symbol(kOutHeaders)]: [Object: null prototype],
      [Symbol(kUniqueHeaders)]: null
    },
    data: ''
  },
  page: '/allcoupons'
}

Whats the mistake here??

Upvotes: 5

Views: 10017

Answers (2)

Abhishek Prajapati
Abhishek Prajapati

Reputation: 106

First of all I do not recommend returning authtoken in response of successful login. You should use httpOnly cookies to set authtoken. It is more secure and you can set it from backend like this.

Second axios provide a way to set Authorization token in request. I mean you can alter default headers like below:

axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;

Read more in doc

Upvotes: 1

SMH
SMH

Reputation: 1099

This how do usually when I create react apps

  1. I create a global context
import { createContext } from "react";
import { GlobalContextType } from "../types";

const defaultValue: GlobalContextType = {
   dispatch: () => {},
   state: {
        user: { authToken: "", username: "" }
        
    },
};
export const GlobalContext = createContext(defaultValue);
  1. create a hook for my global context
import { useContext } from "react";
import { GlobalContext } from "../contexts";

export function useGlobalContext () {

    const context = useContext(GlobalContext)
    if (!context) throw new Error("Missing Context - Global context");

    return context;
}
  1. Create state reducer hook
import { Dispatch, useMemo, useReducer } from "react";
import {
   
    GlobalContextActions,
    GlobalContextState,
   
} from "../types";


export function globalContextReducer(
    state: GlobalContextState,
    action: ReducerAction<GlobalContextActions>
): GlobalContextState {
    switch (action.type) {
        case GlobalContextActions.setUser:
            return {
                ...state,
                user: action.payload
            };
        

        default:
            return state;
    }
}
export function useGlobalContextReducer(reducer = globalContextReducer) {
    const [state, dispatch] = useReducer(reducer, {
        user : {authToken: "", username: ""}
    });
  

    return useMemo(() => {
        return {
            dispatch,
            state,
        };
    }, [d, state]);
}
  1. Wrapped the whole App with my global context
import { GlobalContext } from "../src/contexts";
import { useGlobalContextReducer } from "../src/hooks/use-global-context-reducer";

export default function MyApp(props: MyAppProps) {
    const { Component pageProps } = props;
    const globalContextReducer = useGlobalContextReducer();
    return (       
          <GlobalContext.Provider value={globalContextReducer}>
            <Component {...pageProps} />   
          </GlobalContext.Provider>
    );
}

this just my setup

then in my login page

import {GlobalContextActions, useGlobalContext} from "../../context"
import {Routes} from "../routes"

function Login() {
    const { dispatch} = useGlobalContext();
    const router = useRouter();
    const [form, updateForm] = useState({
        email: "",
        password: "",
    });

   

    const onFieldChange = (event: ChangeEvent<HTMLInputElement>) => {
        const { target } = event;
        const { id, value } = target;
        updateForm((current) => ({ ...current, [id]: value }));
    };

    const isLoginDisabled = !validateEmail(form.email) || !form.password;

    const onLogin = async () => {
        const dataService = container.resolve(DataService);
        const result = await dataService.getLoginData(form.email, form.password);
        if (result.error) {
            return dispatchError(result?.errorMessage as string);
        }
        useGlobalContextReducer({action:GlobalContextActions.setUser, payload: result.data});
        router.push(Routes.home);
    };
    return (
        <Box>
            <form action="" onSubmit={(event) => event?.preventDefault()}>
                <TextField
                    id="email"
                    label={"email"}
                    variant="standard"
                    required
                    value={form.email}
                    onChange={onFieldChange}
                    type="email"
                    fullWidth
                    data-testid="email"
                />
                <TextField
                    id="password"
                    label={"password"}
                    variant="standard"
                    type="password"
                    value={form.password}
                    required
                    fullWidth
                    onChange={onFieldChange}
                    data-testid="password"
                />
                <Button type="submit" variant="contained" disabled={isLoginDisabled} onClick={onLogin}>
                    Login
                </Button>
            </form>
        </Box>
    );
}

export default Login;

and this how I use the global state

function MyPage() {
   const {state} = useGlobalContext

  return (<div>{JSON.stringinfy(state)}</div>)

}

Upvotes: 3

Related Questions