On the frontend code, I have a user login form that takes in the values (strings) email and password. In my userstore using MobX State Management, I have an action when a user presses the login button to submit the strings as an HTTP post
#action login = async (values: IUserFormValues) => {
try {
console.log(values);
const user = await agent.User.login(values);
runInAction(() => {
this.user = user;
});
console.log(user);
} catch (error) {
console.log(error);
}
};
}
The Request looks something like this:
const responseBody = (response: AxiosResponse) => response.data;
const requests = {
post: (url: string, body: {}) =>
axios.post(url, body).then(sleep(1000)).then(responseBody),
};
login: (user: IUserFormValues): Promise<IUser> =>
requests.post(`/user/login`, user)
Now to the backend, this is where I am completely lost. Not sure what to build from here:
[HttpPost("login")]
- Here -
I am gonna have to take these values and verify with a database from SQL server. There's just so many different examples using different middleware that I am just not sure what's correct or best practices.
Here is a very nice tutorial from the Microsoft Docs, using Entity Framework (very nice), and some dependency injection (very very nice).
Basically you create an API controller class with your CRUD methods in them like so:
namespace MyApiControllerClass
{
[Authorize]
[RoutePrefix("users")]
public class UsersApiController : ControllerBase
{
private readonly UserContext _context;
public UsersApiController(UserContext context)
{
_context = context;
}
[Route("/login")]
public IHttpActionResult LoginUser(User user)
{
try
{
// login logic here
return Ok(); // you can return whatever you need
}
catch (Exception exception)
{
// log any issues using your preferred method of logging
return InternalServerError(); // you can return different status codes as well. Depends on what you want
}
}
}
}
You can read more about the Authorize annotation here and customize it to your liking.
Then you fire up your web project which will be available at a local URL that you can set in the project's configuration say http://localhost:4000/ which then makes your controller URL available at http://localhost:34501/users/login. Then you use this URL in your Javascript call and add the User object in the request body.
I am new to both. My thought process is as follows. When I login in with Angular to my Firebase backend I send a response to the console if it succeeds. In this response I can see all the keys that a firebase user has. What I want to do is to link these to/in a user model in my front-end so I can access it easily when showing a user profile page or something else. (I don't know if this is the correct thought process too. Nudge me in the right way if you know a better solution)
auth.service.ts
constructor(
private angularFireAuth: AngularFireAuth,
) {
this.userData = angularFireAuth.authState;
}
signIn(email: string, password: string) {
return this.angularFireAuth.auth.signInWithEmailAndPassword(email, password);
}
login.component.ts
signIn(email: string, password: string) {
this.spinnerButtonOptions.active = true;
email = this.loginForm.value.email;
password = this.loginForm.value.password;
this.auth.signIn(email, password).then(
res => {
console.log('signed in ', res);
this.router.navigate(['/dashboard']);
}
).catch(
error => {
console.log('something went wrong ', error);
this.formError = true;
this.spinnerButtonOptions.active = false;
}
);
}
How would I got on to do this? I've searched everywhere and can't find any solution. Is this actually the correct way? If there is a better way please let me know!
You can use your auth service to store the data returned from the Firebase backend. Or else you can store it in a shared service where it's been available throughout the all components and modules.
In your auth service :
#Injectable()
export class AuthService{
public usermodel:any;
constructor(private angularFireAuth: AngularFireAuth) {
this.userData = angularFireAuth.authState;
}
signIn(email: string, password: string) {
return this.angularFireAuth.auth.signInWithEmailAndPassword(email, password);
}
setLoggedInUserData(userDetails: any) {
this.usermodel = userDetails;
}
getLoggedInUserData() {
return this.usermodel;
}
}
In you login component.ts :
signIn(email: string, password: string) {
this.spinnerButtonOptions.active = true;
email = this.loginForm.value.email;
password = this.loginForm.value.password;
this.auth.signIn(email, password).then(
res => {
this.authService.setLoggedInUserData(res);
this.router.navigate(['/dashboard']);
}
).catch(
error => {
console.log('something went wrong ', error);
this.formError = true;
this.spinnerButtonOptions.active = false;
}
);
}
In other components where you need to use the use details inject the auth.service.ts and use the getLoggedInUserData() method fetch the logged in users details.
There are several other ways of doing this too. One option is to use the ngrx store implementation. Other ways is to use global data service at the root level of your angular app to store the user details.
When the token expires, I want to get a new token based on refresh_token. I have read that this can be obtained with axios.interceptors.
Please check if:
Have I correctly configured axios.interceptors?
Have I placed it in the right place, i.e. above theItems class.
axios.interceptors.response is assigned to theinterceptor variable. What should I do with this variable?
In addition to `axios.interceptors', I need to get a new token. The token is valid for 24 hours.
Do I have to wait 24 hours to test whether it works, or is it possible in a different way, faster?
Where should I put 'client_id', 'secret_id', 'grant_type'?
Code here: https://stackblitz.com/edit/react-pkea41
import axios from 'axios';
axios.defaults.baseURL = localStorage.getItem('domain');
const interceptor = axios.interceptors.response.use(
response => response,
error => {
// Reject promise if usual error
if (errorResponse.status !== 401) {
return Promise.reject(error);
}
/*
* When response code is 401, try to refresh the token.
* Eject the interceptor so it doesn't loop in case
* token refresh causes the 401 response
*/
axios.interceptors.response.eject(interceptor);
return axios.post('/api/refresh_token', {
'refresh_token': JSON.parse(localStorage.getItem('token'))['refresh_token']
}).then(response => {
/*saveToken();*/
localStorage.setItem('token', JSON.stringify(response.data));
error.response.config.headers['Authorization'] = 'Bearer ' + response.data.access_token;
return axios(error.response.config);
}).catch(error => {
/*destroyToken();*/
localStorage.setItem('token', '');
this.router.push('/login');
return Promise.reject(error);
}).finally(createAxiosResponseInterceptor);
}
);
class Items extends Component {
constructor (props) {
super(props);
this.state = {
}
}
render () {
return (
<div >
</div>
)
}
}
render(<Items />, document.getElementById('root'));
This is what I did before. Your configuration is a little different from mine.
const baseURL = localStorage.getItem('domain');
const defaultOptions = {
baseURL,
method: 'get',
headers: {
'Content-Type': 'application/json',
}
};
// Create Instance
const axiosInstance = axios.create(defaultOptions);
// Get token from session
const accessToken = ...
// Set the auth token for any request
instance.interceptors.request.use(config => {
config.headers.Authorization = accessToken ? `Bearer ${accessToken}` : '';
return config;
});
// Last step: handle request error general case
instance.interceptors.response.use(
response => response,
error => {
// Error
const { config, response: { status } } = error;
if (status === 401) {
// Unauthorized request: maybe access token has expired!
return refreshAccessToken(config);
} else {
return Promise.reject(error);
}
}
});
I think this part should be separated with Components - it will be placed on helpers or utils.
Also, you have to wait for 24 hrs because refreshToken() method is never called before 24 hrs.
You don't need to process client_id, secret_id, grant_type right here.
Please check if I have correctly configured axios.interceptors.
I think it works. But I suggest that you should test it carefully.This is a good article to refer https://blog.liplex.de/axios-interceptor-to-refresh-jwt-token-after-expiration/
Have I placed it in the right place, i.e. above theItems class. ?
You should create a service function to wrap Axios and API configs,and interceptor of course
axios.interceptors.response is assigned to the interceptor variable. What should I do with this variable?
It is just a variable used to define the interceptor. Don't care about it. If you want to avoid assigning it, just do it inside a function like this Automating access token refreshing via interceptors in axios
I have to wait 24 hours to test whether it works, or is it possible in a different way, faster?
You can change the token saved in your localStorage, and do that
Where should I put 'client_id', 'secret_id', 'grant_type'?
If you store it inside localStorage, it's accessible by any script inside your page (which is as bad as it sounds as an XSS attack can let an external attacker get access to the token).
Don't store it in local storage (or session storage). If any of the 3rd part scripts you include in your page gets compromised, it can access all your users' tokens.
The JWT needs to be stored inside an HttpOnly cookie, a special kind of cookie that's only sent in HTTP requests to the server, and it's never accessible (both for reading or writing) from JavaScript running in the browser.
Please check if I have correctly configured axios.interceptors.
From what I can see the configuration seems ok, as it's the same of this answer https://stackoverflow.com/a/53294310/4229159
Have I placed it in the right place, i.e. above theItems class. ?
That is something that I can't answer, every application is different, it's not the best place to put it, but might be OK for an example. In your app however it should be together with all the API calls (for example)
axios.interceptors.response is assigned to theinterceptor variable. What should I do with this variable?
As you can see, the variable that got answered from the call to /refresh_token for assigned to config.headers['Authorization'] = 'Bearer ' + response.data.access_token; if you backend reads from there the auth value you should be fine
I have to wait 24 hours to test whether it works, or is it possible in a different way, faster?
You should wait unless the backend can change that, and expire the token in less time (EG in 5 or 2 minutes)
Where should I put 'client_id', 'secret_id', 'grant_type'?
Seems like the backend should have that, unless they are public ones... You are probably the best to know whether that belongs to the config for the call or if you are authenticating with them. If you are authenticating with them and they are the ones that grant you a token, then you shouldn't put it in the client side, as it is a security risk
1) Configuration looks fine to me. But your solution won't work when there are multiple parallel requests and all of them trying to refresh auth token at the same time. Believe me this is a issue is really hard to pin point. So better be covered upfront.
2) No. Not the right place. Create a separate service (I call it api.service) and do all the network/api commutation using that.
3) There is no use of interceptor variable. You can avoid assigning it to a variable.
4) If have control over the API you can reduce the timeout for a bit. Also i think 24 hours is bit too long. Else no option I guess.
5) Not sure you have to deal with them.
Bellow is a working code of api.service.ts. You might have to change few things here and there to fit that in to your application. If you get the concept clearly it wont be hard. Also it cover multiple parallel request problem as well.
import * as queryString from 'query-string';
import axios, { AxiosRequestConfig, Method } from 'axios';
import { accountService } from '../account.service'; //I use account service to authentication related services
import { storageService } from './storage.service'; //I use storage service to keep the auth token. inside it it uses local storage to save values
var instance = axios.create({
baseURL: 'your api base url goes here',
});
axios.defaults.headers.common['Content-Type'] = 'application/json';
export const apiService = {
get,
post,
put,
patch,
delete: deleteRecord,
delete2: deleteRecord2
}
function get<T>(controller: string, action: string = '', urlParams: string[] = [], queryParams: any = null) {
return apiRequest<T>('get', controller, action, null, urlParams, queryParams);
}
function post<T>(controller: string, action: string = '', data: any, urlParams: string[] = [], queryParams: any = null) {
return apiRequest<T>('post', controller, action, data, urlParams, queryParams);
}
function put<T>(controller: string, action: string = '', data: any, urlParams: string[] = [], queryParams: any = null) {
return apiRequest<T>('put', controller, action, data, urlParams, queryParams);
}
function patch<T>(controller: string, action: string = '', data: any, urlParams: string[] = [], queryParams: any = null) {
return apiRequest<T>('patch', controller, action, data, urlParams, queryParams);
}
function deleteRecord(controller: string, action: string = '', urlParams: string[] = [], queryParams: any = null) {
return apiRequest<any>('delete', controller, action, null, urlParams, queryParams);
}
function deleteRecord2<T>(controller: string, action: string = '', urlParams: string[] = [], queryParams: any = null) {
return apiRequest<T>('delete', controller, action, null, urlParams, queryParams);
}
function apiRequest<T>(method: Method, controller: string, action: string = '', data: any, urlParams: string[] = [], queryParams: any = null) {
var url = createUrl(controller, action, urlParams, queryParams);
var options = createRequestOptions(url, method, data);
return instance.request<T>(options)
.then(res => res && res.data)
.catch(error => {
if (error.response) {
//handle error appropriately: if you want to display a descriptive error notification this is the place
} else {
//handle error appropriately: if you want to display a a generic error message
}
throw error;
});
}
function createUrl(controller: string, action: string = '', urlParams: string[] = [], queryParams: any = null) {
let url = controller + (action ? '/' + action : '');
urlParams.forEach(param => {
url += '/' + param;
});
let params = '';
if (queryParams) {
params += '?' + queryString.stringify(queryParams);
}
return url += params;
}
function createRequestOptions(url: string, method: Method, data: any, responseType?: any) {
var authToken = storageService.getAuthToken();
var jwtToken = authToken != null ? authToken.authToken : '';
var options: AxiosRequestConfig = {
url,
method,
data,
headers: {
'Authorization': 'bearer ' + jwtToken
},
}
if (responseType) {
options.responseType = responseType;
}
return options;
}
let isRefreshing = false;
let failedQueue: any[] = [];
const processQueue = (error: any, token: string = '') => {
failedQueue.forEach(prom => {
if (error) {
prom.reject(error);
} else {
prom.resolve(token);
}
});
failedQueue = [];
}
instance.interceptors.response.use(undefined, (error) => {
const originalRequest = error.config;
if (originalRequest && error.response && error.response.status === 401 && !originalRequest._retry) {
if (isRefreshing) {
return new Promise(function (resolve, reject) {
failedQueue.push({ resolve, reject })
}).then(authToken => {
originalRequest.headers.Authorization = 'bearer ' + authToken;
return axios(originalRequest);
}).catch(err => {
return err;
})
}
originalRequest._retry = true;
isRefreshing = true;
return new Promise(function (resolve, reject) {
accountService.refreshToken()
.then(result => {
if (result.succeeded) {
originalRequest.headers.Authorization = 'bearer ' + result.authToken;
axios(originalRequest).then(resolve, reject);
processQueue(null, result.authToken);
} else {
reject(error);
}
}).catch((err) => {
processQueue(err);
reject(err);
}).then(() => { isRefreshing = false });
});
}
return Promise.reject(error);
});
Cheers,
I have a class like the following:
import axios from "axios";
//axios.defaults.headers["Email"] = sessionStorage.getItem('email');
//axios.defaults.headers["Access-Token"] = sessionStorage.getItem('access_token');
class Api {
// users
static getUsers(params={}) { return axios.get("users", {params}) }
static getUser(userId) { return axios.get("users/" + userId) }
static updateUser(userId, data) { return axios.put("users/" + userId, data) }
// other resources...
}
By reading Axios documentation I see that I can set the header for each request by doing something as follows:
axios.defaults.headers["Email"] = sessionStorage.getItem('email');
axios.defaults.headers["Access-Token"] = sessionStorage.getItem('access_token');
If I insert these 2 lines outside the class at the beggining of the file, I do get an error because sessionStorage hasn't been set yet. After a refresh though sessionStorage gets set so everything works fine.
How could I solve this issue? I would like to keep the axios calls as short as they are right now.
Since you are using OOP I will suggest to use a constructor and some dependency injection to make your life easier.
Use a constructor like this:
constructor(email, accessToken) {
axios.defaults.headers["Email"] = email
// same thing for accessToken
}
If you are using axios in some other places that share the same headers I would suggest initialize axios with the app, some "main" kind of file, set the defaults values and instead of passing email and accessToken just pass the axios instance like:
constructor(axios) {
this.axios = axios // without importing axios
}
// and use like that
this.axios.get("users", {params})
Other suggestions would be pass axios instance directly to your methods like:
static getUsers(axios, params={}) { return axios.get("users", {params}) }
I would highly suggest to avoid to depend on sessionStorage on the class, since you don't know when that would load or be executed. It's just better to have that explicitly telling where it's coming from. Also would be easier to test.
try with this:
axios.defaults.headers.common["token"] = action.payload.data.token;
For this you can use interceptors in the axios. You can intercept requests or responses before they are handled by then or catch.
axios.interceptors.request.use(function (config) {
// Do something before request is sent
axios.defaults.headers["Email"] = sessionStorage.getItem('email');
axios.defaults.headers["Access-Token"] = token;
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
for more details : https://github.com/axios/axios
you could set the headers each time you call axios (even if the calls are a bit longer :
import axios from "axios";
class Api {
// users
static getUsers(params={}) {
params.headers = params.headers || {}
params.headers["Email"] = sessionStorage.getItem('email');
params.headers["Access-Token"] = sessionStorage.getItem('access_token');
return axios.get("users", {params})
}
static getUser(userId, params={}) {
params.headers = params.headers || {}
params.headers["Email"] = sessionStorage.getItem('email');
params.headers["Access-Token"] = sessionStorage.getItem('access_token');
return axios.get("users/" + userId, params)
}
static updateUser(userId, data, params={}) {
params.headers = params.headers || {}
params.headers["Email"] = sessionStorage.getItem('email');
params.headers["Access-Token"] = sessionStorage.getItem('access_token');
return axios.put("users/" + userId, data, params)
}
// other resources...
}
Not the shortest, but I think it could do the job.
I hope it helps.
Maxime
Using the code snippet below I'm authenticating email, password. The customerlogin() method returns some JSON data which I want to show in the next page. In other words I want to pass the data returned from customerlogin() to then() and then pass it to /customerprofile
Please help
login(form) {
this.submitted = true;
if (form.$valid) {
this.Auth.customerlogin({
email: this.operator.email,
password: this.operator.password
})
.then(() => {
// Logged in, redirect to home
this.$location.path('/customerprofile');
})
.catch(err => {
this.errors.login = err.message;
});
}
}
//Other file Auth.js
customerlogin({
email,
password
}, callback) {
console.log('Customer Authentice Method');
return $http.post(properties.customer_login, {
email, password
})
.then(res => {
properties.GetId = res.data.id;
$cookies.put('token', res.data.token);
currentUser = User.get();
return currentUser.$promise;
})
.then(user => {
safeCb(callback)(null, user);
return user;
})
.catch(err => {
Auth.logout();
safeCb(callback)(err.data);
return $q.reject(err.data);
});
}
i want show data these textbox
enter image description here
Your login function should be calling a service method which makes the ajax call and stores the response as an object property on that service. The controller then has that on scope because you've injected the service. There's nothing to pass. It's already there and is watched automatically by Angular.
Something like this:
angular.someModule('someModule')
.service('someService', function($http) {
return {
loginCall: function(...) {
// do ajax call here
return loginStuff; // must be an object (or wrapped in one)
}
};
})
.controller('SomeController', ['someService', function(someService) {
var sc = this; // controllerAs syntax
sc.login = function(form) {
someService.customerlogin(...).then(...).catch(...);
// because someService has been injected, someService.loginCall is now
// available and being watched by the controller, on its scope...
// and can be used in your view like {{someService.loginCall.dataProperty}}
...
};
}]);
There are probably some missing pieces here (module injections), but this should get you started.
At first, try to use this construction for your .then:
.then(function (data) {
$log.debug(data); //to console log passed data via angular
});