简体   繁体   中英

Flask-Login doesn't set cookies to browser with angular?

I'm building a fullstack web project using Angular 6 & Python Flask, mainly using Flask-Security extension.

Currently, I try to implement a login system of users, using the login_user() (Flask-Login's method) . Basiclly, login_user() works, but I can't see any session cookies on my browser.

As documentation says, every change/new instance of session object sets/modifies cookies accordingly, so login_user() creates a new instance of session .

I'm running and testing the project on ' http://127.0.0.1:4200 ' (Angular default port) , and Flask, using ' http://127.0.0.1:5000/ '.

As a last resort, I tried to build a Flask app without any actual frontend, running and testing it from ' http://127.0.0.1:5000/ ', and it did work. I've managed to see the cookies that login_user() should've set from the start.

Mainly, my question, why doesn't it work with Angular? frontend code:

export class Login {
    constructor(private userSerivce : UserService, private router : Router) {}

    outputMessage : string = null;

    loginOnSubmit(form : FormGroup): void {
        let formDict = {
            "email" : form.controls["email"].value,
            "password" : form.controls["password"].value
        }
        this.userSerivce.loginOnSubmit(formDict).subscribe({
            next : value => {
                //whatever, still didn't get here
            },
            error : response => {this.outputMessage = response.error}
        })
    }

backend login function:

@user_app.route('/signin', methods=['POST'])
def signIn():
    session.permanent = True
    status_code = 200
    output_string = None
    form = json.loads(request.data.decode('utf-8'))
    user = User.query.filter_by(email=form['email']).first()
    if user is not None:
        if utils.verify_password(form['password'],user.password) and user.is_authenticated:
            user.active = True
            db.session.commit()
            if login_user(user, True, datetime.timedelta(days=24), False, True):
                i=1 #debugging purposes only
        else:
            status_code = 400
            output_string = "error"
    else:
        status_code = 400
        output_string = "error"

    return jsonify(1), status_code

The models is exactly as documentation suggests, I even used the same code in my empty Flask app (the same classes and database, and as I said, it worked).

You can't set browser cookies by using the server session. You'd have to send cookies in the response. If you want to set cookies in the response, you could do something like this:

from flask import make_response # and your other stuff
# ... other imports ...
def login():
# ... some authentication code here to get your access_token (like a jwt)...
    resp = make_response(redirect('http://localhost:4200')) # your angular app
    resp.set_cookie('token', access_token) # set the cookie on the response header for the browser to extract
    return resp # return the response with the new cookie attached

Since your client application isn't on the same domain as your server application, setting the session isn't going to help you in the way you want for authentication. The best way to do what you want is to pass a JWT back and forth between client and server.

One thing you can try to do (if you want to set some kind of authentication on the front end) would be to authenticate your user return a JWT back to Angular. You could then set an http header to come to the backend each time. The backend would parse the request and extract the JWT from the header. You would then use that header to authenticate the user's request to your backend by decrypting the JWT when it comes in. There is a great deal of literature on this. I'll put in some good tutorials at the end of this post.

You can use (in Angular) an HttpInterceptor . Something like this:

import { Injectable } from "@angular/core";
import { HttpInterceptor, HttpHandler, HttpEvent } from "@angular/common/http";
import { AuthService } from "../auth/auth.service";
import { HttpRequest } from '@angular/common/http';
import { Observable } from "rxjs";

@Injectable()
export class TokenInterceptor implements HttpInterceptor {
  constructor(public auth: AuthService) { }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    if (this.auth.isLoggedIn()) {
      request = request.clone({
        setHeaders: {
          Authorization: `Bearer ${this.auth.getToken()}`
        }
      });
    }

    return next.handle(request);
  }
}

You could have an Auth service like so:

import { Injectable } from '@angular/core';
import { HttpClient, HttpParams, HttpHeaders } from '@angular/common/http';
import { CookieService } from 'ngx-cookie-service';
import { map } from 'rxjs/operators';
import { environment } from '../../environments/environment';

@Injectable({
  providedIn: 'root'
})
export class AuthService {

  redirectUrl: string;
  // cookie service from ngx-cookie-service
  constructor(private http: HttpClient, private cookieService: CookieService) { }

  checkToken() {
    return this.cookieService.check('token');
  }

  getToken() {
    return this.cookieService.get('token');
  }

  loginWithUsernameAndPassword(userName: string, password: string) {
    return this.http.post<any>(`${environment.API_URL}/auth/login`,
        new HttpParams({fromObject: {userName, password}}),
        {
          headers: new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded')
        }
      ).pipe(map(user => {
        if (user && user.token) {
          this.cookieService.set('token', user.token);
        }
        return user;
      }));
  }

  logout() {
    this.cookieService.delete('token');
  }

  isLoggedIn() {
    return this.cookieService.check('token');
  }

  registerWithUsernameAndPassword(userName, password, email) {
    return this.http.post<any>(`${environment.API_URL}/auth/create`,
      new HttpParams({fromObject: {userName, password, email}}),
      {
        headers: new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded')
      }
    )
      .pipe(map(user => {
        console.log(user);
        return user;
      }));
  }

}

In your AppModule you can then specify a provider called HTTP_INTERCEPTORS and use the HttpInterceptor you created -- in my case, I would call it TokenInterceptor :

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing/app-routing.module';

import { SharedModule } from './shared/shared.module';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { CookieService } from 'ngx-cookie-service';
import { AuthService } from './auth/auth.service';
import { TokenInterceptor } from './interceptors/token.interceptor';

@NgModule({
  imports: [
    BrowserModule,
    AppRoutingModule,
    SharedModule,
    HttpClientModule
  ],
    declarations: [
    AppComponent,
  ],
  exports: [],
  providers: [
    AuthService,
    CookieService,
    {
      provide: HTTP_INTERCEPTORS,
      useClass: TokenInterceptor,
      multi: true
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {
}

A good reference for the interceptor: https://angular.io/api/common/http/HttpInterceptor and: https://medium.com/@ryanchenkie_40935/angular-authentication-using-the-http-client-and-http-interceptors-2f9d1540eb8

And the canonical source on Flask would be Miguel Grinberg, who has written some JWT authentication tutorials -- https://blog.miguelgrinberg.com/post/json-web-tokens-with-public-key-signatures

here's another tutorial for JWT in Flask as well: https://realpython.com/token-based-authentication-with-flask/

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM