Resend unathorized requests after changing token in Angualr8 - error-handling

I'm new to angular 8.
I have an interceptor :
export class HttpRequestInterceptor implements HttpInterceptor {
private apiAddress = 'http://localhost:1080';
private refreshTokenIsInProgress = false;
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
let cloneReq;
//...
// some codes like adding access token to header
//...
cloneReq = request.clone();
return next.handle(cloneReq)
.pipe(
catchError((error: any) => {
if(error.status==401)//means token expired
{
//Here i need help
//Get NEW Token And Replace With previous And Resend Current Request
}
return of(error);
})
)
;
}
}
export const httpInterceptorProviders = [
{ provide: HTTP_INTERCEPTORS, useClass: HttpRequestInterceptor, multi: true },
];
my requests are like:
return this.http.get(url).subscribe();
As I mentioned,if request returns 401(unauthorized) I need to get new token (JWT) from server , replace it with previous one, and then resend CURRENT request;
there is no problem on getting new token!
the problem is replacing new token and resend request;
i searched and googled form many hours but ...
i thought i can reach it by using retry() and retryWhen() , but they can't change request parameters.
thank you all buddy

Haven't tested the code, but you can try wrap the request into higher order function and get access to request object, also allow function to be called recursively until condition is met.
const repeatRequest=(cloneReq)=>next.handle(cloneReq)
.pipe(
catchError((error: any) => {
if(error.status==401)//means token expired
req=........ // modify your request here
return repeatRequest(req)
return of(error);
})
)
cloneReq = request.clone();
return repeatRequest(cloneReq)

Related

Update Next.js to React 18 breaks my API calls using next-auth

This is a strange one, but here's the situation.
I'm using Next.js with the Next-auth package to handle authentication.
I'm not using Server-Side rendering, it's an admin area, so there is no need for SSR, and in order to authenticate users, I've created a HOC to wrap basically all components except for the "/sign-in" route.
This HOC all does is check if there's a session and then adds the "access token" to the Axios instance in order to use it for all async calls, and if there is no session, it redirects the user to the "sign-in" page like this ...
const AllowAuthenticated = (Component: any) => {
const AuthenticatedComponent = () => {
const { data: session, status }: any = useSession();
const router = useRouter();
useEffect(() => {
if (status !== "loading" && status === "unauthenticated") {
axiosInstance.defaults.headers.common["Authorization"] = null;
signOut({ redirect: false });
router.push("/signin");
} else if (session) {
axiosInstance.defaults.headers.common["Authorization"] = `Bearer ${session.accessToken.accessToken}`;
}
}, [session, status]);
if (status === "loading" || status === "unauthenticated") {
return <LoadingSpinner />;
} else {
return <Component />;
}
};
return AuthenticatedComponent;
};
export default AllowAuthenticated;
And in the Axios instance, I'm checking if the response is "401", then I log out the user and send him to the "sign-in" screen, like this ...
axiosInstance.interceptors.response.use(
response => response,
error => {
const { status } = error.response;
if (status === 401) {
axiosInstance.defaults.headers.common["Authorization"] = null;
signOut({ redirect: false });
return Promise.reject(error);
}
return Promise.reject(error);
},
);
Very simple stuff, and it works like a charm until I decided to upgrade my project to use "react 18.1.0" and "react-dom 18.1.0", then all of a sudden, my API calls doesn't get the "Authorization" header and they return "401" and the user gets logged out :(
If I tried to make an API call inside the HOC right after I set the Auth headers it works, sot I DO get the "token" from the session, but all the async dispatch calls inside the wrapped component return 401.
I forgot to mention, that this issue happens on page refresh, if I didn't refresh the page after I sign in, everything works great, but once I refresh the page the inner async dispatch calls return 401.
I Updated all the packages in my project including Axios and next-auth, but it didn't help.
I eventually had to downgrade back to "react 17.0.2" and everything works again.
Any help is much appreciated.
For those of you who might come across the same issue.
I managed to solve this by not including the logic for adding the token to the "Authorization" header inside the HOC, instead, I used a solution by #kamal-choudhary on a post on Github talking about how to add "JWT" to every axios call using next-auth.
Using #jaketoolson help at that Github post, he was able to attach the token to every "Axios" call.
The solution is basically to create an Axios instance and add an interceptor like I was doing above, but not just for the response, but also for request.
You'll add an interceptor for every request and check if there's a session, and then attach the JWT to the Authorization header.
That managed to solve my issue, and now next-auth works nicely with react 18.
Here's the code he's using ...
import axios from 'axios';
import { getSession } from 'next-auth/react';
const baseURL = process.env.SOME_API_URL || 'http://localhost:1337';
const ApiClient = () => {
const defaultOptions = {
baseURL,
};
const instance = axios.create(defaultOptions);
instance.interceptors.request.use(async (request) => {
const session = await getSession();
if (session) {
request.headers.Authorization = `Bearer ${session.jwt}`;
}
return request;
});
instance.interceptors.response.use(
(response) => {
return response;
},
(error) => {
console.log(`error`, error);
},
);
return instance;
};
export default ApiClient();
Don't forget to give them a thumbs up for their help if it works for you ...
https://github.com/nextauthjs/next-auth/discussions/3550#discussioncomment-1993281
https://github.com/nextauthjs/next-auth/discussions/3550#discussioncomment-1898233

