Axios Interceptor is not working after page refresh (Vue.js) - vue.js

I am working on a simple crud-application, which is developed with spring,vue.js and h2 database.
I am almost done but unfortunately, I have some problems with the authentication. When I type all required credentials, the login will succeed and I will be redirected to page with a meal table, which displays the data from an API.
When I click on other pages, suddenly the console shows me an error message:
[1]: https://i.stack.imgur.com/zQBw0.png
After a while, I have replaced all "href" to "to". Finally, the navigation through the web application worked and also the access to the data was not denied. Unfortunately, after a page refresh, the access was denied and I received the above error message again.
I have checked the session storage in the Browser if my user account is still saved after a page refresh and this was the case.
I don't know, what the exact error is.
I appreciate your help :)
Backend:
SpringSecurityConfigurationAuthentication.java
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SpringSecurityConfigurationAuthentication extends WebSecurityConfigurerAdapter {
#Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.anyRequest().authenticated()
.and()
//.formLogin().and()
.httpBasic();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.inMemoryAuthentication()
.withUser("admin")
.password("{noop}admin")
.roles("ADMIN")
.and()
.withUser("user")
.password("{noop}user")
.roles("USER");
}
}
AuthenticationBeanController.java
#RestController
#RequestMapping(value="/basicauth")
#CrossOrigin(origins = "*", allowedHeaders = "*")
public class AuthenticationBeanController {
#GetMapping
public AuthenticationBean authenticating() {
return new AuthenticationBean("You are authenticated");
}
}
Frontend Vue.js :
http-common.js:
import axios from 'axios'
export default axios.create({
baseURL: "http://localhost:8080",
headers: {
"Content-type": "application/json",
}
})
AuthenticationService.js
import http from '../http-common'
class AuthenticationService {
registerSuccesfulLogin(username, password) {
this.setupAxiosInterceptors(this.createBasicAuthToken(username, password))
}
startAuthentication(username, password) {
return http.get('/basicauth', {headers: {authorization: this.createBasicAuthToken(username, password)}});
}
createBasicAuthToken(username, password) {
let userToken = 'Basic ' + window.btoa(username + ":" + password);
sessionStorage.setItem('authenticatedUser', username)
return userToken
}
logout() {
sessionStorage.removeItem('authenticatedUser');
}
isUserLoggedIn() {
let user = sessionStorage.getItem('authenticatedUser');
if (user === null) return false
return true
}
setupAxiosInterceptors(userToken) {
http.interceptors.request.use(
(config) => {
if (this.isUserLoggedIn()) {
config.headers.Authorization = userToken
}
return config
}
)
}
}
export default new AuthenticationService();
MealDataService.js
import http from '../http-common'
class MealDataService {
retrieveAllMeals() {
return http.get('/meal');
}
deleteMealById(id) {
return http.delete(`/meal/${id}`);
}
getMealById(id) {
return http.get(`/meal/${id}`);
}
updateMealById(data) {
return http.put('/meal', data);
}
addMealById(data) {
return http.post('/meal', data);
}
}
export default new MealDataService();

Possible lead :
Since your app is a single page client side app, without a proper server configuration, the users will get an error (404 if not found or authentication error if protected) if they access any other route different than home directly in their browser (or by refreshing).
Look at your web server configuration and see if there is any configuration to handle this behavior HTML5 history mode.

Related

Vue app and .NET Core Web Api losing session

