Refresh token with Graph OAuth v2.0 - javascript

I'm requesting a user's info via Microsoft Graph. I use the 2.0 endpoint.
This is my login function:
login() {
hello('msft').login({scope: Configs.scope}).then(
() => {
this.zone.run(() => {
this.meService.getMe().subscribe(data => {
localStorage.setItem('username', data.mail);
localStorage.setItem('jobtitle', data.jobTitle);
localStorage.setItem('loggedin', 'yes');
},
err => {
console.log(err);
},
() => {
this.router.navigate(['/home']);
});
});
},
e => console.error(e.error.message)
);
}
This is my init function:
initAuth() {
this.redirect_uri = window.location.href;
hello.init({
msft: {
id: Configs.appId,
oauth: {
version: 2,
auth: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize'
},
scope_delim: ' ',
form: false
},
},
{redirect_uri: window.location.href}
);
}
And here I am getting the access token:
getAccessToken() {
const msft = hello('msft').getAuthResponse();
console.log(msft);
const accessToken = msft.access_token;
return accessToken;
}
I get an access token, via which I can login. However, I get no refresh token. From what I read, you get the refresh and the access token via the /token endpoint. As far as I can see, I only use the /authorize endpoint and it works?
This poses a problem. I can't refresh my token!
A response looks like this:
access_token:
"This is private, but it's a very long string"
client_id:"e6c987d2-8bdc-4f1a-bafc-04ba3d51f340"
display:"popup"
expires:1524649746.548
expires_in:3599
network:"msft"
redirect_uri:"http://localhost:4200/"
scope:"basic,User.Read"
session_state:"89a68bd2-5ae5-4df2-88d0-d28718fd10bc"
state:""
token_type:"Bearer"
Any help would be appreciated!

Since you're using the Implicit grant, you cannot use Refresh Tokens. They're only supported using the Authorization Code grant.
In order to use Refresh Tokens, you'll need to switch to the Authorization Code grant and implement the server-side code to process the authorization code into an access token. You'll also need to request the scope offline_access which triggers the generation of a refresh_token.

Related

401 error while using #react-oauth/google

const googleLogin = useGoogleLogin({.
flow: 'auth-code',
onSuccess: async (codeResponse) => {
const response = await axios.post(
'my backend api '
{
accessToken: codeResponse.code,
oAuthAgency: 2,
}
);
console.log(codeResponse);
},
onError: (errorResponse) => console.log(errorResponse),
});
This is React Oauth2 grammar. I want to do by authorization code flow
sending authorization code to backend for getting access token.
I'm working on this for two days. Please help me.

Use firebase tokens to query an API in a client

