Spring MVC + AngularJS + JWT Token Expiration - HowTo - javascript

I would like to ensure that my JSON Web tokens are revoked/expire after a configurable ammount of time and i have the following set up:
Security Filter:
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import yourwebproject2.service.UserService;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
/**
* #author: kameshr
*/
public class JWTTokenAuthFilter extends OncePerRequestFilter {
private static List<Pattern> AUTH_ROUTES = new ArrayList<>();
private static List<String> NO_AUTH_ROUTES = new ArrayList<>();
public static final String JWT_KEY = "JWT-TOKEN-SECRET";
static {
AUTH_ROUTES.add(Pattern.compile("/api/*"));
NO_AUTH_ROUTES.add("/api/user/authenticate");
NO_AUTH_ROUTES.add("/api/user/register");
}
private Logger LOG = LoggerFactory.getLogger(JWTTokenAuthFilter.class);
#Autowired
private UserService userService;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String authorizationHeader = request.getHeader("authorization");
String authenticationHeader = request.getHeader("authentication");
String route = request.getRequestURI();
// no auth route matching
boolean needsAuthentication = false;
for (Pattern p : AUTH_ROUTES) {
if (p.matcher(route).matches()) {
needsAuthentication = true;
break;
}
}
if(route.startsWith("/api/")) {
needsAuthentication = true;
}
if (NO_AUTH_ROUTES.contains(route)) {
needsAuthentication = false;
}
// Checking whether the current route needs to be authenticated
if (needsAuthentication) {
// Check for authorization header presence
String authHeader = null;
if (authorizationHeader == null || authorizationHeader.equalsIgnoreCase("")) {
if (authenticationHeader == null || authenticationHeader.equalsIgnoreCase("")) {
authHeader = null;
} else {
authHeader = authenticationHeader;
LOG.info("authentication: " + authenticationHeader);
}
} else {
authHeader = authorizationHeader;
LOG.info("authorization: " + authorizationHeader);
}
if (StringUtils.isBlank(authHeader) || !authHeader.startsWith("Bearer ")) {
throw new AuthCredentialsMissingException("Missing or invalid Authorization header.");
}
final String token = authHeader.substring(7); // The part after "Bearer "
try {
final Claims claims = Jwts.parser().setSigningKey(JWT_KEY)
.parseClaimsJws(token).getBody();
request.setAttribute("claims", claims);
// Now since the authentication process if finished
// move the request forward
filterChain.doFilter(request, response);
} catch (final Exception e) {
throw new AuthenticationFailedException("Invalid token. Cause:"+e.getMessage());
}
} else {
filterChain.doFilter(request, response);
}
}
}
Method that creates the Authentication token:
String token = Jwts.builder().setSubject(user.getEmail())
.claim("role", user.getRole().name()).setIssuedAt(new Date())
.signWith(SignatureAlgorithm.HS256, JWTTokenAuthFilter.JWT_KEY).compact();
authResp.put("token", token);
authResp.put("user", user);
Above i have all the claims that i am using on the JWT , i would like to request that the token is revoked after x ammount of time(of inactivity if possible).
How could i achieve this using JWT / Spring MVC / Angular JS / Spring Security

Set expiration for token
String token = Jwts.builder()
.setSubject(user.getEmail())
.claim("role", user.getRole().name())
.setIssuedAt(new Date())
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS256, WTTokenAuthFilter.JWT_KEY)
.compact();
Then, parseClaimsJws will raise ExpiredJwtException if currentTime>expirationDate
To revoke a valid token is a hard technique with no easy solutions:
1) Maintain a blacklist in server and compare for each request
2) Set a small expiration time and issue new token
3) Insert the login time in the token and compare if acomplish your criteria
4) remove the jwt in client side
Ser Invalidating client side JWT session

Related

Moving Basic Authentication project to Oauth