Nuxt middleware call two times

I have a Nuxt SSR application. There is a middleware on the page which should check token in query string and it it is not there then redirect to new url with token. It seems that this middleware is called two times under the same url but with the different result for token check. I dont understand why it is called two times and the result for the same condition is different.
/** Create DB item and return token */
async middleware(context) {
const token = context.route.query.token;
console.log('token');
console.log(token); // First one is ok but the second is undefined.
if (!token) {
const result = await context.$api.campaigns.createNewShare();
context.redirect({'name': 'campaigns-new', 'query': {token: result.data.token}});
}
},
This its the screenshot of the request flow:
Basically because you're using SSR, that middleware is going to be called once from the server and once from the client, hence why you can see duplicated logs.
You can prevent the middleware from being run on the server by using if (process.client) or prevent the middleware from being run on the client by using if (process.server).
Example:
/** Create DB item and return token */
async middleware(context) {
if (process.client) { // or !process.server if you prefer
const token = context.route.query.token;
console.log('token');
console.log(token); // First one is ok but the second is undefined.
if (!token) {
const result = await context.$api.campaigns.createNewShare();
context.redirect({'name': 'campaigns-new', 'query': {token: result.data.token}});
}
}
}
You probably have the middleware defined twice.
Check if you have this in your route (page):
middleware: ['yourMiddleware']
and this in nuxt.config.js:
router: {
middleware: ['yourMiddleware'],
..
}

i18n-backend with oauth2 authorization in spartacus

We want to use a backend for i18n in spartacus. Unfortunately this backend needs an oauth2 authentication but spartacus does not send a bearer token when trying to access this webservice endpoint and we get a 401 error. Is there anything we can do?
Right now we try to solve this problem in this way:
What we need to have is implemented in ClientTokenInterceptor, so we adapted this interceptor, changed the if-clause a little bit so it fits to the backend-url for this webservices and provide the interceptor via app.module.ts which works so far. Unfortunately calling this.authService.getClientToken() in our Interceptor returns no token.
constructor(
private authService: AuthService,
private occEndpoints: OccEndpointsService
) {}
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return this.getClientToken(request).pipe(
take(1),
switchMap((token: ClientToken) => {
if (
token &&
request.url.includes("i18n")
) {
request = request.clone({
setHeaders: {
Authorization: `${token.token_type} ${token.access_token}`,
},
});
}
return next.handle(request);
})
);
}
private getClientToken(request: HttpRequest<any>): Observable<ClientToken> {
if (
InterceptorUtil.getInterceptorParam(USE_CLIENT_TOKEN, request.headers)
) {
return this.authService.getClientToken();
}
return of(null);
}
What do we miss?
Actually there are couple of things not needed in your solution.
I pasted below what I did instead and tested that it is working correctly (and you can see authorization data in the translation files requests).
First issue:
InterceptorUtil.getInterceptorParam(USE_CLIENT_TOKEN, request.headers) you don't need to check that. If you always need the auth data for translation requests just use return this.authService.getClientToken();
Second issue:
In intercept method you didn't cover cases for any other request than translation. Because of that the request for the client token would hang here, because it would wait for token and so on. If you add option for any other case than i18n it starts working as you intend.
Working solution:
#Injectable({ providedIn: 'root' })
export class TranslationsInterceptor implements HttpInterceptor {
constructor(private authService: AuthService) {}
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
if (request?.url?.includes('i18n')) {
return this.getClientToken().pipe(
take(1),
switchMap((token: ClientToken) => {
if (token) {
request = request.clone({
setHeaders: {
Authorization: `${token.token_type} ${token.access_token}`,
},
});
}
return next.handle(request);
})
);
} else {
return next.handle(request);
}
}
private getClientToken(): Observable<ClientToken> {
return this.authService.getClientToken();
}
}

