IONIC 3 : Publish event from handle error function in rest provider - events

I hope we could help me. I'am a beginner with ionic framework.
I have an app with a restfull api server side. I make a rest provider client to interact with this api :
In this provider i have a function to handle error in each request. When the status code is 401 i would like to publish an event "session:expired" to catch it in my app.component.ts to redirect user to login page.
For example a sample of my code :
#Injectable()
export class RestProvider {
.......
myApiUrl = https://......
constructor(public http: HttpClient,
public loadingCtrl: LoadingController,
public loader: LoaderProvider,
public events: Events) {
}
/***
* API REST FOR USER WEBSERVICE
*/
getUsers(params=null): Observable<Student[]> {
return this.http.get(this.myApiUrl + "/users", { params: params, headers:this.customHeader })
.map(this.extractData)
.catch(this.handleError);
}
...........
private extractData(res: Response) {
console.log(res);
let body = res;
return body || { };
}
private handleError (error: Response | any) {
...
if(error.status == 401)
{
console.log("Status Error 401 : " + error.status);
this.events.publish('session:expired');
}
return ......
}
My problem : the "this.event.publish" in the handleError(or extractData) function doesn't work and break/freeze my App with no error in console ??
if i publish an event in the getUsers function that's work(but no interest) :
getUsers(params=null): Observable<Student[]> {
this.events.publish('session:expired');
return this.http.get(this.myApiUrl + "/users", { params: params, headers:this.customHeader })
.map(this.extractData)
.catch(this.handleError);
}
Is it possible to publish event in map or catch function of http object ? maybe i have not the "good practice" ?
Thanks you in advance and sorry for my english :/

Related

Logging in and calling normal API Routes does not work with websockets

I have an issue with the authentication process of a websocket route.
I'm using PassportJS with 'local' strategy.
Logging in and calling normal API Routes are perfectly working with #AuthenticatedGuard,
but not on websockets
On the Websocket Route, it is throwing following error:
ERROR [WsExceptionsHandler] request.isAuthenticated is not a function
TypeError: request.isAuthenticated is not a function
This is my setup:
Example normal working API Route
test.controller.ts
#UseGuards(AuthenticatedGuard)
#Get(':id')
async findOne(
#Param('id') id: string,
#Req() req: any,
#Res() res: Response,
) {
return res.json(
await this.testService.findOne(+id),
);
}
Websocket Route
messages.gateway.ts
#UseGuards(AuthenticatedGuard)
#SubscribeMessage('findAllMessages')
async findAll(
#ConnectedSocket() client: Socket,
#MessageBody() findAllMessages: findAllMessages,
#Req() req: any,
) {
console.log(req);
//return this.messagesService.findAll(chatroomid);
const user = await this.messagesService.checkCurrentSessionString(
req.handshake.headers.cookie,
);
if (req.user.userid != undefined) {
return await this.messagesService.findAll(findAllMessages, user.userid);
} else client.disconnect(true);
throw new UnauthorizedException({
statusCode: 401,
message: 'Bitte logge dich erst ein',
});
}
authenticated.guard.ts
#Injectable()
export class AuthenticatedGuard implements CanActivate {
async canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();
return request.isAuthenticated();
}
}
Seems like the session is not valid for the websocket routes?

AJAX call to an Action with Authorize attribute in .NET Core 3.1

