How to add Firebase JWT to an Angular HTTP Request? - javascript

I have my own HTTP Service which provides GET, POST, PUT and DELETE methods and enables me to send the JWT from my Firebase user in the HTTP request header.
Now I'm able to get my user object from a redux store, and I can retrieve the JWT via the getIdToken() method on this user object. Unfortunately this method provides me with a Promise<string>.
Is there any way, I can build my Http-Options Object with this JWT-Header, before actually sending the request via Angular's HttpClient?
My Code:
async delete<T = any>(appendix: string): Observable<T> {
const options: any = await this.httpOptions();
return this.http.delete<T>(this.url + appendix, options);
}
private async httpOptions(): Promise<{ headers: { uid: string } }> {
const user: User = this.authQuery.getValue();
const token: string = await user.getIdToken().catch();
return {
headers: {uid: token},
};
}

Related

React Native access token expiration/renewal upon 403 response code from a RTK Query API

I am calling an API defined using RTK Query, within a React Native + Redux Toolkit + Expo app. This is secured with an authentication / authorization system in place i.e. access token (short expiration) and refresh token (longer expiration).
I would like to avoid checking any access token expiration claim (I've seen people suggesting to use a Redux middleware). Rather, if possible, I'd like to trigger the access token renewal when the API being requested returns a 403 response code, i.e. when the access token is expired.
This is the code calling the API:
const SearchResults = () => {
// get the SearchForm fields and pass them as the request body
const { fields, updateField } = useUpdateFields();
// query the RTKQ service
const { data, isLoading, isSuccess, isError, error } =
useGetDataQuery(fields);
return ( ... )
the RTK Query API is defined as follows:
import { createApi, fetchBaseQuery } from "#reduxjs/toolkit/query/react";
import * as SecureStore from "expo-secure-store";
import { baseUrl } from "~/env";
export const api = createApi({
reducerPath: "api",
baseQuery: fetchBaseQuery({
baseUrl: baseUrl,
prepareHeaders: async (headers, { getState }) => {
// retrieve the access_token from the Expo SecureStore
const access_token = await SecureStore.getItemAsync("access_token");
if (access_token) {
headers.set("Authorization", `Bearer ${access_token}`);
headers.set("Content-Type", "application/json");
}
return headers;
},
}),
endpoints: (builder) => ({
getData: builder.query({
// body holds the fields passed during the call
query: (body) => {
return {
url: "/data",
method: "POST",
body: body,
};
},
}),
}),
});
export const { useGetDataQuery } = api;
I understand that when the API returns isError = true and error = something 403 I need to renew the access token within the Expo SecureStore (and there's a function already in place for that). However I have no idea about how can I query the RTKQ API again, on the fly, when it returns a 403 response code, and virtually going unnoticed by the user.
Can someone please point me in the right direction?
I got the hang of it, massive thanks to #phry! I don't know how I could have missed this example from RTKQ docs but I'm a n00b for a reason after all.
This being said, here's how to refactor the RTKQ api to renew the access token on the fly, in case some other react native beginner ever has this problem. Hopefully this is a reasonable way of doing this
import { createApi, fetchBaseQuery } from "#reduxjs/toolkit/query/react";
import * as SecureStore from "expo-secure-store";
import { baseUrl } from "~/env";
import { renewAccessToken } from "~/utils/auth";
// fetchBaseQuery logic is unchanged, moved out of createApi for readability
const baseQuery = fetchBaseQuery({
baseUrl: baseUrl,
prepareHeaders: async (headers, { getState }) => {
// retrieve the access_token from the Expo SecureStore
const access_token = await SecureStore.getItemAsync("access_token");
if (access_token) {
headers.set("Authorization", `Bearer ${access_token}`);
headers.set("Content-Type", "application/json");
}
return headers;
},
});
const baseQueryWithReauth = async (args, api) => {
let result = await baseQuery(args, api);
if (result.error) {
/* try to get a new token if the main query fails: renewAccessToken replaces
the access token in the SecureStore and returns a response code */
const refreshResult = await renewAccessToken();
if (refreshResult === 200) {
// then, retry the initial query on the fly
result = await baseQuery(args, api);
}
}
return result;
};
export const apiToQuery = createApi({
reducerPath: "apiToQuery",
baseQuery: baseQueryWithReauth,
endpoints: (builder) => ({
getData: builder.query({
// body holds the fields passed during the call
query: (body) => {
return {
url: "/data",
method: "POST",
body: body,
};
},
}),
}),
});
export const { useGetDataQuery } = apiToQuery;

How do I add a JS state variable of 1 React component for use in other components?

I have a Home.js component that signs the user up to the API and logs in and then gets the token received from the response authorization header and saves it in the state 'token' variable.
This token will be used in all other components to access the API when requests are made, so what is the best way of using this value for all other components?
Home.js:
const SIGNUP_URL = 'http://localhost:8080/users/signup';
const LOGIN_URL = 'http://localhost:8080/login';
class Home extends Component {
constructor(props) {
super(props);
this.state = {
isAuthenticated:false,
token: ''
};
}
componentDidMount() {
const payload = {
"username": "hikaru",
"password": "JohnSmith72-"
};
fetch(SIGNUP_URL, {
method: 'POST',
headers: {
"Accept": "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(payload)
})
.then(response => response.json())
.then((data) => {
console.log(data);
});
fetch(LOGIN_URL, {
method: 'POST',
headers: {
"Accept": "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(payload)
})
.then(response =>
this.setState({token: response.headers.get("Authorization"), isAuthenticated:true})
)
}
For example the userList component which will fetch the user data from the API, but requires the API token stored in the Home component's token state variable to send the request successfully via the authorization header.
Thanks for any help
You can create a custom function called authenticated_request for example. This function could fetch your token from the CookieStorage in case of web or async storage in case of react-native or even if you have it in some state management library. Doesn't matter. Use this function instead of the fetch function and call fetch inside it. Think of it as a higher order function for your network requests.
const authenticated_request(url, config) {
fetch(url, {
...config,
headers: {
...config.headers,
Authorization: getToken()
}
});
}
You can also leverage the usage of something like axios and use request interceptors to intercept requests and responses. Injecting your token as needed.
You should be using AuthContext and localStorage to do this, save the token in the state or localStorage and make a config file which uses the same token when calling an api i have done it in axios. Axios has a concept of interceptors which allows us to attach token to our api calls, Im saving the token in the localStorage after a successfull login and then using the same token from localStorage to add to every call which needs a token, if the api doesnt need a token (some apis can be public) i can use axios directly, check out the below code:
import axios from 'axios';
let apiUrl = '';
let imageUrl = '';
if(process.env.NODE_ENV === 'production'){
apiUrl = `${process.env.REACT_APP_LIVE_URL_basePath}/web/v1/`;
}else{
apiUrl = `http://127.0.0.1:8000/web/v1/`;
}
const config = {
baseURL: apiUrl,
headers: {
'Content-Type': 'application/json;charset=UTF-8',
"Access-Control-Allow-Origin": "https://www.*******.com",
'Access-Control-Allow-Methods': 'GET, POST, PATCH, DELETE',
},
};
const authAxios = axios.create(config);
authAxios.interceptors.request.use(async function(config) {
config.headers.Authorization = localStorage.getItem('access_token') ?
`Bearer ${localStorage.getItem('access_token')}` :
``;
return config;
});
export { apiUrl, axios, authAxios };
now on making api call u can do something like below:
import { apiUrl, authAxios } from '../config/config'
export async function saveAssignment(data) {
try {
const res = await authAxios.post(apiUrl + 'assignment/save-assignment', data)
return res.data;
}
catch(e){
}
}
here pay attention im not using axios to make api call but using authAxios to make calls(which is exported from the config file) which will have token in the header.
(You can also use a third party library like Redux but the concept remains the same)
You need a centralized state that's what State Management libraries are for. You can use third-party libraries such as Redux, or simply use React's own context. You can search on google for state management in React and you'll find a lot of helpful recourses
You can place the token into a cookie if your app is SSR. To do that, you have to create the following functions:
export const eraseCookie = (name) => {
document.cookie = `${name}=; Max-Age=-99999999;`;
};
export const getCookie = (name) => {
const pairs = document.cookie.split(';');
const pair = pairs.find((cookie) => cookie.split('=')[0].trim() === name);
if (!pair) return '';
return pair.split('=')[1];
};
export const setCookie = (name, value, domain) => {
if (domain) {
document.cookie = `${name}=${value};path=/`;
} else {
document.cookie = `${name}=${value}`;
}
};
You can also place your token into the local storage:
Set into local storage via built-in function:
localStorage.setItem('token', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c');
Get the token via built-in function:
const token = localStorage.getItem('token');

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,
},

How to wait Promise in constructor

I have Angular Service which make some http requests, but I need to get headers for those requests from Promise. Here how it works right now, I convert my promise to Observable:
export class SomeService {
constructor(private http: HttpClient, private auth: AuthenticationService) {}
getInfo(id: string): Observable<any> {
return this.auth.getToken().pipe(mergeMap((token: any) => {
let httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token
})
}
return this.http.get(`${this.serverURL}/info`, httpOptions);
}))
}
}
getToken() {
return from(this.storage.get(TOKEN_KEY));
}
But obviously I have like 20-50 requests and it's not too good to fetch auth token with every request.
I want to fetch my token once and use it for all request. Also I have other header which comes from Promise I need to use in my request. So, how can I get my async headers once (probably in constructor) in this case?
First off consider if optimizing this code is actually needed. Optimizing for performance is often only useful in parts of code which are run very frequently. When you say you do some 20 to 50 requests it does not sound like it's used a lot (other parts of your app are probably a lot more cpu intensive).
That being said: if you still want to solve this you could indeed fetch the token in your constructor.
export class SomeService {
// We store the observable here for later use
private getTokenObservable: Observable<string>;
constructor(private http: HttpClient, private auth: AuthenticationService) {
// Retrieve the token now and store the observable
this.getTokenObservable = getToken();
}
getInfo(id: string): Observable<any> {
// Zip the two observables together
return zip(
// Re use the previously stored observable
this.getTokenObservable,
// Also request the last login
this.auth.getLastLogin()
).pipe(mergeMap((result) => {
const token = result[0];
const lastLogin = result[1];
let httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token
})
}
return this.http.get(`${this.serverURL}/info`, httpOptions);
}))
}
}
getToken() {
return from(this.storage.get(TOKEN_KEY));
}
This works because you can subscribe multiple times to the same observable. So we request and store the getToken observable only once and then re use it for each request.
Also note how we use the zip operator provided by rxjs. This allows us to merge two observables together so we can handle the result of both observables in a single function.
You can write separate service for getting the token and call the service only once. For next time when you need token check if already you have token value in variable of service, so can skip http request and simply return token to requester.
private _tokenObsevable = new BehaviorSubject<string>(null);
constructor(...) {
this.auth.getToken().subscribe(token => this._tokenObservable.next(token))
}
getInfo(...) {
this._tokenObservable.pipe(
switchMap(token => // rest the same
)
optional you can create getter like
get tokenObservable() {
return this._tokenObservable.pipe(filter(val => !!val))
}
in this case you get only non null values, but also if token won't appeare you'll get stuck
In this case you can create a utility function in a file and can import it everywhere you need this token, or can create a service if this token is coming form a server call and then can inject it in all the places you need this token.
This token can also be saved in a const files and imported in the place wherever required.

Asynchronously authenticate before request

So I have an API and I am trying to authenticate by hitting an endpoint with credentials (this part I've gotten working) and then save the received token and use it in all subsequent requests.
My problem is that the authenticate() method is asynchronous, but all other request methods like get() need the token from the authenticate() method. So I can't just export my get() method because the export is synchronous (as I've read) and it will be exported before authentication happens. I could authenticate for every request but that seems wasteful and inefficient.
I am not sure what to do here, I'm using axios, what's the proper way of doing this?
Edit
I'll be a bit more specific here. I have created an axios instance:
var instance = axios.create({
baseURL: `http://${config.server}:${config.port}`,
timeout: 1000,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
})
I want to get the authentication token, and include it in the instance header:
async function authenticate(instance) {
const result = await instance.post(
'/session',
{
'username': config.username,
'password': config.password
}
)
instance['X-Token'] = result.data.token
}
Now I want to export that instance to be used in other files
You can use async/await. This is semi-pseudocode:
async function doStuff() {
const result = await axios.authenticate();
const token = // extract token from whatever format of result is
const data = await axios.get(/* supply token to get */);
}
Alternatively, you can just use then:
function doStuff(token) {
const token = // extract token from whatever format of result is
const data = await axios.get(/* supply token to get */);
}
axios.authenticate().then(result => {
const token = // extract token from whatever format of result is
doStuff(token);
}
With Axios you have the ability to set default values for all requests.
So for just a single axios instance you can do...
async function authenticate(instance) {
const result = await instance.post(
'/session',
{
'username': config.username,
'password': config.password
}
)
instance.defaults.headers.common['X-Token'] = result.data.token;
}
Alternatively, (which it sounds like you want to do) you can add it for the default Axios export. Then all requests will automatically have the header.
async function authenticate(endpoint, username, password) {
const res = await axios.post(`${endpoint}/session`, { username, password });
axios.defaults.headers.common['X-Token'] = result.data.token;
}
Then you don't have to worry about passing around an instance between all parts of your app and can just use import * as axios from 'axios' and have the header set.
Axios also provides and extremely helpful function called interceptors which you can use to inspect a request prior to making it. You can use to check to make sure that the request has the auth header and if it doesn't you can perform that logic. I came up with this and it seems to work well!
axios.interceptors.request.use(async (config) => {
// request intercepted, check (1) the header is missing and (2) that the intercepted request isn't authorizing
if (!config.headers.common['X-Token'] && config.authorizing !== true) {
const { endpoint, username, password } = appConfig;
// make a request to get your token AND pass our custom config
const result = await axios.post(`${endpoint}/session`, { username, password }, { authorizing: true });
// update axios to include the header for future requests
axios.defaults.headers.common['X-Token'] = result.data.token;
}
return config;
});
Two things that you'll want to note -- not only do I check for the existence of your X-token header I also check for a new authorization value in the config. You want to check for that config value, because we are going to use it as a flag to let the interceptor know if it should skip a request. If you don't do this, the authorization request will trigger another authorization request and infinite loop.

Categories

Resources