I have a problem. When I send a login request to the server, I get a status code of 200, but no token in response. Can you tell me what the problem is
import { createApi, fetchBaseQuery } from "#reduxjs/toolkit/query/react";
import { setCredentials, logOut } from "../services/features/authSlice";
const baseQuery = fetchBaseQuery({
baseUrl: "https://central-park.doniraj-krv.w3lab.cloud",
mode: "no-cors",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
prepareHeaders: (headers, { getState }) => {
const token = getState().auth.token;
console.log(token);
if (token) {
headers.set("Authorization", `Bearer ${token}`);
}
// headers.set("Accept", "application/json");
// headers.set("Content-Type", "application/json");
return headers;
},
});
const baseQueryWithReauth = async (args, api, extraOptions) => {
let result = await baseQuery(args, api, extraOptions);
if (result?.error?.originalStatus === 403) {
console.log("sending refresh token");
// send refresh token to get new access token
const refreshResult = await baseQuery(
"/api/auth/refresh",
api,
extraOptions
);
console.log(refreshResult);
if (refreshResult?.data) {
const user = api.getState().auth.user;
// store the new token
api.dispatch(setCredentials({ ...refreshResult.data, user }));
// retry the original query with new access token
result = await baseQuery(args, api, extraOptions);
} else {
api.dispatch(logOut());
}
}
return result;
};
export const apiSlice = createApi({
reducerPath: "api",
baseQuery: baseQueryWithReauth,
endpoints: (builder) => ({}),
});
When I send POST request, I have status code 200 but response is empty
Related
I am currently using NextAuth to signIn in my application, and want to add more scopes into it while the user is already signed in so I can use the Google Fit API.
I've been reading the documentation of NextAuth and doing some research but did not find anything helpful for the current NextAuth v4 in this scope situation.
My current Google configuration:
import NextAuth from 'next-auth';
import GoogleProvider from "next-auth/providers/google"
const GOOGLE_AUTHORIZATION_URL =
'https://accounts.google.com/o/oauth2/v2/auth?' +
new URLSearchParams({
prompt: 'consent',
access_type: 'offline',
response_type: 'code'
})
export default NextAuth({
// Configure one or more authentication providers
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
authorization: GOOGLE_AUTHORIZATION_URL,
}),
],
callbacks: {
async jwt({ token, user, account }) {
// Initial sign in
if (account && user) {
return {
accessToken: account.access_token,
accessTokenExpires: Date.now() + account.expires_in * 1000,
refreshToken: account.refresh_token,
user
}
}
// Return previous token if the access token has not expired yet
if (Date.now() < token.accessTokenExpires) {
return token
}
// Access token has expired, try to update it
return refreshAccessToken(token)
},
async session({ session, token }) {
session.user = token.user;
session.accessToken = token.accessToken
session.error = token.error
return session
}
},
jwt: {
secret: process.env.NEXTAUTH_JWT_SECRET,
},
secret: process.env.NEXTAUTH_SECRET,
})
async function refreshAccessToken(token) {
try {
const url =
"https://oauth2.googleapis.com/token?" +
new URLSearchParams({
client_id: process.env.GOOGLE_CLIENT_ID,
client_secret: process.env.GOOGLE_CLIENT_SECRET,
grant_type: "refresh_token",
refresh_token: token.refreshToken,
})
const response = await fetch(url, {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
method: "POST",
})
const refreshedTokens = await response.json()
if (!response.ok) {
throw refreshedTokens
}
return {
...token,
accessToken: refreshedTokens.access_token,
accessTokenExpires: Date.now() + refreshedTokens.expires_at * 1000,
refreshToken: refreshedTokens.refresh_token ?? token.refreshToken, // Fall back to old refresh token
}
} catch (error) {
console.log(error)
return {
...token,
error: "RefreshAccessTokenError",
}
}
}
My current code is working just fine, so I just need the scopes to authorize and use the Google Fitness API.
Actually made it work, created a file called add_scopes.js inside pages/api/auth/
export default (req, res) => {
if (req.method === 'POST') {
// construct the authorize URL with additional scopes
const scopes = 'openid https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/fitness.activity.read https://www.googleapis.com/auth/fitness.location.read'
const redirectUri = process.env.GOOGLE_CALLBACK_URL
const clientId = process.env.GOOGLE_CLIENT_ID
const authorizationUrl = `https://accounts.google.com/o/oauth2/v2/auth?prompt=consent&access_type=offline&response_type=code&scope=${scopes}&redirect_uri=${redirectUri}&client_id=${clientId}`
// send the authorization URL to the client
res.json({ authorizationUrl });
} else {
res.status(405).end(); // Method Not Allowed
}
}
then made a button to call this api route:
import { useCallback } from 'react';
import { Button } from 'react-bootstrap';
const AddScopesButton = ({scopes=scopes}) => {
const isAuthorized = scopes.includes("https://www.googleapis.com/auth/fitness.activity.read") && scopes.includes("https://www.googleapis.com/auth/fitness.location.read")
const handleClick = useCallback(async () => {
try {
const res = await fetch("/api/auth/add_scopes", { method: "POST" });
const json = await res.json()
if (res.ok) {
window.location.href = json.authorizationUrl;
} else {
throw new Error(res.statusText);
}
} catch (error) {
console.error(error);
}
}, []);
return (
<>
{!isAuthorized && (
<Button className='mt-2' onClick={handleClick}>Add Scopes</Button>
)}
{isAuthorized && <span>Authorized</span>}
</>
);
};
export default AddScopesButton;
The only problem is if you signOut and signIn back in you need to get the authorization again, would really like to know if there is a way to save the accessToken/scopes that were authorized.
import { ApolloClient, createHttpLink, InMemoryCache } from "#apollo/client";
import { _getCookies } from "#utils/cookies";
import createAwsClient from 'agnostic-aws-signature';
import { setContext } from "apollo-link-context";
// fetching tokens received from cognito after login and creating signature
const sessionToken = _getCookies("sessionToken")
const secretAccessKey = _getCookies("secretAccessKey")
const accessKeyId = _getCookies("accessToken")
const expiration = _getCookies('expiration')
const awsClient = createAwsClient(accessKeyId, secretAccessKey, sessionToken, {
serviceName: "appsync",
region: process.env.COGNITO_REGION, // Your AWS resource region
endpoint: process.env.APPSYNC_ENDPOINT, // Your AWS resource url
});
const publicAPI = [
"getFaq",
"getMedia",
"getCategory",
"listVehicleColors",
"getLocationByPIN",
"listAccessories",
"verifyEmailToken"
]
const customFetch = (uri, options) => {
// check if values exists in public API
const addAPIKeyIf = publicAPI.map(val => options.body.includes(val)).reduce((a, b) => a || b, false);
// Sign our Request to allow User access to AWS resource
const signedRequest = awsClient.signRequest({
method: options.method, // Method of your request
headers: {
// Whatever headers you need to send to the resource
// accept: '*/*',
// 'content-type': 'application/json;application/x-www-form-urlencoded;charset=utf-8',
},
body: options.method == "POST" ? options.body : {}, // Whatever body you need to send to the resource
});
// if api is public add x-api-key or else remove it
addAPIKeyIf ? "" : delete options.headers["x-api-key"]
options.headers = { ...options.headers, ...signedRequest.headers }
return fetch(uri, options)
}
const httpLink = createHttpLink({
uri: process.env.APPSYNC_ENDPOINT,
fetch: accessKeyId ? customFetch : null,
});
const authLink = setContext((_, { headers }) => {
return {
headers: {
...headers,
"x-api-key": process.env.API_KEY
}
};
});
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
defaultOptions:{
watchQuery: {
fetchPolicy: 'no-cache'
},
query: {
fetchPolicy: 'no-cache'
}
}
});
export default client;
I am storing the Cognito tokens as cookies and creating signature using aws-agnostic-signature
this is all working. and we are mapping out the public api and if addApiKeyif is true, we are adding the X-api-key, else deleting it from headers. so, our requirement is, if its public api ,we have to pass x-api-key, if its private we have to pass aws signature as authorization, but currently all the Api's are taking api-key as authorization.
I made a jwt authetication using asp.net core and vuejs
this is my auth controller :
[Route("Login")]
[HttpPost]
public IActionResult Login(LoginArgument loginArgument)
{
var user = _userService.GetByEmail(loginArgument.Email);
if (user == null) return BadRequest(error: new { message = "Invalid credential : verify email" });
if (!BC.BCrypt.Verify(text: loginArgument.Password, hash: user.Password))
{
return BadRequest(error: new { message = "Invalid credential : verify password" });
}
var jwt= _jwtService.Generate(user.Id);
Response.Cookies.Append(key: "jwt", value: jwt, new Microsoft.AspNetCore.Http.CookieOptions
{
HttpOnly=false,
SameSite=Microsoft.AspNetCore.Http.SameSiteMode.None
}) ;
return Ok(user);
}
[Route("User")]
[HttpGet]
public IActionResult User()
{
try
{
var jwt = Request.Cookies["jwt"];
var token = _jwtService.Verify(jwt);
int userId = int.Parse(token.Issuer);
var user = _userService.GetById(userId);
return Ok(user);
}
catch (Exception)
{
return Unauthorized();
}
}
and this is the login in vue
<script lang="ts">
import { reactive } from 'vue';
import { useRouter } from "vue-router";
export default {
name: "Login",
setup() {
const data = reactive({
email: '',
password: ''
});
const router = useRouter();
const submit = async () => {
await fetch('https://localhost:44391/api/Auth/Login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify(data)
});
await router.push('/Countries');
}
return {
data,
submit
}
},
}
the login part is working in front and back sides perfectly and i can see the cookies
the problem is when i try to get the logged user. in back side i can get it successfully but in front it says that no user is logged
here is the loggedUser vue
<script lang="ts">
import { onMounted, ref } from 'vue';
export default {
name: "LoggedUser",
setup() {
const message = ref('You are not logged in!');
onMounted(async () => {
const response = await fetch('https://localhost:44391/api/Auth/User', {
headers: { 'Content-Type': 'application/json' },
credentials: 'include'
});
const content = await response.json();
message.value = `hi ${content.name}`;
});
return {
message
}
}
}
Here is the errors i got when i inspect the page :
this issues appear the moment of login
1- Mark cross-site cookies as Secure to allow setting them in cross-site contexts
2- Migrate entirely to HTTPS to have cookies sent to same-site subresources
this one appears when i call loggedUser in front even so it works in the back side
{type: "https://tools.ietf.org/html/rfc7235#section-3.1", title: "Unauthorized", status:
401,…}
status: 401
title: "Unauthorized"
traceId: "00-b4a9f6fee8dff6439952ded0bb50005d-43c9aee84c454b40-00"
type: "https://tools.ietf.org/html/rfc7235#section-3.1"
You need to send the access token in the request headers
Example:
let token = '???'
const response = await post('localhost/api/auth/user', {
headers: {
'Content-Type': 'application/json'
'Authorization' : 'Bearer '+ token
}
});
I'm trying to refresh token(firebase) in react native without success. It add the new token but in this way
Authorization Bearer old_token, Bearer new_token
Expected behaviour
Authorization Bearer new_token
Here is my code, we can see the instance, interceptor for append current token to all request and finally the interceptor for the refresh token. I don't know what I'm missing.
const customConfig: AxiosRequestConfig = {
baseURL: 'http://localhost:3000',
headers: {
'content-type': 'application/json',
},
responseType: 'json',
};
const instance: any = axios.create(
customConfig,
);
// interceptor to put token to all request
instance.interceptors.request.use(
async (config: any) => {
const token = await AsyncStorage.getItem('token');
if (token) {
config.headers.authorization = 'Bearer ' + token;
console.log("config.headers.authorization", config.headers.authorization)
}
return config;
},
(error: any) => {
Promise.reject(error);
},
);
// interceptor to handle refresh token
instance.interceptors.response.use((response: any) => {
return response;
},
function (error: any) {
console.log("error en axios", error)
const originalRequest = error.config;
if (!error.response) {
return Promise.reject('Network Error');
}
if ((error.response.status === 401) && !originalRequest._retry) {
originalRequest._retry = true;
return firebase.auth().currentUser?.getIdTokenResult(false).then((res) => {
AppStorage.setToken(res.token).then(() => { console.log('Token saved'); });
const addToken = 'Bearer ' + res.token;
instance.defaults.headers.common['Authorization'] = addToken;
originalRequest.headers['Authorization'] = addToken;
return axios(originalRequest);
});
}
return Promise.reject(error);
},
);
As per instructions followed here, I'm trying to cache my endpoint URL and token from Auth0 before constructing my Apollo client:
import React from 'react';
import { ApolloClient, ApolloProvider, from, HttpLink, InMemoryCache } from '#apollo/client';
import { setContext } from '#apollo/link-context';
import { useAuth0 } from './auth/AuthContext';
const App: React.FC = () => {
const { isLoading, getTokenSilently, getIdTokenClaims } = useAuth0();
if (isLoading) return <Loader />;
let endpoint: string;
let token: string;
const contextLink = setContext(async () => {
if (!token) {
token = await getTokenSilently();
}
if (!endpoint) {
endpoint = await getIdTokenClaims()['https://example.com/graphql_endpoint'];
}
return { endpoint, token };
});
/**
* TODO: check for autorization error and remove token from cache
* See: https://www.apollographql.com/docs/react/v3.0-beta/api/link/apollo-link-context/
*/
const apolloClient = new ApolloClient({
cache: new InMemoryCache(),
link: from([
contextLink,
new HttpLink({
uri: endpoint || '',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
}
})
])
});
return (
<ApolloProvider client={apolloClient}>
<div />
</ApolloProvider>
);
};
export default App;
I'm getting the error TS2454 (variable is used before being assigned) for both endpoint and token above. Any idea how I can get around this?
You're declaring both endpoint and token as variables, but not initializing them to anything before checking them inside of setContext.
let endpoint: string;
let token: string;
const contextLink = setContext(async () => {
if (!token) {
token = await getTokenSilently();
}
if (!endpoint) {
endpoint = await getIdTokenClaims()['https://example.com/graphql_endpoint'];
}
return { endpoint, token };
});
Try setting default values:
let endpoint: string = "";
let token: string = "";