Since Microsoft disabled Basic authentication, I need to change this project for using OAuth and I cannot get it to work. Any help would be greatly appreciated.
old code:
// expose our config directly to our application using module.exports
module.exports = {
// this user MUST have full access to all the room accounts
'exchange' : {
'username' : process.env.USERNAME || 'SVCACCT_EMAIL#DOMAIN.COM',
'password' : process.env.PASSWORD || 'PASSWORD',
'uri' : 'https://outlook.office365.com/EWS/Exchange.asmx'
},
// Ex: CONTOSO.COM, Contoso.com, Contoso.co.uk, etc.
'domain' : process.env.DOMAIN || 'DOMAIN.COM'
};
module.exports = function (callback) {
// modules -------------------------------------------------------------------
var ews = require("ews-javascript-api");
var auth = require("../../config/auth.js");
// ews -----------------------------------------------------------------------
var exch = new ews.ExchangeService(ews.ExchangeVersion.Exchange2016);
exch.Credentials = new ews.ExchangeCredentials(auth.exchange.username, auth.exchange.password);
exch.Url = new ews.Uri(auth.exchange.uri);
// get roomlists from EWS and return sorted array of room list names
exch.GetRoomLists().then((lists) => {
var roomLists = [];
lists.items.forEach(function (item, i, array) {
roomLists.push(item.Name);
});
callback(null, roomLists.sort());
}, (err) => {
callback(err, null);
});
};
I recently went into the exactly situation and finally got it working after spending countless hours. Hope this post will help some lost souls like I was.
So, what happened? Basic auth to MS Exchange Online will be disabled by end of 2022. All relevant applications' authentication will require updates.
reference: https://techcommunity.microsoft.com/t5/exchange-team-blog/basic-authentication-deprecation-in-exchange-online-september/ba-p/3609437
How to do it? My use case is a simple one. A mail daemon application 1) logins and 2) download some email attachments. What happens in the background and what do you need to do are written in below article.
reference: https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth
In summary, you need to follow below steps:
Register your application with Azure Active Directory
Get an access token
Add required permissions to your app. If you are using plain password for your old project, you may refer to Use client credentials grant flow to authenticate IMAP and POP connections section in the above article. Below are a list of permissions required for my simple app. I added permission regarding email sending for future use:
Microsoft Graph:
IMAP.AccessAsUser.All
offline_access
openid
POP.AccessAsUser.All
profile
SMTP.Send
Office 365 Exchange Online:
full_access_as_app
IMAP.AccessAsApp
Mail.Read
Mail.ReadWrite
Mail.Send
Get tenant admin consent to your app (done by your Azure admin).
Register service principals in Exchange (done by your Azure admin).
This blog will talk you through above procedures:
https://blog.hametbenoit.info/2022/07/01/exchange-online-use-oauth-to-authenticate-when-using-imap-pop-or-smtp-protocol/#.Y6RdVXZBxm7
Authentication failed? you may be able to retrieve a token from Exchange server, but got an error message:"A1 NO AUTHENTICATE failed" when trying to connect to Exchange server. If you took above steps one by one, it was most likely to be a permission related issue, please refer to the list in step 3. Unfortunately, this is where took me longest to test and Exchange server does not provide more information than "you are screwed", which is a pity.
Last but not the least... Here is my sample Java code. The simple app authenticate with Exchange server using IMAP. Apache HttpCore and Jackson libraries are required.
1 - Class for access token generation:
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
public class OAuthDL {
public String getAuthToken() {
String token = "";
HttpClient httpClient = HttpClients.createDefault();
String tenant_id = "from Azure portal";
String client_id = "from Azure portal";
String client_pw = "created after app was registered";
String scope = "https://outlook.office365.com/.default";
HttpPost httpPost = new HttpPost("https://login.microsoftonline.com/" + tenant_id + "/oauth2/v2.0/token");
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("grant_type", "client_credentials"));
params.add(new BasicNameValuePair("client_id", client_id));
params.add(new BasicNameValuePair("client_secret", client_pw));
params.add(new BasicNameValuePair("scope", scope));;
try {
httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
try {
HttpResponse response = httpClient.execute(httpPost);
HttpEntity respEntity = response.getEntity();
if (respEntity != null) {
String content = EntityUtils.toString(respEntity);
ObjectNode node = new ObjectMapper().readValue(content, ObjectNode.class);
if (node.has("access_token")) {
token = node.get("access_token").asText();
}
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return(token);
}
public static void main(String[] args) {
OAuthDL oa = new OAuthDL();
String token = oa.getAuthToken();
System.out.println("Token: " + token);
}
}
Class that configures protocols and authenticate with Exchange server. JavaMail is required:
import java.util.Properties;
import javax.mail.Folder;
import javax.mail.Session;
import javax.mail.Store;
public class ImapMailBoxReader {
private String host;
private String username;
private String password;
public ImapMailBoxReader(String host, String username, String password) {
this.host = host;
this.username = username;
this.password = password;
}
public void testConnection(String folder) {
try {
String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory";
Properties props= new Properties();
props.put("mail.imaps.ssl.enable", "true");
props.put("mail.imaps.sasl.enable", "true");
props.put("mail.imaps.port", "993");
props.put("mail.imaps.auth.mechanisms", "XOAUTH2");
props.put("mail.imaps.sasl.mechanisms", "XOAUTH2");
props.put("mail.imaps.auth.login.disable", "true");
props.put("mail.imaps.auth.plain.disable", "true");
props.setProperty("mail.imaps.socketFactory.class", SSL_FACTORY);
props.setProperty("mail.imaps.socketFactory.fallback", "false");
props.setProperty("mail.imaps.socketFactory.port", "993");
props.setProperty("mail.imaps.starttls.enable", "true");
props.put("mail.debug", "true");
props.put("mail.debug.auth", "true");
Session session = Session.getDefaultInstance(props, null);
session.setDebug(true);
Store store = session.getStore("imaps");
store.connect(host, username, password);
Folder inbox = store.getFolder(folder);
inbox.open(Folder.READ_ONLY);
inbox.close(false);
store.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
String host = "outlook.office365.com";
String username = "your email address";
OAuthDL oa = new OAuthDL();
String password = oa.getAuthToken();
ImapMailBoxReader reader = new ImapMailBoxReader(host, username, password);
reader.testConnection("inbox");
}
}
Hope this helps.

How to handle the Internal server error? Nestjs

How to handle the error so that if the user does not provide a token, then an UnauthorizedException is thrown.
At the moment I am getting this error:
{
"statusCode": 500,
"message": "Internal server error"
}
ts:
canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();
try {
const jwt = request.headers.authorization.split(' ')[1];
if (!jwt) {
throw new UnauthorizedException('Token is not provided.');
}
return this.jwtService.verify(jwt);
} catch (e) {
return false;
}
}
You can try to recreate auth module from the documentation.
Or try to console.log() on each line.
By default, JWT internal module works well. It can encode and decode all that you need automatically.
https://docs.nestjs.com/security/authentication
I use a middleware for this purpose. I'll share a basic version of it.
auth-middleware.ts
import {HttpStatus,Injectable,Logger,LoggerService,NestMiddleware,} from '#nestjs/common';
import { NextFunction } from 'express';
import { Request, Response } from 'express';
#Injectable()
export class AuthMiddleware implements NestMiddleware {
constructor(
private readonly authenticationService: AuthService,
// (I use Firebase auth. You can inject JWT service here instead)
private readonly logger: LoggerService, // Good'ol logger
) {}
public async use(req: Request, res: Response, next: NextFunction) {
// Checks if req has authorization header
const header = req.headers['authorization'];
if (!header) {
// If no headers are present, returns a 401.
// I use problem+json
// Thats why you are seeing more fields in the response instead of just a
// code and message
return res
.status(HttpStatus.UNAUTHORIZED)
.json({
title: 'Unauthorized',
detail: 'Invalid Token',
type: 'https://app-site.com/login',
status: HttpStatus.UNAUTHORIZED,
instance: 'login/null',
})
.setHeader('Content-Type', 'application/problem+json');
}
// Splitting "Bearer token" to ["Bearer","token"]
const token = header.split(' ')[1];
// Validating token with auth service
// It returns a "tuple" for me...you can have it return whatever you want
const [
authClaims, // Custom claims that is extracted from the JWT
result, // JWT Validation result (boolean)
authProviderUid, // Firebase UID
] = await this.authenticationService.verifyToken(token);
if (
!result || // If JWT is invalid
authClaims.accountStatus === AccountStatusList.Banned ||
authClaims.accountStatus === AccountStatusList.Suspended
) {
// You shall not pass
return res
.status(HttpStatus.UNAUTHORIZED)
.json({
title: 'Unauthorized',
detail: 'Invalid Token',
type: 'https://app-site.com/login',
status: HttpStatus.UNAUTHORIZED,
instance: 'login/null',
})
.setHeader('Content-Type', 'application/problem+json');
}
// Attaches the claims, result and UID with req for the next middleware(s)/controller
req['authResult'] = { authClaims, result, authProviderUid };
//Reassuring
this.logger.log('Token verified', AuthMiddleware.name);
// next function from express
next();
}
}
Next, In the module(s) your controllers are declared,
api.module.ts
import { MiddlewareConsumer, Module, NestModule, RequestMethod, } from '#nestjs/common';
#Module({
imports: [
//...
],
controllers: [
AuthController,
ProfileController,
SubscriptionController
],
providers: [
//...
],
})
export class ApiModule implements NestModule {
public async configure(consumer: MiddlewareConsumer) {
consumer
.apply(AuthMiddleware)
// Exclude some paths
.exclude({ path: '/api/v1/auth/sign-up', method: RequestMethod.POST })
.forRoutes( // Your controller classes you want to run the middleware on
ProfileController,
SubscriptionController,
AuthController
);
}
}
How it works
Every request goes through the specified middleware (if path not excluded). If the request is unauthorized, throws an error before it reaches the controller.
If the request is at the controller, the request is authenticated. You have to take care of the authorization part with guards etc...
Authentication and Authorization are different.
I'd suggest to use middleware for authentication and guards for authorization.
Links :
NestJS Middleware Documentation
Problem Details

