Angular 2 cookie authentication - ruby-on-rails

I am writing some integration between Angular 2(not a big expert in JS) and Rails 4.2. On Rails side I am using devise for authentication. The thing is when I am requesting to sign in user, Set-Cookie header is returned in response. But then, when I am trying to request any authentication-required resource Cookie header has value of "Set-Cookie=null"(like there is no value of Set-Cookie?). Here's how it's done:
session.service.ts
#Injectable()
export class SessionService{
private headers = new Headers({'Content-Type': 'application/json', 'Accept': 'application/json'})
private createSessionUrl = 'http://localhost:3002/users/sign_in';
constructor(
private http: Http
) { }
create(email: string, password: string): Promise<User>{
return this.http.post(this.createSessionUrl, JSON.stringify({user:{email: email, password: password}}),
{ headers: this.headers })
.toPromise()
.then(response => response.json() as User)
.catch(this.handleError)
}
private handleError(error: any): Promise<any> {
return Promise.reject(error.message || error);
}
}
statistics.service.ts
import { Injectable } from '#angular/core';
import { Headers, Http, RequestOptions } from '#angular/http';
import { Statistics } from '../models/statistics.models'
import 'rxjs/add/operator/toPromise';
#Injectable()
export class StatisticsService{
private headers = new Headers({'Content-Type': 'application/json', 'Accept': 'application/json'})
private statisticsUrl = 'http://localhost:3002/statistics';
constructor(
private http: Http
) {}
show(): Promise<Statistics>{
let options = new RequestOptions({ headers: this.headers, withCredentials: true });
return this.http.get(this.statisticsUrl, options)
.toPromise()
.then(response => response.json() as Statistics)
.catch(this.handleError)
}
private handleError(error: any): Promise<any> {
console.error('An error occurred', error);
return Promise.reject(error.message || error);
}
}
As far as I know, let options = new RequestOptions({ headers: this.headers, withCredentials: true });
should include proper Cookie in request.
If there is any extra info required, please write in comments.

your question is quite old, but I hope it helps anyway. I stumbled about that problem as well and wished to find your question answered. Now I take care of that.
I think you forgot the withCredentials option in the POST request of your session.service.ts. As you send a request with credentials (email and password) and you expect the response to set a cookie, you should tell the http-service about that:
return this.http.post(
this.createSessionUrl,
JSON.stringify({user:{email: email, password: password}}),
{ headers: this.headers, withCredentials: true }
)
Now the cookie should be stored by the browser and the next statistics request should contain the correct cookie.
Does this work?
Cheers,
Friedrich

Related

Axios interceptor changes the POST request's content type on 401 retry