I hope this has not already been asked, I can't seem to find what I need. I have a VUE 3 app and am using a .NET Core Web API to retrieve data from a service. In the Vue app I make an axios call to log in the user
await axios({
method: 'post',
url: 'https://localhost:44345/api/Authentication/SignIn',
contentType: "application/json",
params: {
username: signInData.value.username,
password: signInData.value.password,
keepMeSignedIn: signInData.value.keepMeSignedIn
}
}).then(response => {
if (response.data.succeeded) {
console.log("Result: ", response.data.data);
}
else {
emit('handleServerSideValidationErrors', response);
}
This then calls my API where I call the service to sign in the user. Once I have verified the information and have the user data it is getting set in session.
public void Set<T>(string key, T value)
{
if (key.IsNullOrEmpty())
{
throw new Exception("The key parameter for SessionUtil.Set is required. It cannot be null/empty.");
}
else
{
this._validateSessionObjectVersion();
if (value == null)
{
Remove(key);
}
else
{
string json = JsonConvert.SerializeObject(value, Formatting.None, new JsonSerializerSettings() { ReferenceLoopHandling = ReferenceLoopHandling.Ignore });
_httpContextAccessor.HttpContext.Session.SetString(key, json);
}
}
}
The issue I am running into is, when I go to another page that needs to access this session it is null. The API calls this get method but is null.
public T Get<T>(string key)
{
T value = default(T);
if (key.IsNullOrEmpty())
{
return value;
}
if (_httpContextAccessor.HttpContext == null)
{
return value;
}
this._validateSessionObjectVersion();
string json = _httpContextAccessor.HttpContext.Session.GetString(key);
if (!json.IsNullOrEmpty())
{
value = JsonConvert.DeserializeObject<T>(json);
}
return value;
}
My Vue app is running on localhost:5001 while my API is running on localhost:44345. I do have a cors policy already in place which allows me to call the API but I don't see what I need to do in order to not lose session.
Turns out my issue was I had set the cookie option of SameSite to SameSiteMode.Lax. As soon as I changed it to SameSiteMode.None it was working for me.

How to configure axios to request WebAPI with auth?

I have a local ASP.Net FrameWork WebAPI server with the following controller:
public class ValuesController : ApiController
{
// GET api/values
[AuthorizationFilter]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values/5
public string Get(int id)
{
return "value";
}
}
I created an AuthorizationFilter attribute to handle authorization (only for GET with no id action):
public class AuthorizationFilter : AuthorizationFilterAttribute
{
public override void OnAuthorization(HttpActionContext ctx)
{
if(ctx.Request.Headers.Authorization == null)
{
ctx.Response = ctx.Request.CreateResponse(System.Net.HttpStatusCode.Unauthorized);
} else
{
string authenticationString = ctx.Request.Headers.Authorization.Parameter;
string decodedAuthString = Encoding.UTF8.GetString(Convert.FromBase64String(authenticationString));
string username = decodedAuthString.Split(':')[0];
string password = decodedAuthString.Split(':')[1];
// assume that I have checked credentials from DB
if (username=="admin" && password=="root")
{
// authorized...
} else
{
ctx.Response = actionContext.Request.CreateResponse(System.Net.HttpStatusCode.Unauthorized);
}
}
}
}
Also, I modified Web.config to allow CORS:
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*" />
</customHeaders>
</httpProtocol>
...
</system.webServer>
I ran the server and tried to get /api/values/1 from the browser, and it worked.
I then tried to access the action that requires authorization: /api/values :
I used Insomnia to send requests and test CORS. First I tried the action that doesn't require authorization:
Then I tried the action with authorization:
Then I tried the same action but after adding the authentication username and password, and that worked out fine:
After this point, I knew my webapi is configured correctly, so I tried to pull data from a React app using axios:
const api = axios.create({
baseURL: "http://localhost:50511/api",
});
const response = await api.get("/values/1");
console.log(response.data); // works fine: I get "value" as expected
And now, the final step, to configure axios to request the action that requires authentication:
const api2 = axios.create({
baseURL: "http://localhost:50511/api",
auth : {
username: "admin",
password: "root"
}
});
const response = await api2.get("/values"); // causes a network exception
The reported error is strange, since it talks about CORS. I don't get it. If there shall be an error, it can imagine it being an error related to authorization. Not CORS. Not after being able to use axios to pull data from the action that has no authentication filter.
I examined the request header to make sure that it was configured with the correct Authorization parameter:
I also tried to use axios in different ways:
const response1 = await axios.get("http://localhost:50511/api/values",{
auth: {
username: "admin",
password: "root"
}
});
const response2 = await axios.get("http://localhost:50511/api/values",{
headers: {
Authorization: "Basic " + btoa("admin:root"),
}
});
Both of these attempts did not work.
Then I tried again, but this time passing an empty object as the second parameter to the axios call:
const response3 = await axios.get("http://localhost:50511/api/values", {}, {
auth: {
username: "admin",
password: "root"
}
});
const response4 = await axios.get("http://localhost:50511/api/values", {}, {
headers: {
Authorization: "Basic " + btoa("admin:root"),
}
});
Again, none of these attempts worked. What am I don't wrong?

Angular 6 Http client custom url and header

I am working in an Angular 6 application and I was wondering what should be the best practice when customizing the url while sending requests to the server.
Here is the scenario:
- In my Angular project I have the environment.ts and environment.prod.ts where I added a "host" which contains the url:port of the http server (project with the controllers).
- I am creating Services to be injected in my components which will be responsible for sending requests (GETs and POSTs) to the server to retrieve data or to send updates.
- I want to use the "host" from the environment.ts as part of the request url. So ALL my requests will have the "host" as the base url and then i can concatenate to the desired path.
I already checked a few solutions and I already implemented one of them, but I am not sure this is the right practice. I will write below what i implemented so far and then i will write some ideas, please help me understand what is the best solution (I am new at angular)
Currently implemented:
-> In my feature services, like LoginService, I inject the angular HttpClient. Then I simply call:
return this.httpService.post("/login/", creds).pipe(
map((data: any) => {
this.manager = data;
return this.manager;
}));
I created an interceptor to make changes to the url: InterceptService implements HttpInterceptor where I create a new instance of the HttpRequest and customize the request.url using environment.host. I also needed the interceptor to add a Header for the authentication (still not fully implemented)
const httpRequest = new HttpRequest(<any>request.method, environment.host + request.url, request.body);
request = Object.assign(request, httpRequest);
const headers = new HttpHeaders({
'Authorization': 'Bearer token 123',
'Content-Type': 'application/json'
});
Questions:
1) This works, all my requests are changed in the interceptor as I
wanted, but it doesn't look like the best practice in my first look. I
don't like to create a new HeepRequest to be able to do this (i did it
to keep it immutable, I guess that's the correct way). Do you think
this looks good?
2) What about the Authentication being added to the Header in the interceptor? Is it ok? Most of the references I checked did this
Other solutions:
1) I saw some examples where a HttpClientService extends Http and each of the methods such as get and post edit the url and headers before calling super methods. But I believe this is not Angular 6 and is probably not preferrable
2) I could also create a service that receives an angular HttpClient (angular 6 HttpClientModule) instance by injection and I could implement the methods like get or post.
Well, as I didn't get any answers I will add my solution. i believe it's the best solution based on my researches.
I used an interceptor for adding information to the header such as the
token bearer authentication.
import { Injectable } from '#angular/core';
import {
HttpEvent,
HttpInterceptor,
HttpHandler,
HttpRequest,
HttpResponse,
HttpHeaders,
HttpErrorResponse
} from '#angular/common/http'
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { environment } from "../../../environments/environment";
import { Router } from "#angular/router";
export class HttpClientInterceptor implements HttpInterceptor {
constructor(private router: Router) { }
// intercept request to add information to the headers such as the token
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
//I decided to remove this logic from the interceptor to add the host url on the HttpClientService I created
//const httpRequest = new HttpRequest(<any>request.method, environment.host + request.url, request.body);
//request = Object.assign(request, httpRequest);
var token = localStorage.getItem("bearerToken");
if (token) {
const newReq = request.clone(
{
headers: request.headers.set('Authorization',
'Bearer ' + token)
});
return next.handle(newReq).pipe(
tap(event => {
if (event instanceof HttpResponse) {
console.log("Interceptor - HttpResponse = " + event.status); // http response status code
}
}, error => {
// http response status code
if (error instanceof HttpErrorResponse) {
console.log("----response----");
console.error("status code:");
console.error(error.status);
console.error(error.message);
console.log("--- end of response---");
if (error.status === 401 || error.status === 403) //check if the token expired and redirect to login
this.router.navigate(['login']);
}
})
)
}
else {
return next.handle(request);
}
};
For changing the url, I created a service on file
http-client.service.ts and got the host url from environment.ts
import { Injectable } from "#angular/core";
import { HttpClient } from '#angular/common/http';
import { Observable } from "rxjs";
import { environment } from "../../../environments/environment";
#Injectable({ providedIn:'root' })
export class HttpClientService {
constructor(private http: HttpClient) { }
get(url: string, options?: any): Observable<ArrayBuffer> {
url = this.updateUrl(url);
return this.http.get(url, options);
}
post(url: string, body: string, options?: any): Observable<ArrayBuffer> {
url = this.updateUrl(url);
return this.http.post(url, body, options);
}
put(url: string, body: string, options?: any): Observable<ArrayBuffer> {
url = this.updateUrl(url);
return this.http.put(url, body, options);
}
delete(url: string, options?: any): Observable<ArrayBuffer> {
url = this.updateUrl(url);
return this.http.delete(url,options);
}
private updateUrl(req: string) {
return environment.host + req;
}
}
As i said, I believe this is the best approach, but feel free to add information to my question/answer.