How do I bubble up a non asynchronous error

I'm trying to bubble up an error that comes from the verify function in jsonwebtoken, however, it wraps it in another Internal 500 Status error, rather than an Unauthorized error, that I want it to be.
Most of the components are built in loopback-next authentication components. The class below is a provider for a class called AuthenticationStrategy. AuthenticationStrategy is just a class with a passport strategy that returns an Authentication Strategy, which is passed on the metadata of a route. In the class below (the provider of type Authentication strategy, value() is the function returned when calling the provider. The passport strategy verification function has to first be converted to an Authentication strategy first, then returned through the value function to the Provider. When value() is called, the verify function runs on the metadata of the route, in this case, the bearer token. The Function cb below is the same as the 'done' function in a normal passport strategy, and returns (error, object), respectively
import {AuthenticateErrorKeys} from '../error-keys';
import {RevokedTokenRepository, UserRepository} from '../../../repositories';
import {repository} from '#loopback/repository';
import {Strategy} from 'passport-http-bearer'
import {HttpErrors} from '#loopback/rest';
import {StrategyAdapter} from '#loopback/authentication-passport'
import {AuthenticationStrategy} from '#loopback/authentication'
import {inject, Provider} from '#loopback/context'
var verify = require('jsonwebtoken').verify
export const BEARER_AUTH_STRATEGY = 'bearer';
export class PassportBearerAuthProvider implements Provider<AuthenticationStrategy> {
constructor(
#repository(RevokedTokenRepository)
public revokedTokenRepository: RevokedTokenRepository,
#repository(UserRepository)
public userRepository: UserRepository,
){}
value(): AuthenticationStrategy {
const bearerStrategy = new Strategy(this.verify.bind(this));
return this.convertToAuthStrategy(bearerStrategy);
}
async verify (token: string, cb: Function){
try{
if (token && (await this.revokedTokenRepository.get(token))) {
throw new HttpErrors.Unauthorized(AuthenticateErrorKeys.TokenRevoked);
}
const userAuthToken = await verify(token, process.env.JWT_SECRET as string, {
issuer: process.env.JWT_ISSUER,
})
let authUser = await this.userRepository.getAuthUser(userAuthToken.id)
return cb(null, authUser)
}
catch(error) {
if (error.name && error.name === "JsonWebTokenError") {
return cb(new HttpErrors.Unauthorized(AuthenticateErrorKeys.TokenInvalid), null)
}
if (error.name && error.name === "TokenExpiredError") {
return cb(new HttpErrors.Unauthorized(AuthenticateErrorKeys.TokenExpired), null)
}
if (error.code && error.code === "ENTITY_NOT_FOUND") {
return cb(new HttpErrors.Unauthorized(`${AuthenticateErrorKeys.UserDoesNotExist}, id: ${error.entityId}`), null)
}
return cb(error, null)
}
}
// Applies the `StrategyAdapter` to the configured basic strategy instance.
// You'd better define your strategy name as a constant, like
// `const AUTH_STRATEGY_NAME = 'basic'`
// You will need to decorate the APIs later with the same name
convertToAuthStrategy(bearer: Strategy): AuthenticationStrategy {
return new StrategyAdapter(bearer,BEARER_AUTH_STRATEGY);
}
}
The sequence below is run every time someone makes an API request. If above the route, the route is decorated with #authenticate[BEARER_AUTH_TOKEN], the provider above is called, and the verify function is run on the metadata.
export class MySequence implements SequenceHandler {
constructor(
#inject(SequenceActions.FIND_ROUTE) protected findRoute: FindRoute,
#inject(SequenceActions.PARSE_PARAMS) protected parseParams: ParseParams,
#inject(SequenceActions.INVOKE_METHOD) protected invoke: InvokeMethod,
#inject(SequenceActions.SEND) public send: Send,
#inject(SequenceActions.REJECT) public reject: Reject,
#inject(AuthorizationBindings.AUTHORIZE_ACTION)
protected checkAuthorisation: AuthorizeFn,
#inject(AuthenticationBindings.AUTH_ACTION)
protected authenticateRequest: AuthenticateFn,
) {}
async handle(context: RequestContext) {
try {
const {request, response} = context;
const route = this.findRoute(request);
const args = await this.parseParams(request, route)
const authUser = await this.authenticateRequest(request).catch(error => {
Object.assign(error, {statusCode: 401, name: "NotAllowedAccess", message: (error.message && error.message.message)? error.message.message: "Unable to Authenticate User" });
throw error
})
console.log(authUser)
const isAccessAllowed: boolean = await this.checkAuthorisation(
authUser && authUser.permissions,
request,
);
if (!isAccessAllowed) {
throw new HttpErrors.Forbidden(AuthorizeErrorKeys.NotAllowedAccess);
}
const result = await this.invoke(route, args);
this.send(response, result);
} catch (error) {
this.reject(context, error);
}
}
}
But when it catches the error, the status is 500 and the 401 Unauthorized error is wrapped in the Internal Status error. How can I instead return the 401 error?
I have a lot more cases like this where the error is deeper, so I'm trying to have a rigorous implementation.

how can I use refresh token in angular 4

I have completed steps of authorization and obtained the access token and refresh token by Laravel Passport.
My Angular frontend and Laravel backend work fine.
My main questions are:
How and when should I use the refresh token to make new access token?
Should this be done in the background or should the user have to click on a button to refresh the token?
Should the Angular page be reloaded when creating the new token?
I am using JWT authentication in my angular project where the token is set by the API.
The approach I'm taking when the token is expired in explained below -
Expose a new API which will take an expired token and return newly created token.
The API should check the token expiry in every REST API call.
In case, the token is expired, the API should return a status (as per the standards, 498 - expired/invalid).
In angular,create a service layer (token refresher) which delegates every API calls to the server (internally using the http service).
The job of this service is to check the status of API response (if it is 498) and internally make an additional call to refresh the token.
The service can then re-initiate the original call with newly created token to get the response.
All the api services will call the token refresher to get the response.
On a broader level, token refresher is a wrapper over the default http service which performs the additional check.
This will avoid the annoying page loads and make the application faster.
EDIT - Example of HTTP Interceptor
import { Injectable } from "#angular/core";
import { XHRBackend, RequestOptions, Request, RequestOptionsArgs, Response, Http, Headers } from "#angular/http";
import { Store } from "#ngrx/store";
import { Observable } from "rxjs/Rx";
import { Observer } from "rxjs/Observer";
import { Response as ApiResponse } from "../../models/base/response.model";
import { ToastModel } from "../../redux/app-reducers";
import { ReducerActions } from "../../redux/reducer-actions";
#Injectable()
export class HttpInterceptor extends Http {
constructor(private _XHRBackend: XHRBackend,
private _RequestOptions: RequestOptions,
private _ToastStore: Store<ToastModel>,
private _LoaderStore: Store<boolean>) {
super(_XHRBackend, _RequestOptions);
}
public request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
return this.handleResponse(super.request(url, options));
}
public get(url: string, options?: RequestOptionsArgs): Observable<Response> {
this.beforeRequest(url);
return super.get(url, this.getRequestOptionArgs(options));
}
public post(url: string, body: any, options?: RequestOptionsArgs): Observable<Response> {
this.beforeRequest(url, body);
return super.post(url, body, this.getRequestOptionArgs(options));
}
public put(url: string, body: any, options?: RequestOptionsArgs): Observable<Response> {
this.beforeRequest(url, body);
return super.put(url, body, this.getRequestOptionArgs(options));
}
public delete(url: string, options?: RequestOptionsArgs): Observable<Response> {
this.beforeRequest(url);
return super.delete(url, this.getRequestOptionArgs(options));
}
private getRequestOptionArgs(options?: RequestOptionsArgs): RequestOptionsArgs {
if (options == null) {
options = new RequestOptions();
}
if (options.headers == null) {
options.headers = new Headers();
}
options.headers.append('Content-Type', 'application/json');
return options;
}
private handleResponse(response: Observable<Response>): Observable<Response> {
return response
.catch(this.onCatch)
.do(this.onSuccess.bind(this), this.onError.bind(this))
.finally(this.afterResponse.bind(this));
}
private beforeRequest(url: string, body?: string): void {
this._LoaderStore.dispatch({ type: ReducerActions.Loader.Set, payload: true });
}
private afterResponse(): void {
this._LoaderStore.dispatch({ type: ReducerActions.Loader.Set, payload: false });
}
private onCatch(error: any, caught: Observable<Response>): Observable<Response> {
console.log("interceptor catch called");
return Observable.throw(error);
}
private onSuccess(res: Response): void {
let response: ApiResponse<any> = res.json();
if (!response.message) {
return;
}
let toast: ToastModel = {
text: response.message,
duration: 5000,
type: "success"
};
this._ToastStore.dispatch({ type: ReducerActions.Toast.Update, payload: toast });
}
private onError(error: any): void {
let toast: ToastModel = {
text: "Error occurred!",
duration: 5000,
type: "failure"
};
this._ToastStore.dispatch({ type: ReducerActions.Toast.Update, payload: toast });
}
}
In the above example, handleResponse callback is the hook to do anything you want. (In this case, token refresh API call).
I hope this helps. :)

