Instagram-clone#2-Login

Seo·2020년 5월 27일
0

InastagramClone

목록 보기
2/15

Login

비밀번호를 User 데이터모델에서 관리하지 않고 로그인 인증 메일을 통해 로그인을 구현한다.

Email 사용

  1. 랜덤한 형용사 + 명사를 생성하여 loginSecret값으로 이메일로 보낸 후
  2. 로그인 유저는 이메일에서 받은 loginSecret 값을 가져와서 입력하도록 한다.

nodemailer 사용

https://nodemailer.com/about/
nodeMailer는 Node.js 환경에서 email을 사용할 수 있는 모듈이다.
메일을 전송할 수 있고 파일 첨부 가능도 가능하다.
이메일 전송을 위해서 smtp나 web API와 같은 이메일 전송 플랫폼이 필요한데 여기서는 sendgrid를 사용한다.

sendgrid 사용

https://app.sendgrid.com/guide?from=profile&integrate=true
SendGrid는 커스텀 이메일 서비스 사용에 유연한 API와 함께 신뢰할 만한 트랜잭션 이메일 전송, 확장성 및 실시간 분석 등을 제공하는 클라우드 기반 이메일 서비스이다.

  • 고객에게 확인 메일 자동으로 보내기
  • 월간 전자 전단 및 판촉 행사를 고객에게 보내기 위한 분산 목록 관리
  • 차단된 전자 메일, 고객 응답 같은 항목의 실시간 메트릭 수집
  • 경향을 식별하는 데 도움이 되도록 보고서 생성
  • 고객 문의 전달
  • 애플리케이션의 전자 메일 알림

이런 것들이 가능하다고 적혀있다.

사용법

import nodemailer from "nodemailer";
import sgTransport from "nodemailer-sendgrid-transport";

const sendMail = (email) => {
  const options = {
    auth: {
      api_user: process.env.SENDGRID_USERNAME,
      api_key: process.env.SENDGRID_PASSWORD,
    },
  };
  const client = nodemailer.createTransport(sgTransport(options));
  return client.sendMail(email);
};

export const sendSecretMail = (address, secret) => {
  const email = {
    from: "admin@logintest.com",
    to: address,
    subject: "🔒Login Secret 🔒",
    html: `Hello! Your login secret it ${secret}.<br/>Copy paste on the app/website to log in`,
  };
  return sendMail(email);
};

JWT

JWT : JSON Web Token 의 줄인말로 웹표준 (RFC 7519)을 기반으로 두 개체에서 JSON 객체를 사용하여 가볍고 자가수용적인 (self-contained) 방식으로 정보를 안전성 있게 전달해주는 것을 말한다.

  • 수많은 프로그래밍 언어에서 지원
    JWT 는 C, Java, Python, C++, R, C#, PHP, JavaScript, Ruby, Go, Swift 등 대부분의 주류 프로그래밍 언어에서 지원됨.

  • 자가 수용적 (self-contained)
    JWT 는 필요한 모든 정보를 자체적으로 지니고 있다. JWT 시스템에서 발급된 토큰은, 토큰에 대한 기본정보, 전달 할 정보 (로그인시스템에서는 유저 정보) 그리고 토큰이 검증됐다는것을 증명해주는 signature 를 포함하고 있다.

  • 쉽게 전달 될 수 있다.
    JWT 는 자가수용적이므로, 두 개체 사이에서 손쉽게 전달 될 수 있다. 웹서버의 경우 HTTP의 헤더에 넣어서 전달 할 수도 있고, URL 의 파라미터로 전달 할 수도 있다.

passport-jwt

http://www.passportjs.org/
http://www.passportjs.org/packages/passport-jwt/
https://github.com/mikenicholson/passport-jwt

jwt를 생성하고 다루는데 필요한 기능들을 간편하게 제공하는 module.
jwt 토큰이나 쿠키에서 정보를 가져와서 사용자 정보에 serialize한다.

이 프로젝트에서는 passport-jwt를 이용해 로그인을 구현한다.

JWT create : jsonwebtoken

https://www.npmjs.com/package/jsonwebtoken
jwt.sign(payload, secretOrPrivateKey, [options, callback])

const generateToken = (id) => jwt.sign({ id }, process.env.JWT_SECRET);

JWT read : JWTStrategy