Angular with Azure AD B2C Audience Validation Failed

I have an Anuglar5 spa frontend and ASP.NET Core API. Both secured by Azure AD B2C service. The angular application redirects correctly to the login page and signing in returns a token. When I try to call the API with the token I get;
AuthenticationFailed: IDX10214: Audience validation failed. Audiences: '627684f5-5011-475a-9cbd-55fcdcdf369e'. Did not match: validationParameters.ValidAudience: 'ee8b98a0-ae7a-38b2-9e73-d175df22ef4c' or validationParameters.ValidAudiences: 'null'.
"627684f5-5011-475a-9cbd-55fcdcdf369e" is the Application ID of the frontend app. And "ee8b98a0-ae7a-38b2-9e73-d175df22ef4c" is the Application ID of the API.
My code;
`export class MSALService {
private applicationConfig: any = {
clientID: '627684f5-5011-475a-9cbd-55fcdcdf369e',
authority: 'https://login.microsoftonline.com/tfp/mytenant.onmicrosoft.com/B2C_1_my_signin_signup',
b2cScopes: ['https://meeblitenant.onmicrosoft.com/api/myapp_read', 'https://meeblitenant.onmicrosoft.com/api/myapp_write'],
redirectUrl: 'http://localhost:4200/'
};
private app: any;
public user: any;
constructor() {
this.app = new UserAgentApplication(this.applicationConfig.clientID, this.applicationConfig.authority,
(errorDesc, token, error, tokenType) => {
console.log(token);
},
{ redirectUri: this.applicationConfig.redirectUrl }
);
}
public login() {
let tokenData = '';
this.app.loginRedirect(this.applicationConfig.b2cScopes).then(data => { tokenData = data; });
}
public getUser() {
const user = this.app.getUser();
if (user) {
return user;
} else {
return null;
}
}
public logout() {
this.app.logout();
}
public getToken() {
return this.app.acquireTokenSilent(this.applicationConfig.b2cScopes)
.then(accessToken => {
console.log(accessToken);
return accessToken;
}, error => {
return this.app.acquireTokenPopup(this.applicationConfig.b2cScopes)
.then(accessToken => {
return accessToken;
}, err => {
console.error(err);
});
}
);
}
}`
Using the token that is returned in Postman also returns the same error. My theory is that the URL I am using to call Azure AD B2C is the problem but looking through the docs I cannot find the problem.
Any help would be greatly appreciated.
Kinda sounds like you are sending the Id token to the API (which is meant for your front-end) instead of an access token. You can debug the issue further by decoding the token you get at https://jwt.ms.
There the aud (audience) should match your API's id, and the scopes you asked should also be there.