How to Send Data with POST Requests in Ionic 2

I have created an app using Google API and I'm using Google fusion tables for the backend so I have enabled the fusion table API as well. I'm making an hybrid app using ionic 2. GET works perfectly for reading the table and POST given an
error 401
.
function submitAnswer(button) {
var accessToken = document.getElementById("accessToken").value;
var query = "https://www.googleapis.com/fusiontables/v2/query?sql=INSERT INTO " + answerTableId + "(Answers,QuestionId,UserID) VALUES ('" + button.value + "','" + currentQueNo + "','" + userId + "')"+key+"&access_token="+accessToken;
var xhttp2 = new XMLHttpRequest();
xhttp2.onreadystatechange = function() {
//alert(this.readyState + " " + this.status);
if(this.readyState == 4) {
alert(this.responseText);
}
};
xhttp2.open("POST", query, true);
xhttp2.setRequestHeader('Authorization',accessToken);
xhttp2.send();
}
Maybe you've only forgotten "Bearer " in your Authorization value :
xhr.setRequestHeader('Authorization', 'Bearer ' + oauthToken.access_token);
Or maybe you've just badly encoded your accessToken in your query (you need to use encodeURIComponent(accessToken))
If this is called from a browser and not from NodeJS, you may be blocked by CORS issue.
Also, not related to your question : your way of creating the request is very sensible to SQL injection. Some random user could just delete your whole database without knowing any password.
Sincw you are using ionic 2 while don't create like an interceptor that extends the main angular http like this code sample below should do every trick for you and i suggest you adhere to it. Since it is ionic 2 angular 2+
Firstly, create a class to extend the http class like so api-handler.ts:
import { Storage } from '#ionic/storage';
import { environment } from './environment';
import { Injectable } from '#angular/core';
import { Headers, Http, ConnectionBackend, RequestOptions, RequestMethod, RequestOptionsArgs } from '#angular/http';
import 'rxjs/add/operator/map';
import { Observable } from "rxjs/Observable";
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/finally';
import 'rxjs/add/observable/throw';
import 'rxjs/add/observable/fromPromise';
import 'rxjs/add/operator/mergeMap';
/*
Generated class for the ApiHandler provider.
this is used to make communication with our endpoint where we pass endpoint
header information and any form of manipulation
*/
#Injectable()
export class ApiHandler extends Http {
private bearer: string = 'Plutus';
constructor(backend: ConnectionBackend, defaultOptions: RequestOptions, private _storage: Storage) {
super(backend, defaultOptions);
}
/**
* This is used to call our api service by inputing the service url
* #param service_url
* #param method
* #param params
* #param options
*
* #return Observable<any>
*/
callService(service_url: string, method: RequestMethod, params?: any, options?: RequestOptionsArgs): Observable<any> {
if (params == null) {
params = {};
}
options = this.requestOptions(method, params, options);
let token_promise: Promise<any> = this._storage.get('token');
return Observable.fromPromise(token_promise)
.mergeMap(token => {
console.log("token from storage", token);
if (options.headers == null && token != null) {
options.headers = new Headers({
'Authorization': `${this.bearer} ${token}`
});
}
return super.request(this.getFullUrl(service_url), options)
.catch(this.onCatch);
});
}
/**
* Request options is used to manipulate and handle needed information before
* it is sent to server
* #param options
* #returns {RequestOptionsArgs}
*/
private requestOptions(method: RequestMethod, params: any, options?: RequestOptionsArgs): RequestOptionsArgs {
if (options == null) {
options = new RequestOptions();
}
options.method = method;
if (options.method === RequestMethod.Post || options.method === RequestMethod.Put) {
options.body = params;
} else {
options.params = params;
}
return options;
}
/**
* Build API url.
* and we remove any leading / from the service calls since
* we are not needing then in making request calls
* e.g localhost:1337//base... to localhost:1337/base..
* #param url
* #returns {string}
*/
private getFullUrl(url: string): string {
if (url.charAt(0) == "/") {
url = url.substring(1);
}
return environment.endpoint + url;
}
/**
* Error handler.
* #param error
* #param caught
* #returns {ErrorObservable}
*/
private onCatch(error: any, caught: Observable<any>): Observable<any> {
return Observable.throw(x);
}
}
if you observe the way i added header information on the above code and using the request method which allows for any form of http methods like Request.Get, Request.Post, Request.Put, Request.Delete, etc.
Secondly, in your app.module.ts provide the class as your default http call for any backend communication by adding the below to your providers:[]
{
provide: ApiHandler,
useFactory: (backend: XHRBackend, defaultOptions: RequestOptions, _storage: Storage) => new ApiHandler(backend, defaultOptions, _storage),
deps: [XHRBackend, RequestOptions, Storage]
}
Then finally, to use it in your case just add this to your constructor, then use straight away like this
import { IFeedBack } from './../interfaces/ifeedback';
import { Observable } from 'rxjs/Observable';
import { ApiHandler } from './../util/api-handler';
import { Injectable } from '#angular/core';
import { RequestMethod } from '#angular/http';
import 'rxjs/add/operator/map';
/*
Generated class for the FeedBackServiceProvider provider.
See https://angular.io/docs/ts/latest/guide/dependency-injection.html
for more info on providers and Angular 2 DI.
*/
#Injectable()
export class FeedBackService {
constructor(private _apiHandler: ApiHandler) {
}
/**
* this is used to create new feedback
* #param feedback
*/
create(feedback: IFeedBack): Observable<IFeedBack> {
return this._apiHandler.callService('/feedback', RequestMethod.Post, feedback)
.map(res => <IFeedBack>res.json());
}
}
then you can call the create with the new param to send and then subscribe to it.
Think this should serve you better.

Categories

Resources