On my pet project (a lyrics website), I wish to add "like" functionality, like this:
Code is open source (here's my current branch). A click on the heart icon should add a like to the databse for the logged in user, and if the user isn't logged in, it should redirect to the login page (IdentityServer 4, separate project and domain).
Controller Action:
[Authorize]
[Route("lyrics/like/{lyricId}")]
public async Task<IActionResult> Like(
int lyricId)
{
try
{
string userId = User.GetUserId().ToString();
await _lyricsService.LikeLyricAsync(userId, lyricId);
return RedirectToAction("Index", "Home");
}
catch
{
return RedirectToAction("Index", "Home");
}
}
JavaScript on the View:
<script>
docReady(function () {
let likeBtn = document.getElementById('like-btn');
let likeLyric = (event) => {
event.preventDefault();
console.log('attemping to like a lyric!');
// 1. create a new XMLHttpRequest object
let request = new XMLHttpRequest();
// 2. configure the request
request.open('GET', 'https://localhost:5001/lyrics/like/#Model.Id');
// 3. send the request over the network
request.send();
// 4. this will be called after the response is received
request.onload = function () {
if (request.status != 200) {
// analyse http status of the response
alert(`Error ${request.status}: ${request.statusText}`);
} else {
// show the result
alert(`Done, got ${request.response.length} bytes`); // response is the server response
}
};
request.onprogress = function (event) {
if (event.lengthComputable) {
alert(`Received ${event.loaded} of ${event.total} bytes`);
} else {
alert(`Received ${event.loaded} bytes`); // no Content-Length
}
};
request.onerror = function () {
alert("Request failed");
};
}
likeBtn.addEventListener('click', likeLyric);
});
</script>
I tried to expand on the request.onload function by adding an:
else if (request.status === 302) {
window.location = request.response;
}
But it doesn't seem to get to that, the .send() fails. What am I doing wrong here?
Here's a screen grab of what is happening:
The error is:
attemping to like a lyric!
govenda-sera:1 Access to XMLHttpRequest at 'https://localhost:5006/connect/authorize?client_id=bejebeje-mvc-local&redirect_uri=https%3A%2F%2Flocalhost%3A5001%2Fsignin-oidc&response_type=code&scope=openid&code_challenge=2mUDM3-gR1jhn7E2EY7T17FkPTHikE8v-KQOBMskazM&code_challenge_method=S256&response_mode=form_post&nonce=637437449511000684.OWQ3MTM4MjItOTJhOS00YjgzLTk1OTYtYWE2ZGUyMzRlYzUyOWE1MTkwNjgtNzI2YS00OWJjLTgzYjAtOTY1MDQ1ZDU3YzE1&state=CfDJ8DxKnFiqfK1HscY3j3s4hc-YvLoUa_X_46X1CclU7U-RahgrNQULQOLJu6943zTWCYa5Q5acO7g7vx03ddXSOOKkUtxZQAMHSgnQHFzBvhXnoC2i6yS0PpGxns7oA7tuvcgnp-jxub7RePZl5QAe5BwfXWkyHtMkFAmTkuultwz5w-Duenyb4KNrZRk1RLn6TLL93BS6YfIfoozorOnvKel4cFFjxIc7F_QXgVFKZm6ud5lN2nItw5WhkDfU6qMHhUUSQXQRJqWSit4CW_1hPpbHZhJmatXWxD8mLVFcSEKMNQz2UIU00RDxBCQW09Skuy3Uoz50Vwp4dEYPtNIcolIKrLn1pJguNsYRWBw391uWO7rMy9W5DPJV44fMVe8UR5xKNUarkelFX4CzHidF-rE&x-client-SKU=ID_NETSTANDARD2_0&x-client-ver=5.5.0.0' (redirected from 'https://localhost:5001/lyrics/like/938') from origin 'https://localhost:5001' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
govenda-sera:131 GET https://localhost:5006/connect/authorize?client_id=bejebeje-mvc-local&redirect_uri=https%3A%2F%2Flocalhost%3A5001%2Fsignin-oidc&response_type=code&scope=openid&code_challenge=2mUDM3-gR1jhn7E2EY7T17FkPTHikE8v-KQOBMskazM&code_challenge_method=S256&response_mode=form_post&nonce=637437449511000684.OWQ3MTM4MjItOTJhOS00YjgzLTk1OTYtYWE2ZGUyMzRlYzUyOWE1MTkwNjgtNzI2YS00OWJjLTgzYjAtOTY1MDQ1ZDU3YzE1&state=CfDJ8DxKnFiqfK1HscY3j3s4hc-YvLoUa_X_46X1CclU7U-RahgrNQULQOLJu6943zTWCYa5Q5acO7g7vx03ddXSOOKkUtxZQAMHSgnQHFzBvhXnoC2i6yS0PpGxns7oA7tuvcgnp-jxub7RePZl5QAe5BwfXWkyHtMkFAmTkuultwz5w-Duenyb4KNrZRk1RLn6TLL93BS6YfIfoozorOnvKel4cFFjxIc7F_QXgVFKZm6ud5lN2nItw5WhkDfU6qMHhUUSQXQRJqWSit4CW_1hPpbHZhJmatXWxD8mLVFcSEKMNQz2UIU00RDxBCQW09Skuy3Uoz50Vwp4dEYPtNIcolIKrLn1pJguNsYRWBw391uWO7rMy9W5DPJV44fMVe8UR5xKNUarkelFX4CzHidF-rE&x-client-SKU=ID_NETSTANDARD2_0&x-client-ver=5.5.0.0 net::ERR_FAILED
likeLyric
You can't do an AJAX call to this URL to login the user:
https://localhost:5006/connect/authorize?....
If you want the user to login/authenticate, then you need to redirect the browser to that page.
Or better, don't show the heart icon if the user is not logged in, better to have a login to like button? The user might otherwise be surprised why he needs to login.
It is caused by cors, you need to enable cors in backend.
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy(name: "AllowOrigins",
builder =>
{
builder.WithOrigins("http://example.com",
"http://www.contoso.com");
});
});
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//...
app.UseRouting();
app.UseCors("AllowOrigins");
//...
}
}
In addition, can you switch to another browser to access correctly?

