비밀번호를 User 데이터모델에서 관리하지 않고 로그인 인증 메일을 통해 로그인을 구현한다.
https://nodemailer.com/about/
nodeMailer는 Node.js 환경에서 email을 사용할 수 있는 모듈이다.
메일을 전송할 수 있고 파일 첨부 가능도 가능하다.
이메일 전송을 위해서 smtp나 web API와 같은 이메일 전송 플랫폼이 필요한데 여기서는 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 : 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 의 파라미터로 전달 할 수도 있다.
http://www.passportjs.org/
http://www.passportjs.org/packages/passport-jwt/
https://github.com/mikenicholson/passport-jwt
jwt를 생성하고 다루는데 필요한 기능들을 간편하게 제공하는 module.
jwt 토큰이나 쿠키에서 정보를 가져와서 사용자 정보에 serialize한다.
이 프로젝트에서는 passport-jwt를 이용해 로그인을 구현한다.
https://www.npmjs.com/package/jsonwebtoken
jwt.sign(payload, secretOrPrivateKey, [options, callback])
const generateToken = (id) => jwt.sign({ id }, process.env.JWT_SECRET);
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함수로 그 정보를 넘겨준다.실제 사용된 코드
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 인증을 위한 미들웨어 생성
// 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) 와 동일한 의미로 사용된다.
context 사용
const server = new GraphQLServer({
schema,
context: ({ request }) => ({ request }),
});
export default {
Mutation: {
requestSecret: async (_, args, {request}) => {
console.log(request.user);
},
},
};
#1 post에도 설명되어 있지만 계속 상기하면서 익혀간다.
# 🗳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;
},
},
};