OctoKit with Auth0 (Github Login) in NextJS - authentication

I am building a Next JS app that has Github Login through Auth0 and uses the Octokit to fetch user info / repos.
In order to get the IDP I had to setup a management api in auth0. https://community.auth0.com/t/can-i-get-the-github-access-token/47237 which I have setup in my NodeJs server to hide the management api token as : GET /getaccesstoken endpoint
On the client side : /chooserepo page, I have the following code :
const chooserepo = (props) => {
const octokit = new Octokit({
auth: props.accessToken,
});
async function run() {
const res = await octokit.request("GET /user");
console.log("authenticated as ", res.data);
}
run();
And
export const getServerSideProps = withPageAuthRequired({
async getServerSideProps({ req, params }) {
let { user } = getSession(req);
console.log("user from get session ", user);
let url = "http://localhost:4000/getaccesstoken/" + user.sub;
let data = await fetch(url);
let resData = await data.text();
return {
props: { accessToken: resData }, // will be passed to the page component as props
};
},
});
However, I keep getting Bad credentials error. If I directly put the access token in the Octokit it seems to work well, but doesn't work when it's fetching the access token from the server.
It seems like Octokit instance is created before server side props are sent. How do I fix it ?

I figured out the error by comparing the difference between the request headers when hardcoding and fetching access token from server. Turns out quotes and backslashes need to be replaced (and aren't visible when just console logging)

Related

How to decode token in Vue.js?

I got a token after I successfully logged in. I need to be able to parse + decode/descrypt it to see the permission information inside that. How would I do that?
I tried
// token is accessible
var decoded = jwt_decode(token)
console.log('decoded', decoded)
I kept getting
Here is my token
"e2kEd...LIPH0="
I'm using Vue.js v2.
"InvalidTokenError"
How do I know if my token is compatible with jwt_decode() ?
Try #2
Paste my token here :
https://jwt.io/
Try #3
If I base64_decode() it, I see this
{iversI�iuser{inameSibheng#test.comiapplSiVCiserv[$U#i0-�8rDaiperm[{inameSiEIDiparm[{inameSiAPPidataSiVC}]idataSi COLM,DFQL}{inameSiEIDiparm[{inameSiAPPidataSi*}]idataSiECNVF,CNVZ,DFQL,DJ1L,FV8Z,HY0B,N94X,RD8L,W3XV,X3CY,XPH4,YX4N,ZR10,COLM}{inameSi
VC_GET_EIDiparm[{inameSiBRANDidataSiBROO}]idataT}]}irelm[$U#i'$}s,9ialgoSi
SHA256-RSAisign[$U#I�ZϏpRV,lYt
>Ni_h{,*wE&!?`h±VmSr,n>쏝?L+7_d]JIVl1s:Gɳ<}`
The core piece of info that I really really need is BROO
It's there, but I can't seem to parse it.
This decoded works for nuxt js and vue js. Just install vue-jwt-decode and follow this code. Good luck
Node js Login Controller
static async loginUser(req, res){
req.body.password = await hashPassword(req.body.password);
const user = req.body;
await UserServiceClass.loginUser(user).
then((respond)=>{
const user = {id:respond._id,username:respond.username};
const access_token = generateToken(user);
const refresh_token = refreshToken(user);
res.status(200).json({access_token:access_token, refresh_token:refresh_token});
}).
catch((err)=>{
res.status(500).json({login_error:err})
})
}
Vue js Login Page
<script setup>
import VueJwtDecode from 'vue-jwt-decode';
let current_user = reactive({})
const login = async () => {
console.log(user);
await useFetch('http://localhost:4000/user/login',{
method:'POST',
body:user
}).then((respond)=>{
//Keep Token inside of window localstorage
localStorage.setItem('user', respond.data.value.access_token);
}).catch((err)=>{
console.log('err : ', err);
})
}
const decode = () => {
//Take token from window local storage
let token = localStorage.getItem('user');
try{
let decoded = VueJwtDecode.decode(token)
current_user = decoded;
}
catch(err){
console.log('token is null: ',err);
}
}
</script>

Vue.js (Quasar) SPA restarts Auth Code Flow on each page reload

I have a SPA built with the Quasar Framework (based on Vue.js). The SPA is registered in Auth0 and uses the auth0-spa-js library to handle the login via Auth Code Flow. While the login works and I get a token, when I reload the page the Auth Code Flow is started again and the user is redirected to the /authorize endpoint to get a new code, which is then again exchanged for a new token.
To me this does not seem like the correct behaviour. I would have expected that the Auth0 library caches/stores the token in the browser and on page reload checks if there is a valid token already, instead of restarting the Auth Code Flow every time.
Or is that actually the way it should be considering this is a SPA and token storage in the browser is not good.
The code from the boot file:
import createAuth0Client from '#auth0/auth0-spa-js';
import axios from 'axios'
export default async ({ app, router, Vue }) => {
let auth0 = await createAuth0Client({
domain: '{domain}.auth0.com',
client_id: '{client_id}',
audience: '{audience}'
});
const isAuthenticated = await auth0.isAuthenticated();
if (isAuthenticated) {
// show the gated content
await afterLogin(auth0, Vue)
return;
}
const query = window.location.search;
if (query.includes("code=") && query.includes("state=")) {
// Process the login state
await auth0.handleRedirectCallback()
await afterLogin(auth0, Vue)
// Use replaceState to redirect the user away and remove the querystring parameters
window.history.replaceState({}, document.title, "/");
return
}
await auth0.loginWithRedirect({
redirect_uri: window.location.origin
});
}
async function afterLogin(auth0, Vue) {
let user = await auth0.getUser()
Vue.prototype.$user = user
Vue.prototype.$auth = auth0
// let claims = await auth0.getIdTokenClaims()
// console.log(claims)
// setAuthHeader(claims.__raw)
let token = await auth0.getTokenSilently()
setAuthHeader(token)
}
function setAuthHeader(token) {
axios.defaults.headers.common['Authorization'] = 'Bearer ' + token
}
What am I missing?
When refreshing you should check if user exists..
const user = await auth0.getUser();
You could create an autoCheckUser function at src/boot folder, that can check if user exist every time app created...
The feedback from Auth0 is that this is the expected behaviour.
"If the token is stored in memory, then it will be erased when the page is refreshed. A new token is silently requested via a cookie session."
Here is the link to the original answer

Oauth2 Google Authentication flow - Next.JS / Express

I am using a React/Next.Js Frontend and am trying to implement authentication with the Oauth2 strategy with Google.
I am very confused by the process.
Currently on the client, I have a Google sign in component that has a Client ID with in it and can retrieve an access token.
<GoogleLogin
clientId="myclientid"
buttonText="Login"
onSuccess={userLogin}
onFailure={userLogin}
cookiePolicy={'single_host_origin'}
/>
I then have a function, which on success sends a post message to my backend with an access token, such as this:
export function googleAuthenticate(accessToken : string) : any{
axios({
method: 'post',
url: "http://localhost:4000/auth/google",
data: {
accessToken: accessToken
}
})
.then(res => {
console.log(res);
})
.catch(err => {
console.log("Failure!");
console.log(err);
})
};
On the backend I am using passport, and the routes look like this:
import express from 'express';
import passport from 'passport';
import Logger from '../logger/index';
const router = express.Router();
export function isAuthenticated(req:express.Request, res:express.Response, next : any) {
return req.isAuthenticated() ?
next() :
res.sendStatus(401);
}
router.get('/fail', (_req:express.Request, res:express.Response) => {
res.json({ loginFailed: true });
});
router.post('/google', passport.authenticate('google', { scope: ['profile']}), (_req:express.Request, _res:express.Response) => {
Logger.info("GET Request at Google Authentication endpoint received.");
});
router.get(
'/google/callback',
passport.authenticate('google', { failureRedirect: '/login' }),
(_req:express.Request, res:express.Response) => {
res.redirect('/graphql');
}
);
export default router;
My passport module looks like this:
module.exports = function(passport : any, GoogleStrategy : any){
passport.use(new GoogleStrategy({
clientID: config.google.client_id,
clientSecret: config.google.client_secret,
callbackURL: config.google.redirect_url
},
function(accessToken : string, profile : Profile, refreshToken : string, cb : any) {
return cb(null, {
id: profile.googleId,
username: profile.email,
image: profile.imageUrl,
firstName: profile.givenName,
surname: profile.familyName,
accessToken: accessToken,
refreshToken: refreshToken
})
}
));
}
Since Next.js is a server side rendered, I am not able to use save a token. I understand I have to use a cookie. But how does this work? I cannot redirect the client browser from the express backend.
Currently I'm just seeing these 2 errors:
OPTIONS https://accounts.google.com/o/oauth2/v2/auth?response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A4000%2localhost:3000%2Fdashboard&scope=profile&client_id=687602672235-l0uocpfchbjp34j1jjlv8tqv7jadb8og.apps.googleusercontent.com 405
Access to XMLHttpRequest at 'https://accounts.google.com/o/oauth2/v2/auth?response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A4000%2Fbackoffice.dev.myos.co%2Fdashboard&scope=profile&client_id=687602672235-l0uocpfchbjp34j1jjlv8tqv7jadb8og.apps.googleusercontent.com' (redirected from 'http://localhost:4000/auth/google') from origin 'null' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Firstly i think google auth will not work on localhost.
If i understand correctly in your serverside logic you can easily save your token as a cookie and then read them in the client.
Not sure with passport, but you can do something similar to this :
(my app is working with an implementation of this code)
frontend :
<GoogleLogin
clientId="myclientid"
buttonText="Login"
onSuccess={userLogin}
onFailure={userLogin}
cookiePolicy={'single_host_origin'}
/>
userLogin:
async userLogin(response){
var url = '/google-login/'+response.tokenObj.id_token
fetch(url).then(/* i will handle response*/)
}
Then in the backend you can use google-auth-library to login or register.
server.js:
const {OAuth2Client} = require('google-auth-library');
const GOOGLEID = "mygoogleid.apps.googleusercontent.com"
const client = new OAuth2Client(GOOGLEID);
var cookieParser = require('cookie-parser')
async function verify(userToken) {
const ticket = await client.verifyIdToken({
idToken: userToken,
audience: "clientid.apps.googleusercontent.com", // Specify the CLIENT_ID of the app that accesses the backend
// Or, if multiple clients access the backend:
//[CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3]
});
const payload = ticket.getPayload();
const userid = payload['sub'];
return payload
// If request specified a G Suite domain:
//const domain = payload['hd'];
}
In server.js a route similar to this :
server.get('/google-login/:token',(req,res) => {
const userToken = req.params.token
var result = verify(userToken).then(function(result){
var userName = result.given_name
var userSurname = result.family_name
var userEmail = result.email
/*
Now user is authenticated i can send to the frontend
user info or user token o save the token to session
*/
}).catch(function(err){
// error handling
})
})
You could use NextAuth.js to handle this for you.
In order to test localhost you should use ngrok to expose your localhost server to the web and configure the given url in google platform

Express auth middleware not liking the JWT Token it receives from client side React Native AsyncStorage

I'm working on login authentication with a react native app. I have login working as someone logs in for the first time through the login form. At this initial login step, I'm sending my JWT web token to my secure routes in my express server as a string and this works fine. NOTE: I have a middleware setup that my token goes through before my routes will fire completely.
The problem is when the app refreshes it needs to go to my "local storage". My express middleware doesn't like the token I'm pulling from my "local storage (I retrieve it through AsyncStorate.getItem('UserToken'). When I look at the item being stored in the header, it seems like it's the exact item I was sending it at the initial login, but I think it's my middleware on the server that doesn't like the value when it's coming from the " local storage" header. Below is my middlware.js code.
I've tried looking at the JWT value being sent in both scenarios, and it seems like it's the exact item being sent to the server in both situations.
module.exports = function(req, res, next) {
//Get Token from header
const token = req.header('x-auth-token');
// Check if no token
if(!token) {
return res.status(401).json({ msg: 'No token, authorization denied'})
}
//Verify token
try {
var decoded = jwt.verify(token, global.gConfig.jwtSecret);
req.user = decoded.user;
next();
}catch(err){
res.status(401).json({ msg: 'Token is not valid'});
}
}
This is the function I'm using to retrieve the token from AsyncStorage
export const getAsyncStorage = async () => {
try {
// pulling from header to get x-auth-token here
Value = await AsyncStorage.getItem('Usertoken');
//setting x-auth-token here
axios.defaults.headers.common['x-auth-token'] = Value;
} catch (error) {
// Error retrieving data
}
};

How use the #c8y/client library

I am testing the new #c8y/client library for typescript.
I have a very simple code :
import {
Client
} from '#c8y/client';
//const baseUrl = 'https://bismark1.cumulocity.com/';
const baseUrl = 'https://demos.cumulocity.com/';
const tenant = 'bismark1';
const user = '...';
const password = '.....';
(async() => {
console.log('authentication to c8y server')
const client = await Client.authenticate({
user,
password,
tenant
}, baseUrl);
console.log('result from authetication', client)
const {
data,
paging
} = await client.inventory.list();
console.log('result from inventory ', data)
// data = first page of inventory
const nextPage = await paging.next();
// nextPage.data = second page of inventory
const managedObjId: number = 1;
(async() => {
const {
data,
res
} = await client.inventory.detail(managedObjId);
console.log(data)
})();
})();
When I run the .js compiled form the .ts file I get the response below :
authentication to c8y server
And then the execution stops.
The line
console.log('result from authetication', client)
is never called. Seems like something fails in the authentication process and not error is showed.
What I'm doing wrong ?
Thanks.
The first problem might be CORS. You need to enable it if you want to request from a different domain. Here is a guide how to do that in Cumulocity:
Under "Access control", administrators can enable cross-origin
resource sharing or "CORS" on the Cumulocity API.
The second problem could be that you are not running it from a local development server. I mostly use this http-server from npm to quickly test scripts. You can use it the following way:
$ npm install http-server -g
$ http-server
If that all is not helping you might try catch the client to see the error it is throwing:
try {
const client = await Client.authenticate({
user,
password,
tenant
}, baseUrl);
} catch(ex) {
console.log(ex);
}
The exeption might tell you more about what is wrong with your code or if there is a bug in the client.