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
}
});
Related
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
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.
I am trying to set up authentication for a project. Once a user signs up for our app they get sent to our home page with an id in the query. This id then gets used to submit user and then the jwt token gets saved inside redux state.
All our calls now go through an axios client where the jwt token is passed on every request. The token gets read with store.getState(injectStore)
This all works fine inside getserversideProps, but the issue comes in when using calls on the frontend that goes through NextJs built in 'pages/api' folder. Any calls inside those folders causes the store.getState() to be undefined. I do not understand why since it uses the exact same client as geserversideProps.
Example GetServersideProps(working)
try {
const response = await serverApiClient.get('v1/config');
return {
props: {
},
};
} catch ({ error: { statusCode = 500, message = 'Internal Server Error' } }) {
if (statusCode === 401) {
return {
redirect: {
permanent: false,
destination: '/',
},
};
}
throw new Error(message as string);
}
};
Example Frontend bff call(not working)
try {
// Call below get sent to next built in api
const players = await apiClient.get(`/defenders?sortBy=${statId}&team_id=${teamShortName}`);
return players;
} catch (error) {
return { error };
}
};
export default async function handler(req: NextApiRequest) {
console.log('Start request')
try {
const { sortBy, team_id: teamId } = req.query;
const response = await serverApiClient.get(`/v1/players/picks?position=DEF&sort_by=${sortBy}&team_id=${teamId}`);
Api Client
mergeConfigs(
params: Record<string, string>,
headers: Record<string, string>,
configs: Record<string, string>,
): AxiosRequestConfig {
const defaultConfigs = ApiClient.getDefaultConfigs();
*const token = store?.getState()?.jwtToken?.value*
//ISSUE ABOVE - This store .getState() is only undefined in nextJS api folder calls.
return {
...defaultConfigs,
...configs,
params,
headers: {
...defaultConfigs.headers,
...headers,
...(token ? { Authorization: `Bearer ${token}` } : {}),
},
};
}
get(
uri: string,
params = {},
headers = {},
configs = {},
): Promise<AxiosResponse | any> {
return this.client
.get(uri, this.mergeConfigs(params, headers, configs))
.then((response) => {
return (response.data ? response.data : response);
})
.catch((error) => {
const errorObject = {
error: error?.response?.data,
};
throw Object.assign(errorObject);
});
}
If anyone has some advice on why that getStore is undefined in frontend-to-backend calls please assist. Thanks all!
I'm using the JWT Authentication for WP-API plugin to generate the token.
User crendetials are submitted to /jwt-auth/v1/token endpoint then store token to cookie then setState as follow
setAuth({ status: 'SIGNED_IN', user: response.data.user_display_name});
then on restricted page I check if user is valid from getServerSideProps
export const authenticateUser = async (ctx) => {
try {
const response = await axios.get(`${process.env.NEXT_PUBLIC_API_URL}/wp/v2/users/me`, {}, {
headers: {
'Authorization': `Bearer ${token}`
}
});
// if is it returns the right informations
return { status: 'SIGNED_IN', user: response.data.user}
// else
return { status: 'SIGNED_OUT', user: null}
}
}
export const getServerSideProps = async context => {
// retrieve user
const auth = await authenticateUser(context);
console.log(auth);
// if there is no authenticated user, redirect to homepage
if (auth.status === 'SIGNED_OUT') {
return {
props: {},
redirect: { destination: "/" },
}
}
return { props: { user: auth.user } }
}
In postman the requests are working fine using the right authorization bearer token.
But in my nextjs app I have the following response
{
code: 'rest_not_logged_in',
message: 'Vous n’êtes actuellement pas connecté.',
data: { status: 401 }
}
I also tried to check if user id exists using
const response = await axios.get(`${process.env.NEXT_PUBLIC_API_URL}/wp/v2/users/${userId}`, {}, {
headers: {
'Authorization': `Bearer ${token}`
}
});
but I have the following response
{
code: 'rest_user_cannot_view',
message: 'Désolé, vous n’avez pas l’autorisation de lister les comptes.',
data: { status: 401 }
}
Any help will be appreciate.
EDIT
It appears that the problem come from axios, fetch API works great.
I have an Angular 2 app which runs on port 8000 on the lite server. My express app (back-end) runs on port 3000.
When I send my credentials to the back-end which runs on port 3000, then I can see that I had succesfully logged in on port 8000. But I don't get a session as a response from the back-end. Here is a screenshot of it link:
As you can see the response says Session not found.
And when I succesfully log in on port 3000. Then I do get a session as response from the back-end. Here is a screenshot of it link:
Also here is a screenshot of logging in on port 3000 through Postman (Rest client) link:
And here is a screenshot of getting a session as a response on port 3000 link:
So now I can conclude that there is no problem at the back-end.
My question is: how can I also get the session as a response on port 8000?
Here is my code:
app.component.ts:
export class AppComponent {
//On page refresh the constructor gets called
constructor(private _userService: UserService) {
this._userService.getSession()
.subscribe(
res => {
console.log("app session ");
//log the response which shows a session
console.log(res);
},
error => {
console.log("app session error");
console.log(error);
}
);
}
}
login.component.ts:
export class LoginComponent {
user: User = new User();
loginRes: String;
private toasterService: ToasterService;
public toasterconfig: ToasterConfig = new ToasterConfig({
showCloseButton: true,
tapToDismiss: false,
timeout: 0
});
constructor(private _userService: UserService, toasterService: ToasterService, private router: Router) {
this.toasterService = toasterService;
}
data = {};
onSubmit() {
this._userService.login(this.user)
.subscribe(
//we are using an arrow function here
res => {
this.toasterService.pop('success', 'Success', 'You have logged in ' + res.username);
// I don't use navigate(), because I want a redirect where the page gets refreshed
// this.router.navigate(['/']);
var host = location.host;
console.log("host");
console.log(host);
//redirects where the page also gets refreshed
window.location.href = "http://"+ host;
},
error => {
this.toasterService.pop('error', 'Failed', error._body);
}
);
}
}
user.service.ts:
#Injectable()
export class UserService {
private url: string;
constructor(private _http: Http) {
}
login(user: User) {
this.url = config.getEnvironmentVariable('endPoint')+'api/auth/login';
let data = { "username": user.username, "password": user.password };
let body = JSON.stringify(data);
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
console.log("login url " + this.url);
return this._http.post(this.url, body, options)
.map(this.extractData)
.catch(this.handleError);
}
getSession() {
this.url = config.getEnvironmentVariable('endPoint')+'api/auth/';
console.log("session url " + this.url);
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
options.body = '';
return this._http.get(this.url, options)
.map(this.extractData)
.catch(this.handleError);
}
private extractData(res: Response) {
let body = res.json();
return body || {};
}
private handleError(error: any) {
//the Observable catches and throws an error
return Observable.throw(error.message || error);
}
}