401 error when using [Authenticate] with BasicAuthProvider

I'm having some trouble with authenticating with ServiceStack using the BasicAuthProvider. All works well when I authenticate using the provider route 'auth/myauth' but when I go to one of my other service DTOS that use the [Authenticate] attribute e.g. /hello, I always get a 401 Unauthorized error even when I always supply the basic authentication details in the 'Authorization' header using beforeSend with jQuery.
Basically, I'm building an API for a mobile app that involves credential authentication on the first time(or if a supplied token isn't expired), then subsequently basic authentication of supplied token for other requests. I'm trying to authenticate every request, as described here. Also here. Here's my code:
Custom Provider
public class MyAuthProvider : BasicAuthProvider
{
public new static string Name = "MyAuth";
public new static string Realm = "/auth/myauth";
public MyAuthProvider()
{
this.Provider = Name;
this.AuthRealm = Realm;
}
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
var httpReq = authService.RequestContext.Get<IHttpRequest>();
var basicAuth = httpReq.GetBasicAuthUserAndPassword();
if (basicAuth == null)
throw HttpError.Unauthorized("Invalid BasicAuth credentials");
var us = basicAuth.Value.Key;
var ps = basicAuth.Value.Value;
if (ps == "password")
{
return true;
}
return false;
}
}
Service
public class HelloService : Service
{
//handle OPTIONS in preflight - http://joeriks.com/2013/01/12/cors-basicauth-on-servicestack-with-custom-authentication/
public object Options(Hello request) { return true; }
[Authenticate("MyAuth")]
public object Post(Hello request)
{
return new HelloResponse { Result = "Hello, " + request.Name };
}
[Authenticate("MyAuth")]
public object Get(Hello request)
{
return new HelloResponse { Result = "Hello, " + request.Name };
}
}
Configure Method
public override void Configure(Container container)
{
Plugins.Add(new AuthFeature(() => new AuthUserSession(), new IAuthProvider[] {
new MyAuthProvider()
}));
//register any dependencies your services use, e.g:
container.Register<ICacheClient>(new MemoryCacheClient() { FlushOnDispose = false });
//set endpoint information
SetConfig(new EndpointHostConfig
{
GlobalResponseHeaders =
{
{"Access-Control-Allow-Origin","http://localhost"},
{"Access-Control-Allow-Methods","GET, POST, PUT, DELETE, OPTIONS"},
{"Access-Control-Allow-Headers", "Content-Type, Authorization, Accept, Origin" }
},
});
}
This works
function make_base_auth(user, password) {
var tok = user + ':' + password;
var hash = btoa(tok);
return "Basic " + hash;
}
////
$.ajax({
url: 'http://localhost:61750/auth/myauth?format=json',
type: 'POST',
beforeSend: function(xhr) {
xhr.setRequestHeader("Authorization", make_base_auth("id#email.com","password"));
}
}).done(function (data) {
if( console && console.log ) {
console.log("Sample of data:", data);
}
});
But this doesn't
$.ajax({
url: 'http://localhost:61750/hello?format=json',
data: { Name:"Foo" },
type: 'POST',
beforeSend: function(xhr) {
xhr.setRequestHeader("Authorization", make_base_auth("id#email","password"));
}
}).done(function (data) {
if( console && console.log ) {
console.log("Sample of data:", data);
}
});
Thanks for your help.
I had to create a custom authenticate attribute with guidance from this gist -> https://gist.github.com/joeriks/4518393
In the AuthenticateIfBasicAuth method, I set provider to use MyAuthProvider.Name
Then,
[CustomAuthenticate]
public object Post(Hello request)
{
return new HelloResponse { Result = "Hello, " + request.Name };
}