var JwtStrategy = require('passport-jwt').Strategy,
    ExtractJwt = require('passport-jwt').ExtractJwt;
var opts = {}
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
opts.secretOrKey = 'secret';
opts.issuer = 'accounts.examplesoft.com';
opts.audience = 'yoursite.net';
passport.use(new JwtStrategy(opts, function(jwt_payload, done) {
    User.findOne({id: jwt_payload.sub}, function(err, user) {
        if (err) {
            return done(err, false);
        }
        if (user) {
            return done(null, user);
        } else {
            return done(null, false);
            // or you could create a new account
        }
    });
}));
  • ExtractJwt.fromAuthHeaderAsBearerToken() : Authorization 헤더에서 jwt를 추출해낸다.
    {Authorization: 'Bearer [TOKEN]'} 형태로 나타난다.
  • secertOrKey: 토큰을 암호화하기 위한 문자열
  • passport.use(): jwtOptions 값이 정상적이면 JwtStrategy 실행되는데 토큰을 입력받아서 정보를 해석하여 callback함수로 그 정보를 넘겨준다.
  • callback:
    error가 있을 시 : done(err, false)
    error는 없으나 user가 없을 때 : done(null, false)
    error도 없고 user를 찾았을 때 : done(null, user);

실제 사용된 코드

const jwtOptions = {
  jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  secretOrKey: process.env.JWT_SECRET,
};

const verifyUser = async (payload, done) => {
  try {
    const user = await prisma.user({ id: payload.id });
    if (user !== null) {
      return done(null, user);
    } else {
      return done(null, false);
    }
  } catch (error) {
    return done(error, false);
  }
};

passport.use(new Strategy(jwtOptions, verifyUser));

JWT Authenticate Middleware : authenticateJwt()

JWT 인증을 위한 미들웨어 생성

// session, cookie로부터 어떤 것도 입력되지 않기를 원하기 때문에 sessions: false한다.
authenticateJwt = (req, res, next) => 
  passport.authenticate("jwt", {sessions: false}, (error, user) => {
    if(user){
      req.user = user;
      // 🚗express router 
      // authenticate를 진행하게 되면 req.user에는 user가 serialize 되기 때문에
      // 그 뒤 요청에는 req.uesr가 존재하게 된다.(로그인됨)
      // 로그인 되어있다면 
      // graphql 함수 실행 시에 req.user 값을 보고 처리할 수 있게 된다.
      // 기본적인 로그인 인증과정, 로그인 관리방법이다.
    }
    next();
  })(req, res, next);
// syntax 가 이상해보일 수 있는데
// passport.authenticate(...) 자체가 함수로 사용된다고 보면 된다.
// 다시 발해 Fn(req, res, next) 와 동일한 의미로 사용된다.

graphql resolver authentication info

context 사용

  • context: resolver 사이에서 정보를 공유할 때 사용함(react context랑 비슷한거 같기도 하고)
const server = new GraphQLServer({
  schema,
  context: ({ request }) => ({ request }),
});
export default {
  Mutation: {
    requestSecret: async (_, args, {request}) => {
      console.log(request.user);
    },
  },
};

remind

jwt auth 흐름

  1. auth middleware create
  2. passport.js setting(jwt옵션 등록, strategy 등록, callback 등록, passport initialize())
  3. passport middleware express에 등록
  4. 런타임에는 express에서 모든 route에 passport middleware를 거치게됨
  5. http header authorization 항목에 "bearer" token을 확인하여 로그인 유저가 인식된다면 req.user에 등록하여서 http req header 항목에 user가 붙게 된다.

.graphql / .js

#1 post에도 설명되어 있지만 계속 상기하면서 익혀간다.

  • .graphql : graphql type/schema 정의 파일
  • .js : graphql resolver 정의 파일

예시 : createAccount

  • createAccount.graphql
  • createAccount.js
# 🗳createAccount.graphql
type Mutation {
  createAccount(
    username: String!
    email: String!
    firstName: String
    lastName: String
    bio: String
  ): User!
}
// 🗳createAccount.js
export default {
  Mutation: {
    createAccount: async (_, args) => {
      const { username, email, firstName = "", lastName = "", bio = "" } = args;
      const user = await prisma.createUser({
        username,
        email,
        firstName,
        lastName,
        bio,
      });
      return user;
    },
  },
};

Reference

JWT : https://velopert.com/2389

profile
개발관심자

0개의 댓글