Currently, after logging in I'm able to get the JWT to the frontend. My app currently has a logging page as the landing page and as soon as the user logins the route checks for authentication to redirect to the guarded home path.
My first intuition was to send a boolean from the backend (Django) and use that to create a guard. But I keep seeing that seems to be better practice to handle this in the front end.
What I did was create an auth.service.ts and an auth.guard.ts. In the service, I try to retrieve the token from the browser and then verify that it hasn't expired. Then I call that method on the guard and return a boolean. Problem is that every time I look for the token in the local storage, I get back null.
Is there any better way to get achieve this?
auth.guard.ts
import { Injectable } from '#angular/core';
import {
ActivatedRouteSnapshot,
CanActivate,
Router,
RouterStateSnapshot,
UrlTree,
} from '#angular/router';
import { Observable, of } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { AuthService } from './auth.service';
#Injectable({
providedIn: 'root',
})
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
):
| Observable<boolean | UrlTree>
| Promise<boolean | UrlTree>
| boolean
| UrlTree {
console.log(this.authService.isAuthenticated());
if(!this.authService.isAuthenticated()){
this.router.navigate(['/login']);
return false;
}
return true;
}
}
auth.service.ts
import { Injectable } from '#angular/core';
import { JwtHelperService } from '#auth0/angular-jwt';
#Injectable({
providedIn: 'root'
})
export class AuthService {
public jwtHelper: JwtHelperService = new JwtHelperService();
constructor() { }
isAuthenticated(){
const jwt = localStorage.getItem('token');
return !this.jwtHelper.isTokenExpired(jwt!);
}
}
app-routing.module.ts
...
import { AuthGuard } from './user/services/auth.guard'
const routes: Routes = [
{
path: '',
component: LandingComponent,
children: [
{ path: '', component: HomeComponent, canActivate: [AuthGuard],},
{ path: 'home', component: HomeComponent, canActivate: [AuthGuard],},
{
path: 'cohort-charts',
component: CohortChartsComponent,
children: [
{ path: 'selection', component: CohortSelectionComponent },
{ path: 'edit', component: CohortEditComponent },
{ path: '', redirectTo: 'selection', pathMatch: 'full' },
],
},
],
},
{
path: 'login',
component: LoginComponent,
},
{ path: '**', redirectTo: '' },
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}
I guess that every time you look for the token in the local storage, you get back null because you aren't saving the token, or if you do, you are trying to store the token as object, not serialized (as string, stringifying it) , so it doesn't store, or when you get it, you aren't pasing it.
Any way, I guess that the best practice to manage the whole jwt/authentification section, would be with an interceptor:
And Interceptor is a service which intercepts all your http calls, and you cans set that it does something authomatically (for instance, magaging the jwt).
More info about how to Adding and updating headers and how to Use the interceptor for Intercepting requests and responses:
https://angular.io/guide/http#adding-and-updating-headers
https://angular.io/guide/http#intercepting-requests-and-responses
I leave you a glimpse of how you do it /what you need:
Provide the Angular Interceptor
app.module.ts:
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: HttpJwtAuthInterceptor,
multi: true,
},
{ provide: BASE_PATH, useValue: environment.apiUrl },
],
First, in your auth.service.ts, 2 methods to store/get the token
// STORE the token in localstore:
setToken(token:string){
// First, serialize it (but just if token is not string type).
const tokenString:string = JSON.stringify( token );
localStorage.setItem('token', tokenString);
}
// READ the token from localstorage and Deserialize
getToken(): string | null{
let token = localStorage.getItem( 'token' );
if( token !=null){
// You just need to parse if you serialized it inside setToken() method
token = JSON.parse(carItemsString);
}
return token;
}
Then, in your interceptor:
import { Injectable } from '#angular/core';
import {
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptor,
} from '#angular/common/http';
import { AuthService } from '../_services/auth.service';
#Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private authService: AuthService) {}
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
const url="\yourAPI\endpoint";
// Get your token
cont myToken = this.authService.getToken();
// Add authorization header with token if available
if (myToken) {
request = request.clone({
setHeaders: {
Authorization: `Bearer ${myToken}`,
'Content-Type': 'application/json',
},
url,
});
}
…
return next.handle(request);
}
Related
I have created a Django and Angular application to upload files. It was working without errors until I integrated a login page. I have not been able to upload files since integration. I get 401 - "Unauthorized" error. What could have possibly gone wrong?
Auth-interceptor:
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest,HttpErrorResponse } from "#angular/common/http";
import { Injectable } from "#angular/core";
import { catchError, Observable, throwError } from "rxjs";
import { LoginService } from "src/services/login.service";
#Injectable()
export class AuthInterceptorService implements HttpInterceptor {
constructor(private authService: LoginService) {}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (this.authService.isLoggedIn()) {
const token = this.authService.getAuthToken();
console.log("intercept",token)
// If we have a token, we set it to the header
request = request.clone({
setHeaders: {Authorization: `Token ${token}`}
});
}
return next.handle(request)
}
}
fileupload.component.ts:
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormGroup } from '#angular/forms';
import { LoginService } from 'src/services/login.service';
import { FileUploader, FileLikeObject } from 'ng2-file-upload';
import { concat, Observable } from 'rxjs';
import { HttpEvent, HttpEventType } from '#angular/common/http';
#Component({
selector: 'app-fileupload',
templateUrl: './fileupload.component.html',
styleUrls: ['./fileupload.component.scss']
})
export class FileuploadComponent {
DJANGO_SERVER = 'http://127.0.0.1:8081'
public uploader: FileUploader = new FileUploader({});
public hasBaseDropZoneOver: boolean = false;
constructor(private formBuilder: FormBuilder, private uploadService: LoginService) { }
fileOverBase(event): void {
this.hasBaseDropZoneOver = event;
}
getFiles(): FileLikeObject[] {
return this.uploader.queue.map((fileItem) => {
return fileItem.file;
});
}
upload() {
let files = this.getFiles();
console.log(files);
let requests= [];
files.forEach((file) => {
let formData = new FormData();
formData.append('file' , file.rawFile, file.name);
requests.push(this.uploadService.upload(formData));
console.log(requests,file)
});
concat(...requests).subscribe(
(res) => {
console.log(res);
},
}
);
}}
console.log(err);
}
);
}}
service:
public upload(formData) {
let token= localStorage.getItem('token');
return this.http.post<any>(`${this.DJANGO_SERVER}/upload/`, formData).pipe(map((res) => {
console.log(res)
})
)
}
Thank you
I resolved the issue. It was because I was usign interceptor and I was using third party API for authentication. So instead of Django token, the third party APIs token was sent in header of POST request.
How I resolved it?
I used Httpbackend to process POST requests to Django DB so that the request is not intercepted and then I added custom header (with Django token to the reuest). I used the code snippet on this website: https://levelup.gitconnected.com/the-correct-way-to-make-api-requests-in-an-angular-application-22a079fe8413
Can anyone help me with a simple question(I guess). I'm trying to display a json file in a table through Django into an Angular front end app. I can view the data by console logging it, however I can't seem to get the data into the webpage.
I know how to display the table in HTML. The specific problem is that the object which is fetched from the API does not appear in the HTML.
component.ts
import { AfterViewInit, ChangeDetectionStrategy, Component, OnDestroy, OnInit, ViewChild,
ViewEncapsulation } from '#angular/core';
import ApexChart from 'apexcharts'
import { ApexOptions } from 'ng-apexcharts';
import { FinanceService } from 'app/modules/admin/dashboards/finance/finance.service';
#Component({
selector: 'app-finance',
templateUrl: './finance.component.html',
styleUrls: ['./finance.component.scss'],
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class FinanceComponent implements OnInit {
_stockData: any;
_portData: any;
_effData: any;
_calData: any;
_labels: any;
_table: any;
constructor(
private _financeService: FinanceService,
){
}
ngOnInit(): void {
this._financeService.getFinanceData()
.subscribe((res: any) => {
console.log(res);
this._stockData = res.stocks;
this._portData = res.port;
this._effData = res.eff;
this._calData = res.cal;
this._labels = res.labels;
this._table = res.table;
console.log(res.table);
this._prepareChartData();
},
(response) => {
// Re-enable the form
});
}
Service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { catchError, switchMap } from 'rxjs/operators';
import { BehaviorSubject, of, Observable } from 'rxjs';
import { environment } from 'environments/environment';
#Injectable({
providedIn: 'root'
})
export class FinanceService {
/**
* Constructor
* #param {HttpClient} _httpClient
*/
constructor(private _httpClient: HttpClient){
}
// -----------------------------------------------------------------------------------------------------
// # Public methods
// -----------------------------------------------------------------------------------------------------
/**
* get Finance Data
*/
getFinanceData(): Observable<any>{
return this._httpClient.get(environment.baseURL + 'apiurls/', {}).pipe(
switchMap((response: any) => {
return of(response);
})
);
}
}
template.html
<div>
<p>{{_table.Percentage}}</p>
</div>
json (as presented by the django API)
{
"stocks":[],
"eff":[],
"port":[],
"cal":[],
"labels":[],
"table":{
"Percentage":{
"AD.AS":16.1,
"ASML.AS":15.67,
"AAPL":68.23
},
"Budget":{
"AD.AS":241.5,
"ASML.AS":235.05,
"AAPL":1023.45
},
"Closing":{
"AD.AS":25.22,
"ASML.AS":314.3,
"AAPL":129.04
},
"Number of stocks to buy":{
"AD.AS":10.0,
"ASML.AS":1.0,
"AAPL":8.0
},
"final":{
"AD.AS":252.0,
"ASML.AS":314.0,
"AAPL":1032.0
},
"final allocation":{
"AD.AS":15.77,
"ASML.AS":19.65,
"AAPL":64.58
},
"Diff":{
"AD.AS":-0.33,
"ASML.AS":3.98,
"AAPL":-3.65
}
}
}
To display the json data on html you may use json pipe
<pre>
{{ _table.Percentage | json }}
</pre>
i am trying to develop django/angular project and using base user model for django user.
I tried to implement JWT auth in both side. On django side i am using rest_framework_jwt library.
I checked the token flow from client-side to server-side. With a simple login page on angular i use my username and password for logging in. It's accepting my data and redirects me to the main page where i use a service to get "feed" data from server-side. In this step it gives me 403 error. By the way when i use Postman for checking the scenerios im adding jwt as a authorization header like "JWT " and it still give me "Authentication credentials were not provided". Waiting your helps. Thanks.
django -> settings.py middleware ;
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
django -> settings py rest_framework ;
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATON_CLASSES':(
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
),
'NON_FIELD_ERRORS_KEY': 'global',
}
i use these settings for jwt
#JWT SETTINGS
JWT_AUTH = {
'JWT_ALLOW_REFRESH': True,
'JWT_EXPIRATION_DELTA': timedelta(days=2) # datetime imported at start
}
using classic urls.py ( its and included part so it begins with api/auth/ )
urlpatterns = [
path('login/', obtain_jwt_token),
path('refresh-token/', refresh_jwt_token),
]
in front end i have a interceptor, authservice and feedservice
authservice
import { Injectable } from '#angular/core';
import { HttpClient, HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '#angular/common/http';
import { tap, shareReplay } from 'rxjs/operators';
import * as JwtDecode from 'jwt-decode';
import * as moment from 'moment';
import { JWTPayload } from '../interfaces/JWTPayload';
// logs in and out, notifies other components with subscription
#Injectable({
providedIn: 'root'
})
export class AuthenticationService {
URL_API = 'http://localhost:80/api/auth';
constructor(private http: HttpClient) { }
private setSession(authResult) {
const token = authResult.token;
const payload = <JWTPayload>JwtDecode(token);
const expiresAt = moment.unix(payload.exp);
localStorage.setItem('token', authResult.token);
localStorage.setItem('expires_at', JSON.stringify(expiresAt.valueOf()));
}
get token(): string {
return localStorage.getItem('token');
}
login(username: string, password: string) {
return this.http.post(`${this.URL_API}/login/`, { username, password })
.pipe(tap(
response => {
console.log("API CALL RESPONSE FOR LOGIN => ", response);
this.setSession(response);
}
),
shareReplay(),
);
}
logout() {
localStorage.removeItem('token');
localStorage.removeItem('expires_at');
console.log("CIKIS YAPILDI ");
}
refreshToken() {
if (moment().isBetween(this.getExpiration().subtract(1, 'days'), this.getExpiration())) {
return this.http.post(
`${this.URL_API}/refresh-token/`,
{ token: this.token }
).pipe(
tap(response => { this.setSession(response); console.log("response for refresh token=>", response); }),
shareReplay(),
).subscribe();
}
}
getExpiration() {
const expiration = localStorage.getItem('expires_at');
const expiresAt = JSON.parse(expiration);
console.log("expires=>", moment(expiresAt).calendar());
return moment(expiresAt);
}
isLoggedIn() {
return moment().isBefore(this.getExpiration());
}
isLoggedOut() {
return !this.isLoggedIn();
}
}
interceptor
import { Injectable } from '#angular/core';
import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '#angular/router';
import { AuthenticationService } from '../services/authentication.service';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '#angular/common/http';
import { Observable } from 'rxjs';
// intercepts every api call and adds jwt header to call.
#Injectable({
providedIn: 'root'
})
export class AuthInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const token = localStorage.getItem('token');
console.log("firs token check => ", token);
if (token) {
console.log("gecerli token=> ", token)
const cloned = req.clone({
headers: req.headers.set('Authorization', 'JWT '.concat(token))
});
console.log("cloned ->", cloned.body);
return next.handle(cloned);
} else {
console.log("next.handle(req) => ", next.handle(req));
return next.handle(req);
}
}
}
feedservice
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Observable } from "rxjs";
import { Post } from '../interfaces/Post';
#Injectable({
providedIn: 'root'
})
export class FeedService {
API_URL = 'http://localhost:80'
constructor(private httpClient: HttpClient) { }
public getFeed(): Observable<Post[]> {
return this.httpClient.get<Post[]>(`${this.API_URL}/api/feed/`);
}
public retrievePost(post_id: number): Observable<Post> {
return this.httpClient.get<Post>(`${this.API_URL}/api/feed/${post_id}`)
}
public postFeed(post: Post) {
return this.httpClient.post(`${this.API_URL}/api/feed/`, post)
}
}
and in finale there is login component
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormGroup, Validators } from '#angular/forms';
import { Router, ActivatedRoute } from '#angular/router';
import { first } from 'rxjs/operators';
import { AuthenticationService } from '../services/authentication.service';
#Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
loginForm: FormGroup
returnUrl: string
loading = false;
submitted = false;
error = '';
constructor(
private formBuilder: FormBuilder,
private authenticationService: AuthenticationService,
private router: Router,
private route: ActivatedRoute
) {
//redirect home if already logged in
if (authenticationService.isLoggedIn()) {
console.log("isLoggedIn() => already logged in")
router.navigate(['/']);
}
}
ngOnInit(): void {
this.loginForm = this.formBuilder.group({
username: ['', Validators.required],
password: ['', Validators.required]
});
//get returl url or redirect to /
this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
}
//easy way to access fields
get f() { return this.loginForm.controls }
onSubmit() {
this.submitted = true;
// stop here if form is invalid
if (this.loginForm.invalid) { console.log("if code block => invalid credentials"); return; }
this.loading = true;
this.authenticationService.login(this.f.username.value, this.f.password.value)
.subscribe(
success => { console.log("succesfull login"); this.router.navigate([this.returnUrl]); },
error => {
this.error = error;
this.loading = false;
console.log("errors =>", error);
}
);
}
}
I figured it out. In my other api's view i added those lines
permission_classes = [ IsAuthenticated ]
authentication_classes = [ JSONWebTokenAuthentication ]
and it worked!
I'm trying to integrate ibeacon feature in Ionic 2 app.
I'm using https://ionicframework.com/docs/native/ibeacon/ plugin.
Followed the steps as mentioned in the document.
Created a provider class.
Added the plugin integration.
Invoke the provider class in Home page.
But when running the app on android device, getting error,
"Failed to navigate: No provider for IBeacon!"
Please suggest any fix.
Thanks.
Beacon Provider class:
import { Injectable } from '#angular/core';
import { Platform, Events } from 'ionic-angular';
import { IBeacon } from '#ionic-native/ibeacon';
/*
Generated class for the BeaconProvider provider.
//
See https://angular.io/docs/ts/latest/guide/dependency-injection.html
for more info on providers and Angular 2 DI.
*/
#Injectable()
export class BeaconProvider {
delegate: any;
region: any;
constructor(public platform: Platform, public events: Events, private ibeacon : IBeacon) {
}
initialise(): any {
let promise = new Promise((resolve, reject) => {
// we need to be running on a device
if (this.platform.is('cordova')) {
// Request permission to use location on iOS
this.ibeacon.requestAlwaysAuthorization();
// create a new delegate and register it with the native layer
this.delegate = this.ibeacon.Delegate();
// Subscribe to some of the delegate’s event handlers
this.delegate.didRangeBeaconsInRegion()
.subscribe(
data => {
this.events.publish('didRangeBeaconsInRegion', data);
},
error => console.error()
);
// setup a beacon region – CHANGE THIS TO YOUR OWN UUID
this.region = this.ibeacon.BeaconRegion('deskBeacon', 'E2C56DB5-DFFB-48D2-B060-D0F5A71096E0');
// start ranging
this.ibeacon.startRangingBeaconsInRegion(this.region)
.then(
() => {
resolve(true);
},
error => {
console.error('Failed to begin monitoring: ', error);
resolve(false);
}
);
} else {
console.error('This application needs to be running on a device');
resolve(false);
}
});
return promise;
}
}
And in Home page,
import { Component } from '#angular/core';
import { NavController } from 'ionic-angular';
import { AuthService } from '../../providers/auth-service';
import { LoginPage } from '../login/login';
import { BeaconProvider } from '../../providers/beacon-provider';
import { BeaconModel } from '../../models/beacon-module';
import { Platform, Events } from 'ionic-angular';
import { NgZone } from '#angular/core';
#Component({
selector: 'page-home',
templateUrl: 'home.html',
providers : [BeaconProvider]
})
add
import { IBeacon } from '#ionic-native/ibeacon'; to your app.module.ts
and add IBeacon to your providers in app.module.ts.
That fixed the issue for me.
Have you tried adding the service in the constructor of home.ts ?
constructor(private myService: IBeacon ){
}
(This is for Ionic 3 but the process is similar for Ionic 2)
I suggest you put IBeacon definition in app.module.ts under the provider list like this
#NgModule({
declarations: [
MyApp,
],
imports: [
BrowserModule,
HttpModule,
HttpClientModule,
AngularFireDatabaseModule,
AngularFireModule.initializeApp(config),
AngularFireAuthModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: (createTranslateLoader),
deps: [HttpClient]
}
}),
IonicModule.forRoot(MyApp),
IonicStorageModule.forRoot()
],
bootstrap: [IonicApp],
entryComponents: [
MyApp,
],
providers: [
Api,
Items,
User,
Camera,
CardIO,
NFC,
Ndef,
IBeacon,
Stripe,
SplashScreen,
StatusBar,
{ provide: Settings, useFactory: provideSettings, deps: [Storage] },
// Keep this to enable Ionic's runtime error handling during development
{ provide: ErrorHandler, useClass: IonicErrorHandler },
FirebaseProvider,
BarcodeScanner,
AuthProvider,
BeaconProvider,
]
})
export class AppModule { }
I am a newbie to Angular 2 and Karma + Jasmine unit tests. I cannot figure out what semantic error I have made in order to make this unit test use the mocked response. In the console, when "expect(items[0].itemId).toBe(2);" is run, it says items[0].itemId is undefined.
Would someone be able to help me out or point me in the right direction? Please let me know if you need any additional information. Thanks!
item.ts
export class Item {
itemId: number;
itemName: string;
itemDescription: string;
}
item.service.ts
import { Injectable, Inject } from '#angular/core';
import { Headers, Http } from '#angular/http';
import { Observable } from 'rxjs/Rx';
import { Item } from './item';
#Injectable()
export class ItemService {
private headers = new Headers({'Content-Type': 'application/json'});
constructor(
private http: Http)
{
}
getItems(listOptions: Object): Observable<Item[]> {
return this.http.post('/listItems', listOptions, {headers:this.headers})
.map(response => response.json() as Item[])
}
}
item.service.spec.ts
import { TestBed, fakeAsync, inject, tick } from '#angular/core/testing';
import { MockBackend } from '#angular/http/testing';
import { Http, BaseRequestOptions, Response, ResponseOptions } from '#angular/http';
import { Observable } from 'rxjs/Rx';
import { ItemService } from './item.service';
import { Item } from './item';
describe('ItemService', () => {
let mockResponse, matchingItem, connection;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
ItemService,
MockBackend,
BaseRequestOptions,
{
provide: Http,
useFactory: (backend, defaultOptions) => new Http(backend, defaultOptions),
deps: [MockBackend, BaseRequestOptions]
},
// { provide: XHRBackend, useClass: MockBackend }
]
});
const items = [
{
"itemId":2,
"itemName":"test item1",
"itemDescription":"hello hello"
},
{
"itemId":1,
"itemName":"name2124111121",
"itemDescription":"description212412112"
}
];
mockResponse = new Response(new ResponseOptions({body: {data: items}, status: 200}));
});
describe('getItems', () => {
//Subscribing to the connection and storing it for later
it('should return all the items',inject([ItemService, MockBackend], (service: ItemService, backend: MockBackend) => {
backend.connections.subscribe(connection => {
connection.mockRespond(mockResponse);
});
service.getItems({isActive: true, sortColumn: "lastModifiedDateUtc", sortOrder: "desc"})
.subscribe((items: Item[]) => {
expect(items.length).toBe(2);
});
}));
});
});
Plunkr: https://plnkr.co/edit/m7In2eVh6oXu8VNYFf9l?p=preview
(There are some errors with the Plunkr I need help with as well but the main files are there)
The mockResponse body did not match the actual response body, that is why I was getting the error.
mockResponse = new Response(new ResponseOptions({body: {data: items}, status: 200})); should be mockResponse = new Response(new ResponseOptions({body: items, status: 200}));