Request failing error message when passing data from react application to web service

I have a javascript object and when I stringify It, I get the data below which
is called dataToJson
0: {invoiceId: 45233265}
1: {invoiceId: 89238545}
2: {invoiceId: 65235465}
Here is my endpoint written with spring.io which should accept the json data
above. I decided not to include all the code in the method because it works.
All I need is how to pass the data.
#ApiOperation(
value = "Returns a pageable list of data.",
notes = "Must be authenticated.")
#EmptyNotFound
#GetMapping({
"customers/{customerId}/getProductsForInvoices/{invoiceIds}"
})
public ArrayList<Page<CustomerInvoiceProduct>> getProductsForInvoices(
#PathVariable(required = false) Long customerId,
#RequestBody List<CustomerInvoice> invoiceIds,
Pageable pageInfo) {
......
......
return result;
}
Here is how I am using Axios to make the web service call and pass in the data.
AXIOS_AUTHED.get(`${API}/customers/${getCustomerId}/getProductsForInvoices/invoiceIds`, {
params:dataToJson
})
.then(function (response) {
console.log(response);
console.log(dataToJson);
})
.catch(function (error) {
console.log(error);
console.log(dataToJson);
});
All I get is "request failed with status code 400"
When I pass data like this from postman, it works very well.
[
{
"invoiceId":"45233265"
},
{
"invoiceId":"89238545"
},
{
"invoiceId":"65235465"
}
]

How to handle an unauthorized ajax call

I am trying to figure out how to prevent a cors error from showing up in developer tools. The way I get the cors error is when I am using an application but in another tab/window I log out of that application but then go back to the other tab and try to do work. Below is my ajax call.
function RemoveScholarshipRequest(id, name) {
if (confirm("Are you sure you want to delete the scholarship request for " + name + "?")) {
var dataSource = $('#Pending').data('kendoGrid').dataSource;
$.ajax({
type: "POST",
url: '#Url.Action("RemoveRequest", "Admin")',
data: {id: id}
}).done(function (response, data, xhr) {
if (response.success) {
dataSource.read();
alert(response.responseText);
}
else if (!response.success) {
if (response.responseText === "Not Authenticated")
alert(response.responseText);
console.log("error", data.status);
//This shows status message eg. Forbidden
console.log("STATUS: "+JSON.stringify(xhr.status));
}
}).fail(function (response) {
console.log(response);
console.log(JSON.stringify(response));
//window.location.href = "/forms/ScholarshipDisbursement/Admin/PendingRequests";
});
}
}
The controller action that the above ajax method calls is below:
[AllowAnonymous]
[HttpPost]
public ActionResult RemoveRequest(string id)
{
if (!User.Identity.IsAuthenticated)
{
return Json(new { success = false, responseText = "Not Authenticated" }, JsonRequestBehavior.AllowGet);
}
if (User.IsInRole("Developer") || User.IsInRole("BannerAdmin"))
{
new ScholarshipRequestStore().DeleteScholarshipRequest(id);
return Json(new { success = true, responseText = "Successfully deleted" }, JsonRequestBehavior.AllowGet);
}
else
{
return Json(new { success = false, responseText = "You are not an authorized user" }, JsonRequestBehavior.AllowGet);
}
}
One way I get around the cors error is by putting AllowAnonymous on the method and then checking for authentication in the method itself but I don't really like that idea. Is there another way of resolving this issue?
Allow anonymous will not solve this, instead you need to send the allow origin header in your api. You can do this by enabling CORs in the startup class as follows
public void ConfigureServices(IServiceCollection services)
{
// Add Cors
services.AddCors(o => o.AddPolicy("MyPolicy", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
}));
// Add framework services.
services.AddMvc();
services.Configure<MvcOptions>(options =>
{
options.Filters.Add(new CorsAuthorizationFilterFactory("MyPolicy"));
});
...
...
...
}
// This method gets called by the runtime. Use this method to configure
//the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
// Enable Cors
app.UseCors("MyPolicy");
//app.UseMvcWithDefaultRoute();
app.UseMvc();
...
...
...
}
and then using the "Enable cors" attribute on your controller
[EnableCors("MyPolicy")]
[AllowAnonymous]
[HttpPost]
public ActionResult RemoveRequest(string id)
read this for better idea https://learn.microsoft.com/en-us/aspnet/core/security/cors?view=aspnetcore-2.2
Note: I have allowed any origin to talk to the API, you can specify whatever origin you want like "https://example.com"
AllowAnonymous won't resolve a "cross-origin" request. The issue you are getting is due to tabbed browsing within your browser having a shared store of authenticated sessions. When you log out in tab 1, the session cookie is removed and then tab 2 is no longer authenticated. This is why AllowAnonymous "works" because without a current authenticated session, you're an anonymous user.
CORS, on the other hand, is when you allow calls to http://myservice.com to come from a different host like http://myclient.com. Anonymous access won't have any impact on that.