I cannot figure out why Axios is changing my request's content-type on retry.
I am creating an axios instance as follows (notice global default header):
import axios, { type AxiosInstance } from "axios";
const api: AxiosInstance = axios.create({
baseURL: "https://localhost:44316/",
});
export default api;
I import this instance in various components within my vue3 app. When my token has expired and I detect a 401, I use the interceptor to refresh my token and retry the call as follows (using a wait pattern to queue multiple requests and prevent requesting multiple refresh tokens):
axios.interceptors.request.use(
(config) => {
const authStore = useAuthStore();
if (!authStore.loggedIn) {
authStore.setUserFromStorage();
if (!authStore.loggedIn) {
return config;
}
}
if (config?.headers && authStore.user.accessToken) {
config.headers = {
Authorization: `Bearer ${authStore.user.accessToken}`,
};
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
axios.interceptors.response.use(
(res) => {
return res;
},
async (err) => {
if (err.response.status === 401 && !err.config._retry) {
console.log("new token required");
err.config._retry = true;
const authStore = useAuthStore();
if (!authStore.isRefreshing) {
authStore.isRefreshing = true;
return new Promise((resolve, reject) => {
console.log("refreshing token");
axios
.post("auth/refreshToken", {
token: authStore.user?.refreshToken,
})
.then((res) => {
authStore.setUserInfo(res.data as User);
console.log("refresh token received", err.config, res.data);
resolve(axios(err.config));
})
.catch(() => {
console.log("refresh token ERROR");
authStore.logout();
})
.finally(() => {
authStore.isRefreshing = false;
});
});
} else {
// not the first request, wait for first request to finish
return new Promise((resolve, reject) => {
const intervalId = setInterval(() => {
console.log("refresh token - waiting");
if (!authStore.isRefreshing) {
clearInterval(intervalId);
console.log("refresh token - waiting resolved", err.config);
resolve(axios(err.config));
}
}, 100);
});
}
}
return Promise.reject(err);
}
);
But when axios retries the post request, it changes the content-type:
versus the original request (with content-type application/json)
I've read every post/example I could possible find with no luck, I am relatively new to axios and any guidance/examples/documentation is greatly appreciated, I'm against the wall.
To clarify, I used this pattern because it was the most complete example I was able to put together using many different sources, I would appreciate if someone had a better pattern.
Here's your problem...
config.headers = {
Authorization: `Bearer ${authStore.user.accessToken}`,
};
You're completely overwriting the headers object in your request interceptor, leaving it bereft of everything other than Authorization.
Because the replayed err.config has already serialised the request body into a string, removing the previously calculated content-type header means the client has to infer a plain string type.
What you should do instead is directly set the new header value without overwriting the entire object.
config.headers.Authorization = `Bearer ${authStore.user.accessToken}`;
See this answer for an approach to queuing requests behind an in-progress (re)authentication request that doesn't involve intervals or timeouts.

download attachments from mail using microsoft graph rest api

I've been successfully getting the list of mails in inbox using microsoft graph rest api but i'm having tough time to understand documentation on how to download attachments from mail.
For example : This question stackoverflow answer speaks about what i intend to achieve but i don't understand what is message_id in the endpoint mentioned : https://outlook.office.com/api/v2.0/me/messages/{message_id}/attachments
UPDATE
i was able to get the details of attachment using following endpoint : https://graph.microsoft.com/v1.0/me/messages/{id}/attachments and got the following response.
I was under an impression that response would probably contain link to download the attachment, however the response contains key called contentBytes which i guess is the encrypted content of file.
For attachment resource of file type contentBytes property returns
base64-encoded contents of the file
Example
The following Node.js example demonstrates how to get attachment properties along with attachment content (there is a dependency to request library):
const attachment = await getAttachment(
userId,
mesasageId,
attachmentId,
accessToken
);
const fileContent = new Buffer(attachment.contentBytes, 'base64');
//...
where
const requestAsync = options => {
return new Promise((resolve, reject) => {
request(options, (error, res, body) => {
if (!error && res.statusCode == 200) {
resolve(body);
} else {
reject(error);
}
});
});
};
const getAttachment = (userId, messageId, attachmentId, accessToken) => {
return requestAsync({
url: `https://graph.microsoft.com/v1.0/users/${userId}/messages/${messageId}/attachments/${attachmentId}`,
method: "GET",
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json;odata.metadata=none"
}
}).then(data => {
return JSON.parse(data);
});
};
Update
The following example demonstrates how to download attachment as a file in a browser
try {
const attachment = await getAttachment(
userId,
mesasageId,
attachmentId,
accessToken
);
download("data:application/pdf;base64," + attachment.contentBytes, "Sample.pdf","application/pdf");
} catch (ex) {
console.log(ex);
}
where
async function getAttachment(userId, messageId, attachmentId, accessToken){
const res = await fetch(
`https://graph.microsoft.com/v1.0/users/${userId}/messages/${messageId}/attachments/${attachmentId}`,
{
method: "GET",
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json;odata.metadata=none"
}
}
);
return res.json();
}
Dependency: download.js library
I don't know if this would help but you just have to add /$value at the end of your request :
https://graph.microsoft.com/v1.0/me/messages/{message_id}/attachments/{attachment_id}/$value

Post request with Cookies in Flutter

I'm trying to add Cookies to my request:
Here i get csrftoken with a GET request:
Future<String> getCsrftoken() async{
var response = await http.get(Uri.encodeFull('http://test/accounts/login/'));
var csrftoken = response.headers.remove('set-cookie').substring(10,74); //csrf
64 chars
return csrftoken;
}
Here i'm trying to perform the POST (application/x-www-form-urlencoded) request using the package Dio.
getSessionId() async {
var csrf = await getCsrftoken();
var cj = new CookieJar();
List<Cookie> cookies = [new Cookie("csrftoken", csrf)];
cj.saveFromResponse(Uri.parse("http://test/accounts/login/"), cookies);
List<Cookie> results = cj.loadForRequest(Uri.parse("http://test/accounts/login/"));
var dio = new Dio(new Options(
baseUrl: "http://test/accounts/login/",
connectTimeout: 5000,
receiveTimeout: 100000,
// 5s
headers: {
},
contentType: ContentType.JSON,
// Transform the response data to a String encoded with UTF8.
// The default value is [ResponseType.JSON].
responseType: ResponseType.PLAIN
));
Response<String> response;
response = await dio.post("",
data: {
"username": "username",
"password": "password",
"csrfmiddlewaretoken" : getCsrftoken()
},
// Send data with "application/x-www-form-urlencoded" format
options: new Options(
contentType: ContentType.parse("application/x-www-form-urlencoded")),
);
print(response.statusCode);
}
I get 403 status code, because i need to add as a cookie csrftoken.
How should I proceed?
From the Dio Dart API Docs:
Cookie Manager
You can manage the request/response cookies using cookieJar .
The dio cookie manage API is based on the withdrawn cookie_jar.
You can create a CookieJar or PersistCookieJar to manage cookies automatically, and dio use the CookieJar by default, which saves the cookies in RAM. If you want to persists cookies, you can use the PersistCookieJar class, the example codes as follows:
var dio = new Dio();
dio.cookieJar=new PersistCookieJar("./cookies");
PersistCookieJar is a cookie manager which implements the standard cookie policy declared in RFC. PersistCookieJar persists the cookies in files, so if the application exit, the cookies always exist unless call delete explicitly.
More details about cookie_jar see : https://github.com/flutterchina/cookie_jar .
Check if the csrftoken needs to be passed in the header and the cookie or just one of them. It sometimes needs to be included as a header, which is shown in the example below, but the header name varies. To persist cookies, use a PersistCookieJar. Other options are persisted through BaseOptions (previously named Options).
Add to pubspec.yaml the latest versions of these plugins
path_provider: ^1.1.0
dio: ^2.1.6
cookie_jar: ^1.0.0
In a new class named webFunctions:
import 'dart:io';
import 'package:cookie_jar/cookie_jar.dart';
import 'package:dio/dio.dart';
import 'package:path_provider/path_provider.dart';
class webFunctions {
final Dio _dio = Dio();
PersistCookieJar persistentCookies;
final String URL = "http://test/";
Future<String> get _localPath async {
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
Future<Directory> get _localCoookieDirectory async {
final path = await _localPath;
final Directory dir = new Directory('$path/cookies');
await dir.create();
return dir;
}
Future<String> getCsrftoken() async{
try {
String csrfTokenValue;
final Directory dir = await _localCoookieDirectory;
final cookiePath = dir.path;
persistentCookies = new PersistCookieJar(dir: '$cookiePath');
persistentCookies.deleteAll(); //clearing any existing cookies for a fresh start
_dio.interceptors.add(
CookieManager(persistentCookies) //this sets up _dio to persist cookies throughout subsequent requests
);
_dio.options = new BaseOptions(
baseUrl: URL,
contentType: ContentType.json,
responseType: ResponseType.plain,
connectTimeout: 5000,
receiveTimeout: 100000,
headers: {
HttpHeaders.userAgentHeader: "dio",
"Connection": "keep-alive",
},
); //BaseOptions will be persisted throughout subsequent requests made with _dio
_dio.interceptors.add(
InterceptorsWrapper(
onResponse:(Response response) {
List<Cookie> cookies = persistentCookies.loadForRequest(Uri.parse(URL));
csrfTokenValue = cookies.firstWhere((c) => c.name == 'csrftoken', orElse: () => null)?.value;
if (csrfTokenValue != null) {
_dio.options.headers['X-CSRF-TOKEN'] = csrfTokenValue; //setting the csrftoken from the response in the headers
}
return response;
}
)
);
await _dio.get("/accounts/login/");
return csrfTokenValue;
} catch (error, stacktrace) {
print("Exception occured: $error stackTrace: $stacktrace");
return null;
}
}
getSessionId() async {
try {
final csrf = await getCsrftoken();
FormData formData = new FormData.from({
"username": "username",
"password": 'A *passphrase* is stronger than a password.',
"csrfmiddlewaretoken" : '$csrf'
});
Options optionData = new Options(
contentType: ContentType.parse("application/x-www-form-urlencoded"),
);
Response response = await _dio.post("/accounts/login/", data: formData, options: optionData);
print(response.statusCode);
} on DioError catch(e) {
if(e.response != null) {
print( e.response.statusCode.toString() + " " + e.response.statusMessage);
print(e.response.data);
print(e.response.headers);
print(e.response.request);
} else{
print(e.request);
print(e.message);
}
}
catch (error, stacktrace) {
print("Exception occured: $error stackTrace: $stacktrace");
return null;
}
}
}

Getting Data with Header Authorization Ionic 2

I'm currently working on Ionic 2 with Ruby on Rails as a backend. The issue I face is that I have trouble understanding Observables and Promises. Are they related to each other? Right now I'm trying to retrieve the data after the POST request is authenticated with the header.
//clocks.ts (Provider)
import { Injectable } from '#angular/core';
import { Http, Headers, Response, RequestOptions } from '#angular/http';
import { Storage } from '#ionic/storage';
import 'rxjs/add/operator/map';
#Injectable()
export class Clocks {
baseUrl: string = "http://localhost:3000/api/v1"
token: any;
constructor(public http: Http, public storage: Storage) {}
getAttendanceInfo() {
return new Promise((resolve,reject) => {
// Load token
this.storage.get('token').then((value) => {
this.token = value;
let headers = new Headers();
headers.append('Authorization', 'Token ' + this.token);
this.http.get(this.baseUrl + '/attendances.json', {headers: headers})
.subscribe(res => {
resolve(res);
}, (err) => {
reject(err);
})
});
});
}
At Attendance Page
//attendance.ts (Page)
loadAttendance() {
this.clocks.getAttendanceInfo().then(res => {
let response = (<Response>res).json();
this.attendance = response.data;
console.log(this.attendance)
})
}
Here are my questions.
Could I use Observables in this case to achieve the same result as the getAttendanceInfo() method? How do they work?
And also, is there any way that I can retrieve the token from the storage for every page request without rewriting the same code for headers? Eg. One method that can always be used to retrieve the token from the storage and append at the header.
Greatly appreciate if you guys can clear my confusion.
I've found the solution.
You can create a authentication as part of the service provider. For this case, I'm using localStorage. For the remarks, the structure of the Token is dependent for your backend as well. For my case, I'm using authentication_with_http_token from Rails method, the structure is like this
GET /attendances HTTP/1.1
Host: localhost:3000
Authorization: Token token=123123123
We have to match that.
// ../providers/auth.ts
import { Injectable } from '#angular/core';
import { Http, Headers } from '#angular/http';
import 'rxjs/add/operator/map';
#Injectable()
export class Auth {
constructor(public http: Http) {}
createAuthorizationHeader(headers: Headers) {
headers.append('Authorization', 'Token ' + window.localStorage.getItem('token'));
}
}
Wen you return a http request, it was returned in a Observable format. Unless you want to convert into Promises, you don't have to do anything about it.
// ../pages/attendances/attendances.ts
import { Component } from '#angular/core';
import { NavController } from 'ionic-angular';
import { Auth } from '../../providers/auth';
#Component({
selector: 'page-home',
templateUrl: 'home.html'
})
const baseUrl: string = 'http://localhost:3000/api/v1/';
export class HomePage {
constructor(public navCtrl: NavController, public auth: Auth) {}
ionViewDidLoad() {
this.getAttendances();
}
getAttendances() {
return this.http.get(baseUrl + 'bookings.json', { headers: headers })
.map(data => {
// convert your data from string to json
data = data.json();
})
.subscribe(response => {
// to subscribe to the stream for the response
console.log(response);
// you can save your response in object based on how you want it
})
}

Angular 2 authentication using cookies [duplicate]

I'm having trouble moving my Angular 1 JavaScript service to a Angular 2 TypeScript service using http to make a CORS request (this is using Ionic version 2). In Angular 1, I do something like this.
angular.module('app.services',[])
.factory('LoginService', ['$http', function($http) {
var service = {};
service.isUserLoggedIn = function() {
var restUrl = 'http://10.10.10.25:8080/api/user/isloggedin';
var options = {
method: 'GET', url: restUrl, withCredentials: true,
headers: {
'x-request-with': 'XMLHttpRequest',
'x-access-token': 'sometoken'
}
};
return $http(options);
};
return service;
}])
In Angular 2 using TypeScript, a lot has changed (Promises/Observables). My attempt so far looks like the following.
import { Injectable } from '#angular/core';
import { Headers, Http, Response, RequestMethod, RequestOptionsArgs } from '#angular/http';
import { Observable } from 'rxjs/Observable';
#Injectable()
export class LoginService {
constructor(private _http:Http) {
}
isLoggedIn():boolean {
var r:any;
var e:any;
let url = 'http://10.10.10.25:8080/api/user/isloggedin';
let options:RequestOptionsArgs = {
url: url,
method: RequestMethod.Get,
search: null,
headers: new Headers({
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
'x-access-token' : 'sometoken'
}),
body: null
};
this._http.get(url, options).map(this.extractData).catch(this.handleError)
.subscribe(
response => { r = <any>response; console.log(r); },
error => { e = <any>error; console.log(e); });
return false;
}
private extractData(response:Response) {
let body = response.json();
return body.data || { };
}
private handleError(error:any) {
let errMsg = (error.message) ? error.message :
error.status ? `${error.status} - ${error.statusText}` : 'Server error';
console.log('error = ' + errMsg);
return Observable.throw(errMsg);
}
}
I am no longer sure how to address in Angular 2 what I had in Angular 1
withCredentials (note that https://angular.io/docs/js/latest/api/http/index/RequestOptionsArgs-interface.html#!#withCredentials-anchor says the RequestOptionsArgs should have a field withCredentials but I get an error in the IDE Visual Studio Code saying that it does not exists in the interface; ionic is probably using an older angular 2 version?)
headers (x-request-with and x-access-token)
the actual response
I did a little bit more searching, and I was able to understand how to insert headers and properly handle the subscription. However, the withCredentials is still a problem.
I found this SO post angular2 xhrfields withcredentials true and modified my constructor as follows. It works now.
constructor(#Inject(Http) private _http:Http) {
let _build = (<any> _http)._backend._browserXHR.build;
(<any> _http)._backend._browserXHR.build = () => {
let _xhr = _build();
_xhr.withCredentails = true;
return _xhr;
}
}
The support of withCredentails will be present in the RC2 version of Angular2 that will be released soon. It's not part of the RC1 version... You need to wait a bit.
With RC2, you will be able to use this property directly in the request options:
this.get('...', { withCredentials: true })
See this question for more details:
Angular 2 - http get withCredentials

Resources