how to add attributes to a PUT request in GUN?

I have the following code in my HTML page
Gun.on('opt', function (ctx) {
if (ctx.once) {
return
}
this.to.next(ctx)
window.auth = ctx.opt.auth
ctx.on('get', function (msg) {
msg.auth = window.auth
this.to.next(msg)
})
ctx.on('put', function (msg) {
msg.put.auth = window.auth
this.to.next(msg)
})
})
var gun = Gun({
peers: ['http://localhost:8765/gun'],
auth: {
user: 'mroon',
password: 'titi'
}
})
On the server, I simply watch the requests
Gun.on('create', function(db) {
console.log('gun created')
this.to.next(db);
db.on('get', function(request) {
// this request contains the auth attribute from the client
this.to.next(request);
});
db.on('put', function(request) {
// this request does not contain the auth attribute from the client
this.to.next(request);
});
});
every time I query the graph with gun.get('someAttribute') the request on the server contains the auth attribute.
but when a gun.get('someAttribute').put({attribute: 'my new value'}) is called, the request on the server does not contain the auth attribute.
How can I add the auth attribute to the put request in such a way that all the peers will get it too?
#micha-roon you jumped straight to GUN's core/internal wire details, which is not the easiest thing to start with, but here is something I do that I'm guessing is what you are looking for:
(if not, please just comment & I'll update)
What this does is it adds a DEBUG flag to all outbound messages in GUN, you can change this to add other metadata or info
Gun.on('opt', function(root){
if(!root.once){
root.on('out', function(msg){
msg.DBG = msg.DBG || +new Date;
this.to.next(msg);
});
}
this.to.next(root);
})
Also another good reference: https://github.com/zrrrzzt/bullet-catcher

FeatherJS - Get user information with hook?

So im trying out FeatherJS and i was able to register a new user, request a token and also request protected data (using Authorization in the header).
Very important: I am using HTTP Rest API only. The docs seem to point often times to the client feathers module, which i don't use.
So currently i have a super simple setup, i have a message service with a text. One before hook to process the message. Here i want to get back the user information:
module.exports = function (options = {}) {
return async context => {
const text = context.data.text
const user = context.params.user;
context.data = {
text,
userId: user._id
}
return context;
};
};
this doesn't work. In my mdb i only get back:
{
"_id": "5c35ce18523501803f6a8d8d",
"text": "123",
"createdAt": "2019-01-09T10:34:00.774Z",
"updatedAt": "2019-01-09T10:34:00.774Z",
"__v": 0
}
i've tried to add the token, that i always submit when i post a message via Authorization, like so:
module.exports = function (options = {}) {
return async context => {
const text = context.data.text
const user = context.params.user;
const token = context.params.accessToken
context.data = {
text,
userId: user._id,
tokId: token
}
return context;
};
};
but it seems like i always just get the same result back like shown above.
Any ideas how i can get the user information back of the current user by using the accessToken?
Never used FeathersJS before, so just trying to understand the ecosystem and how to approach this in FeathersJS.
Any advice is appreciated! Thanks in advance everyone!
Not quite sure what exactly went wrong, but i got it now working by just creating a new project.
Now i did recreate this project actually before and got the issue as above , but this time it somehow worked.
For anyone who wants to know the steps i did to 'fix' it:
1.Create a new folder
2. feathers generate app
3. feathers generate authentication
4. feathers generate service (name for the service: messages)
5. feathers generate hook (name: process-msg, before hook, model: messages)
6. Add this to the hook process-msg:
module.exports = function (options = {}) {
return async context => {
const user = context.params.user;
const text = context.data.text;
context.data = {
userId: user.email,
text,
dateTime: new Date().getTime()
}
return context;
};
};
Use postman, register a new account then authenticate to get the token. Save token and add it as Authoriztation Header inside Postman. You should then get also back the user email from the user that is registered, simply because of the token that was added to the Authorization Header.
Greetings!
go to authentication.js and find app.service definition. Right in there, create an after hook and add the details you want the client to receive
app.service('authentication').hooks({
before: {
...//as you currently have it
},
after: {
create: {
hook => {
// hook.result.accessToken is already provided
delete hook.params.user.password
hook.result.user = hook.params.user;
hook.result.token_type = 'Bearer';
hook.result.exp = 3600;
}
}
}
})
I hope this helps
So, if I understand it correctly, you want to get the user object from the hook?
You can just use const user = context.user;to accomplish this.