Authenticating my Ionic 3 app against Spring Boot REST API

The question must be very typical, but I can't really find a good comparison.
I'm new to Ionic & mobile dev.
We have a REST API (Spring Boot).
API is currently used by AngularJS 1.5 front-end only.
AngularJS app is authenticated based on the standard session-based authentication.
What should I use to authenticate an ionic 3 app?
As I understand, have 2 options:
Use the same auth as for Angular front-end.
implement oauth2 on the back-end and use the token for the ionic app.
As for now, I understand that implementing oauth2 at back-end is a way to go because with the option #1 I should store the username & password in the local storage (ionic app), which is not safe. Otherwise, if I don't do that - the user will have to authenticate each time the app was launched. Am I right?
So, that leaves me with option #2 - store oauth2 token on the device?
Good to go with #2. Here is how i manage token.
I use ionic storage to store token and a provider config.ts which hold the token during run time.
config.ts
import { Injectable } from '#angular/core';
#Injectable()
export class TokenProvider {
public token: any;
public user: any = {};
constructor( ) { }
setAuthData (data) {
this.token = data.token;
this.user = data
}
dropAuthData () {
this.token = null;
this.user = null;
}
}
auth.ts
import { TokenProvider} from '../../providers/config';
constructor(public tokenProvider: TokenProvider) { }
login() {
this.api.authUser(this.login).subscribe(data => {
this.shared.Loader.hide();
this.shared.LS.set('user', data);
this.tokenProvider.setAuthData(data);
this.navCtrl.setRoot(TabsPage);
}, err => {
console.log(err);
this.submitted = false;
this.shared.Loader.hide();
this.shared.Toast.show('Invalid Username or Password');
this.login.password = null;
});
}
and i do a check when app launch.
app.component.ts (in constructor)
shared.LS.get('user').then((data: any) => {
if (!data) {
this.rootPage = AuthPage;
} else {
tokenProvider.setAuthData(data);
this.rootPage = TabsPage;
}
});
api.provider.ts
updateUser(data): Observable < any > {
let headers = new Headers({
'Content-Type': 'application/json',
'X-AUTH-TOKEN': (this.tokenProvider.token)
});
return this.http.post(`${baseUrl}/updateUser`, JSON.stringify(data), {
headers: headers
})
.map((response: Response) => {
return response.json();
})
.catch(this.handleError);
}
And last logout.ts
logOut(): void {
this.shared.Alert.confirm('Do you want to logout?').then((data) => {
this.shared.LS.remove('user').then(() => {
this.tokenProvider.dropAuthData();
this.app.getRootNav().setRoot(AuthPage);
}, () => {
this.shared.Toast.show('Oops! something went wrong.');
});
}, err => {
console.log(err);
})
}
The final solution i've made:
ionic app:
implemented a jwt token storage similar to Swapnil Patwa answer.
Spring back-end:
Tried to use their original ouath2 package, but found out that as always with spring/java, configs are too time-consuming => made a simple filter which is checking for the manually generated & assigned jwt token.

Resources