Steam authentication with Angular

2020-07-11 06:59发布

问题:

I'm trying to do authentication with Steam from the home page in Angular but whenever I click on button (which has (click) event pointing at login() function in AppComponent), instead of being redirected to Steam page, current page is refreshed and nothing happens.

This is server-side code:

'use strict';

const express = require('express');
const app = express();
const server = require('http').createServer(app);
const io = require('socket.io').listen(server);
const jwt = require('express-jwt');
const cors = require('cors');
const passport = require('passport');
const SteamStrategy = require('passport-steam').Strategy;
const mongoose = require('mongoose');

app.use(cors());
mongoose.connect('mongodb://localhost:27017/database_test');

passport.serializeUser((user, done) => {
    done(null, user);
});

passport.deserializeUser((obj, done) => {
    done(null, obj);
});

passport.use(new SteamStrategy({
        returnURL: 'http://localhost:3000/auth/steam/return',
        realm: 'http://localhost:3000',
        apiKey: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
    }, 
    (identifier, profile, done) => {
        process.nextTick(() => {
            profile.identifier = identifier;
            return done(null, profile);
        });
    }
));

app.use(passport.initialize());
app.use(passport.session());

app.get('/auth/steam',
    passport.authenticate('steam', { failureRedirect: 'http://localhost:4200/home' }),
    (req, res) => {
        res.json({"success": true, "res": res});
    }
);

app.get('/auth/steam/return', 
    passport.authenticate('steam', { failureRedirect: 'http://localhost:4200/home' }),
    (req, res) => {
        res.json({"success": true, "res": res});
    }
);

server.listen(3000, () => {
    console.log('Backend listening on port 3000.');
});

This is AuthenticationService:

import { Injectable } from '@angular/core';
import { Http, Headers, RequestOptions } from '@angular/http';
import { Observable } from 'rxjs';
import { JwtHelper, tokenNotExpired } from 'angular2-jwt';
import 'rxjs/add/operator/map';

@Injectable()
export class AuthenticationService {

  domain = 'http://localhost:3000';

  constructor(private http: Http) { }

  login() {
    return this.http.get(this.domain + '/auth/steam').map(res => res.json());
  }

}

This is AppComponent:

import { Component } from '@angular/core';
import { AuthenticationService } from './authentication.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  providers: [ AuthenticationService ]
})
export class AppComponent {

  constructor(private authService: AuthenticationService) { }

  login(): void {
    this.authService.login().subscribe(data => {
      alert(data);
    });
  }

}

Both servers are running (Angular and Node).

Edit: I added button instead of link and log showed me there is an error with Same-origin policy.

回答1:

Simply said, you can't redirect your Angular service to your backend=>steam=>backend callback url. You have to redirect the user there.

So instead of calling the authService.login(), just add a direct link, like said elsewhere. I don't see the first link in your backend code, but frontend code suggests /auth/steam like in SteamStrategy docs.

So, step 1 is this:

// change the login() method on AuthService
login() {
  window.location.href = "http://localhost:3000/auth/steam";
}

(You'll want to take that URL from environment or some such later on.)

What happens now:

  1. Browser goes to localhost:3000/auth/steam
  2. Node redirects the browser to steam openId
  3. Steam, on successfull login and auth grant, redirects back to http://localhost:3000/auth/steam/return

What happens next?

  • Well, first, save that profile data in Passport strategy to user's profile, or at least to user's session.
  • then, redirect the user back to Angular app. Something like this:

    res.redirect('/public/#/authcallback)`

(Using HashLocationStrategy here so you see clearly where I was redirecting you.)

  • Now, your /authcallback route in Angular should probably hit the endpoint directly once more, to get the auth data, before doing it's own redirects.

Alternatives:

Now, that last part could be done differently. You could do something like redirect from node back to index or to whatever the original route was (you could add those as params etc). Then, have an Angular service, that always checks back with backend if there is auth data.

Or you could have your node backend embed the info in Angular's index.html or whatever page you're using. You know, old-school, server-side inject data in maybe a script tag with something like ejs or jade. It would be faster, but I'd still not trust it blindly on client (or server) and would double-check, maybe in background.



回答2:

What you are doing in your code calls a GET request to your own server. I dont see any page redirection at all.

If you want to redirect the user to an external website you need to send the user there, either :

From HTML file :

<a href="http://example.com">Link to steam</a>

From your component .ts file:

window.location.href = 'http://www.example.com'

If you want to redirect the user to another page on your website, you need to use the Angular Router :

From HTML file :

<a routerLink="/component-one">Component One</a>

From your component .ts file :

this.router.navigate(['/component-one']);

So you component now becomes :

import { Component } from '@angular/core';
import { AuthenticationService } from './authentication.service';
import { Router } from '@angular/router';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  providers: [ AuthenticationService ] // This should be provided by the module, not the component
})
export class AppComponent {

  constructor(private authService: AuthenticationService, private router: Router) { }

  login(): void {
    this.authService.login().subscribe(data => {
      alert(data);
      this.router.navigate('/to-some-page');
    });
  }

}

More examples can be found with a simple Google search, like the one below https://coryrylan.com/blog/introduction-to-angular-routing



回答3:

As far as I can see you are never directing the user to a steam page. Your comment to klong: "Redirection is done through Passport's". This might be, however you are never sending you user to a specific page. You are simply making a GET request form you frontend.

About the Same-origin policy error. I assume this is caused by Passport because you use Cors on your express server. I think the best solution would be to create a simple proxy. If you use the CLI you can use this example.



回答4:

First of all, you do not send your login request to express backend. You must send your login request to steam oidc authorize endpoint. See what Steam says:

When using OpenID, the user begins in a web browser at the third-party website. When the user wishes to login/link their account to that website, using OpenID, the site directs the user to a login form on the Steam Community website. Once the user has entered their Steam login credentials, the user's web browser is automatically redirected back to the 3rd party website with some additional OpenID specific data appended to the return URL. The site's OpenID library can then use this data to verify and obtain the user's SteamID.

It is called Oidc and lucky you there's two good libraries for Angular. This (Recommended) and This.

What your express server need to do is validate the token recieved from your angular client before request. I'm not very familiar with express but, I'm sure there is at least a couple of libraries for you to validate token.

Also you need to do more research on this topic.