I used firebase authentication to secure my ASP.NET CORE api.
I actually store the users in the database my API uses. Note that in my database the google identifiers are the uids generated by the firebase authentication and that the classic identifiers (login + password) are generated in my API.
When the user connects with Google, the token is created in the client(Angular) so I send it to my API (of course I don't store it), I just check if the token is valid and if the id which is contained in the token corresponds to the identifier of one of the users which is stored in my database.
In my client, for google authentification:
async GoogleAuth() {
try {
return new Promise((resolve, reject) => {
signInWithPopup(this.auth, this.provider).then(() => {
this.auth.onAuthStateChanged((user) => {
if (user) {
user
.getIdToken()
.then((idToken) => {
this.sendTokenUserGoogleToAPI(user, idToken)
.then((data: any) => {
localStorage.setItem('token', data.token);
resolve(data);
})
.catch((error) => {
console.log("googleAuth : " + error)
reject(error);
});
})
.catch(() => { });
}
});
});
});
} catch (e) { }
}
For the classic connection (login + password), the data is sent directly to my API and I create a personalized token with the user ID in my backend and I send the token to the client who generates a personalized token to from the token.
In my API, for classic authentication :
[HttpPost]
[ActionName("signin")]
public async Task<ActionResult> SignIn([FromBody] UserLoginViewModel userModel)
{
var user = await _context.Users.FirstOrDefaultAsync(u =>
u.Login == userModel.Login && u.Password == userModel.Password);
if (user == null)
{
_logger.LogWarning("Connection attempt failed.");
return NotFound(new { message = "User or password invalid." });
}
if (user.IsLocked)
{
return new ObjectResult(new { message = "Your account has been blocked." }) { StatusCode = 403 };
}
var token = await FirebaseAuth.DefaultInstance.CreateCustomTokenAsync(user.UserId);
var login = user.Login;
return Ok(new
{
login,
token
});
}
In my client, when I receive the token from my API
signInWithCustomToken(getAuth(),token)
.then((userCredential) => {
const user = userCredential.user;
console.log(token)
user!.getIdToken(true).then((idToken) => {
localStorage.setItem('token', idToken)
}).catch((error) => {
console.log(error)
})
})
So I'm guessing I shouldn't generate the token in my backend and only return the id to generate the token in the client? I regenerate the token in my client so that the user can access the chat
With my client, is it better that I get the token by querying firebase each time or is it better that I store this token locally to be able to use it in my requests?
For the moment, I store it locally but I think that it can be problematic if the token changes or if an attacker modifies his token because I verify thanks to firebase that the user is connected, if the local token changes, firebase will always say that the user is logged in but in my api the token will not be valid.
The ID token you get from Firebase Authentication is an exp property/claim that shows you until when it's valid. Firebase's own SDKs refresh the token about 5 minutes before it expires, so your code should probably do the same. In fact, if you listen for when the ID token changes (Android web), you don't have to force a refresh yourself and can just piggyback on the work the SDK already does for you.

Authenticated requests after sign in with React Query and NextAuth

I'm having troubled sending an authenticated request to my API immediately after signing in to my Nextjs app using NextAuth. The request that is sent after signing in returns data for and unauthenticated user.
I believe the issue is that React Query is using a previous version of the query function with an undefined jwt (which means its unauthenticated). It makes sense because the query key is not changing so React Query does not think it's a new query, but, I was under the impression that signing in would cause loading to be set to true temporarily then back to false, which would cause React Query to send a fresh request.
I've tried invalidating all the queries in the app using queryClient, but that did not work. I've also used React Query Devtools to invalidate this specific query after signing in but it still returns the unauthenticated request. Only after refreshing the page does it actually send the authenticated request.
// useGetHome.js
const useGetHome = () => {
const [session, loading] = useSession();
console.log(`session?.jwt: ${session?.jwt}`);
return useQuery(
'home',
() => fetcher(`/home`, session?.jwt),
{
enabled: !loading,
},
);
}
// fetcher
const fetcher = (url, token) => {
console.log(`token: ${token}`);
let opts = {};
if (token) {
opts = {
headers: {
Authorization: `Bearer ${token}`,
},
};
}
const res = await fetch(`${process.env.NEXT_PUBLIC_BACKEND_URL}${url}`, opts);
if (!res.ok) {
const error = await res.json();
throw new Error(error.message);
}
return res.json();
}
// Home.js
const Home = () => {
const { data: home_data, isLoading, error } = useGetHome();
...
return(
...
)
}
Attached is the console immediately after signing in. You can see the the session object contains the jwt after signing in, but in the fetcher function it is undefined.
console after signing in
Any help here is appreciated. Is there a better way to handle authenticated requests using React Query and NextAuth? Thank you!
I have tried a similar situation here and struggled the same thing but the enabled property worked fine for me and it is good to go right now.
https://github.com/maxtsh/music
Just check my repo to see how it works, that might help.

Different headers used in Axios patch

I spent an hour looking in the Chrome console and I cannot see where this bug comes from.
I am finishing an update of OAuth implementation in my Vue app.
The story begins when socialLink.js finds out that a new user must be created. Vue component Vue-authentication depends on the presence of access_token in a response so I return some dummy text:
return api.sendResponse(res, { email, name, socialId, access_token: 'abcd' });
The library stores this value in localStorage:
After a redirect, the SignUp.vue is rendered and I complete the form. The first communication with the server is a Vuex call to create a new user:
response = await this.$store.dispatch('CREATE_USER_PROFILE', payload);
Which returns a real short lived JWT token:
const token = auth.createToken(userId, nickname, new Date(), null, false, '1m');
return api.sendCreated(res, api.createResponse(token));
Which I store in the Vue page afterwards:
const { data } = response;
const token = data.data;
if (token === undefined) {
this.error = this.$t('sign-up.something-went-wrong');
return false;
}
I checked that the token contains what the server returned:
Request URL: https://beta.mezinamiridici.cz/api/v1/users
Request Method: POST
Status Code: 201 Created
{"success":true,"data":"eyJhbGciOiJIUzI1NiIs...Tl8JFw2HZ3VMXJk"}
Then I call another Vuex method and pass the current JWT token:
await this.$store.dispatch('UPDATE_USER_PROFILE', {
I checked in the Vuex devtools that there really is the correct JWT token. I then pass it further to api.js.
Here I create an Axios configuration holding an Authorization header:
function getAuthHeader(context, jwt = undefined, upload) {
const config = { headers: { } };
if (jwt || (context && context.rootState.users.userToken)) {
config.headers.Authorization = `bearer ${jwt || context.rootState.users.userToken}`;
}
Again, I checked that the correct JWT token is used there.
Finally, I pass all data to Axios:
function patch(endpoint, url, body, context, jwt) {
const headers = getAuthHeader(context, jwt);
console.log(headers);
if (endpoint === 'BFF') {
return axios.patch(`${VUE_APP_BFF_ENDPOINT}${url}`, body, headers);
} else {
return axios.patch(`${VUE_APP_API_ENDPOINT}${url}`, body, headers);
}
}
Which I log and can confirm the correct JWT is still there:
bearer eyJhbGciOiJIUzI1N....8JFw2HZ3VMXJk
There is nothing that could change the header now to abcd, but, the 'Network' tab shows it:
And the server fails with a parse error.
Has anybody got an idea why Axios uses the Authorization header with a different value than I pass it?
Ok, mystery solved. vue-authenticate is the reason, because, it creates Axios interceptors and handles the Authorization header itself.
vue-authenticate.common.js:
var defaultOptions = {
bindRequestInterceptor: function ($auth) {
var tokenHeader = $auth.options.tokenHeader;
$auth.$http.interceptors.request.use(function (config) {
if ($auth.isAuthenticated()) {
config.headers[tokenHeader] = [
$auth.options.tokenType, $auth.getToken()
].join(' ');
} else {
delete config.headers[tokenHeader];
}
return config
});
},
My code is more complex and it supports internal accounts with email/password so this code is breaking mine. The interceptor must be present and be a function, so the solution was:
Vue.use(VueAuthenticate, {
tokenName: 'jwt',
baseUrl: process.env.VUE_APP_API_ENDPOINT,
storageType: 'localStorage',
bindRequestInterceptor() {},
bindResponseInterceptor() {},
providers: {
facebook: {
clientId: process.env.VUE_APP_FACEBOOK_CLIENT_ID,
redirectUri: process.env.VUE_APP_FACEBOOK_REDIRECT_URI,
},

Meteor accounts-google Token Expires

I have the Accounts-UI config setup to store an offline token for google thusly:
if (Meteor.isClient) {
Accounts.ui.config({
requestOfflineToken: { google: true },
forceApprovalPrompt: { google: true },
requestPermissions: { google: ["https://mail.google.com/"] }
});
}
However, tokens seem to expire. I assume I need to somehow use the refreshToken. I'm not sure how though with meteor. Any help would be lovely. Thanks!
I recommend using Google API Node JS client to refresh your access tokens.
https://github.com/google/google-api-nodejs-client/
It's available as a server-side NPM package, so you might want to use this package to be able to npmRequire it in your Meteor app.
Use this packages.json config to load the latest googleapis package :
{
"googleapis": "2.1.5"
}
Then in your Meteor server code you'll be able to refresh the access tokens like this :
ES2015
const GoogleApis = Meteor.npmRequire('googleapis');
function getAccessToken(user) {
const googleService = user.services.google;
// is token still valid for the next minute ?
if (googleService.expiresAt < Date.now() + 60 * 1000) {
// then just return the currently stored token
return {
access_token: googleService.accessToken,
token_type: 'Bearer',
id_token: googleService.idToken,
expiry_date: googleService.expiresAt,
refresh_token: googleService.refreshToken,
};
}
// fetch google service configuration
const googleServiceConfig = Accounts.loginServiceConfiguration.findOne({
service: 'google',
});
// declare an Oauth2 client
const oauth2Client = new GoogleApis.auth.OAuth2(googleServiceConfig.clientId, googleServiceConfig.secret);
// set the Oauth2 client credentials from the user refresh token
oauth2Client.setCredentials({
refresh_token: user.services.google.refreshToken,
});
// declare a synchronous version of the oauth2Client method refreshing access tokens
const refreshAccessTokenSync = Meteor.wrapAsync(oauth2Client.refreshAccessToken, oauth2Client);
// refresh tokens
const tokens = refreshAccessTokenSync();
// update the user document with the fresh token
Meteor.users.update(user._id, {
$set: {
'services.google.accessToken': tokens.access_token,
'services.google.idToken': tokens.id_token,
'services.google.expiresAt': tokens.expiry_date,
'services.google.refreshToken': tokens.refresh_token,
},
});
//
return tokens;
}
Here is a full example of how to refresh your access tokens before using a google service.
function listMeteorChannel() {
// fetch a user you want to act on behalf who authorized offline access
const user = Meteor.users.findOne({
'services.google.refreshToken': {
$exists: true,
},
});
if (!user) {
return;
}
const googleServiceConfig = Accounts.loginServiceConfiguration.findOne({
service: 'google',
});
// declare oauth2 client and set credentials
const oauth2Client = new GoogleApis.auth.OAuth2(googleServiceConfig.clientId, googleServiceConfig.secret);
// get user access token
const tokens = getAccessToken(user);
oauth2Client.setCredentials(tokens);
// obtain the youtube service at version 3 and perform authentication at service level
const youtube = GoogleApis.youtube({
version: 'v3',
auth: oauth2Client,
});
// declare a synchronous version of youtube.channels.list
const youtubeChannelsListSync = Meteor.wrapAsync(youtube.channels.list, youtube.channels);
// fetch an info snippet from the Meteor official YouTube channel
const result = youtubeChannelsListSync({
part: 'snippet',
// Meteor channel ID
id: 'UC3fBiJrFFMhKlsWM46AsAYw',
});
result.items.forEach((item) => {
// display the channel title, which should be 'Meteor'
console.log(item.snippet.title);
});
}
Meteor.startup(listMeteorChannel);

Categories

Resources