Auth0使用Graphcool进行通用登录?

时间:2018-03-24 14:20:41

标签: auth0 graphcool

我正在尝试使用Graphcool,Apollo和React应用程序设置Auth0的通用登录。我的网站是一个静态托管Netlify的SPA。

我正在使用Graphcool的官方Auth0模板:https://github.com/graphcool/templates/tree/master/auth/auth0

但是,我在官方文档中找不到太多细节,所以我正在学习本教程:https://medium.com/@quiaro/authentication-inside-a-graphcool-service-using-auth0-5056806d02f0

我有一个由登录组件调用的auth组件:

import React from 'react';
import auth0 from 'auth0-js';

export default class Auth {
    auth0 = new auth0.WebAuth({
        domain: 'MY-SITE.auth0.com',
        clientID: 'MY-CLIENT-ID',
        redirectUri: 'http://localhost:3000/callback',
        audience: 'https://MY-SITE.auth0.com/userinfo',
        responseType: 'id_token',
        scope: 'openid email',
    });

    login() {
        this.auth0.authorize();
    }
}

import Auth from './Auth';
class Login extends React.Component {
    render() {
        const auth = new Auth();
        auth.login();
        return (
            // Login message
        );
    }
}

正如预期的那样,这会将我带到我可以登录的Auth0页面,之后我被重定向回我的网站并将参数添加到网址:

http://localhost:3000/callback#id_token=LONG-ID-TOKEN&state=STRING

如果我将LONG-ID-TOKEN粘贴到https://jwt.io/,则信息似乎是正确的并且表示签名有效:

标题:

{
  "typ": "JWT",
  "alg": "RS256",
  "kid": "STRING"
}

有效载荷:

{
  "email": "test@gmail.com",
  "email_verified": false,
  "iss": "MY-SITE.auth0.com",
  "sub": "auth0|STRING",
  "aud": "MY-CLIENT-ID",
  "iat": INTEGER,
  "exp": INTEGER,
  "at_hash": "STRING",
  "nonce": "STRING"
}

在Graphcool控制台的操场上,我测试了令牌:

mutation {
  authenticateUser(accessToken:"LONG-ID-TOKEN") {
    id
    token
  }
}

但结果是错误。如果我输入一个随机字符串作为标记值,​​我会得到同样的错误。

{
  "data": null,
  "errors": [
    {
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "functionError": "An unexpected error occured",
      "path": [
        "authenticateUser"
      ],
      "code": 5001,
      "message": "function execution error: An unexpected error occured",
      "requestId": "us-west-2:simple:STRING"
    }
  ]
}

从挖掘中我注意到官方模板与教程中使用的模板不同:

教程:

type AuthenticatedUser {
  id: String!
  token: String!
}

extend type Mutation {
  authenticateUser(idToken: String!): AuthenticatedUser!
}

官方:

type AuthenticateUserPayload {
  id: String!
  token: String!
}

extend type Mutation {
  authenticateUser(accessToken: String!): AuthenticateUserPayload!
}

教程:

const jwt = require('jsonwebtoken');
const jwkRsa = require('jwks-rsa');
const fromEvent = require('graphcool-lib').fromEvent;

const verifyToken = token =>
  new Promise((resolve, reject) => {
    // Decode the JWT Token
    const decoded = jwt.decode(token, { complete: true });
    if (!decoded || !decoded.header || !decoded.header.kid) {
      reject('Unable to retrieve key identifier from token');
    }
    if (decoded.header.alg !== 'RS256') {
      reject(
        `Wrong signature algorithm, expected RS256, got ${decoded.header.alg}`
      );
    }
    const jkwsClient = jwkRsa({
      cache: true,
      jwksUri: `https://${process.env.AUTH0_DOMAIN}/.well-known/jwks.json`,
    });

    // Retrieve the JKWS's signing key using the decode token's key identifier (kid)
    jkwsClient.getSigningKey(decoded.header.kid, (err, key) => {
      if (err) return reject(err);

      const signingKey = key.publicKey || key.rsaPublicKey;

      // Validate the token against the JKWS's signing key
      jwt.verify(
        token,
        signingKey,
        {
          algorithms: ['RS256'],
          ignoreExpiration: false,
          issuer: `https://${process.env.AUTH0_DOMAIN}/`,
          audience: `${process.env.AUTH0_CLIENT_ID}`,
        },
        (err, decoded) => {
          if (err) return reject(err);
          resolve(decoded);
        }
      );
    });
  });

//Retrieves the Graphcool user record using the Auth0 user id
const getGraphcoolUser = (auth0UserId, api) =>
  api
    .request(
      `
        query getUser($auth0UserId: String!){
          User(auth0UserId: $auth0UserId){
            id
          }
        }
      `,
      { auth0UserId }
    )
    .then(queryResult => queryResult.User);

//Creates a new User record.
const createGraphCoolUser = (auth0UserId, email, api) =>
  api
    .request(
      `
        mutation createUser($auth0UserId: String!, $email: String) {
          createUser(
            auth0UserId: $auth0UserId
            email: $email
          ){
            id
          }
        }
      `,
      { auth0UserId, email }
    )
    .then(queryResult => queryResult.createUser);

export default async event => {
  if (!process.env.AUTH0_DOMAIN) {
    return { error: 'Missing AUTH0_DOMAIN environment variable' };
  }
  if (!process.env.AUTH0_CLIENT_ID) {
    return { error: 'Missing AUTH0_CLIENT_ID environment variable' };
  }

  try {
    const { idToken } = event.data;
    const graphcool = fromEvent(event);
    const api = graphcool.api('simple/v1');

    const decodedToken = await verifyToken(idToken);
    let graphCoolUser = await getGraphcoolUser(decodedToken.sub, api);

    //If the user doesn't exist, a new record is created.
    if (graphCoolUser === null) {
      graphCoolUser = await createGraphCoolUser(
        decodedToken.sub,
        decodedToken.email,
        api
      );
    }

    // custom exp does not work yet, see https://github.com/graphcool/graphcool-lib/issues/19
    const token = await graphcool.generateNodeToken(
      graphCoolUser.id,
      'User',
      decodedToken.exp
    );

    return { data: { id: graphCoolUser.id, token } };
  } catch (err) {
    return { error: err };
  }
};

官方:

const isomorphicFetch = require('isomorphic-fetch')
const jwt = require('jsonwebtoken')
const jwkRsa = require('jwks-rsa')
const fromEvent = require('graphcool-lib').fromEvent

//Validates the request JWT token
const verifyToken = token =>
  new Promise(resolve => {
    //Decode the JWT Token
    const decoded = jwt.decode(token, { complete: true })
    if (!decoded || !decoded.header || !decoded.header.kid) {
      throw new Error('Unable to retrieve key identifier from token')
    }
    if (decoded.header.alg !== 'RS256') {
      throw new Error(
        `Wrong signature algorithm, expected RS256, got ${decoded.header.alg}`
      )
    }
    const jkwsClient = jwkRsa({
      cache: true,
      jwksUri: `https://${process.env.AUTH0_DOMAIN}/.well-known/jwks.json`
    })
    //Retrieve the JKWS's signing key using the decode token's key identifier (kid)
    jkwsClient.getSigningKey(decoded.header.kid, (err, key) => {
      if (err) throw new Error(err)
      const signingKey = key.publicKey || key.rsaPublicKey
      //If the JWT Token was valid, verify its validity against the JKWS's signing key
      jwt.verify(
        token,
        signingKey,
        {
          algorithms: ['RS256'],
          audience: process.env.AUTH0_API_IDENTIFIER,
          ignoreExpiration: false,
          issuer: `https://${process.env.AUTH0_DOMAIN}/`
        },
        (err, decoded) => {
          if (err) throw new Error(err)
          return resolve(decoded)
        }
      )
    })
  })

//Retrieves the Graphcool user record using the Auth0 user id
const getGraphcoolUser = (auth0UserId, api) =>
  api
    .request(
      `
        query getUser($auth0UserId: String!){
          User(auth0UserId: $auth0UserId){
            id
          }
        }
      `,
      { auth0UserId }
    )
    .then(queryResult => queryResult.User)

//Creates a new User record.
const createGraphCoolUser = (auth0UserId, email, api) =>
  api
    .request(
      `
        mutation createUser($auth0UserId: String!, $email: String) {
          createUser(
            auth0UserId: $auth0UserId
            email: $email
          ){
            id
          }
        }
      `,
      { auth0UserId, email }
    )
    .then(queryResult => queryResult.createUser)

const fetchAuth0Email = accessToken =>
  fetch(
    `https://${process.env.AUTH0_DOMAIN}/userinfo?access_token=${accessToken}`
  )
    .then(response => response.json())
    .then(json => json.email)

export default async event => {
  try {
    if (!process.env.AUTH0_DOMAIN || !process.env.AUTH0_API_IDENTIFIER) {
      throw new Error(
        'Missing AUTH0_DOMAIN or AUTH0_API_IDENTIFIER environment variable'
      )
    }
    const { accessToken } = event.data

    const decodedToken = await verifyToken(accessToken)
    const graphcool = fromEvent(event)
    const api = graphcool.api('simple/v1')

    let graphCoolUser = await getGraphcoolUser(decodedToken.sub, api)
    //If the user doesn't exist, a new record is created.
    if (graphCoolUser === null) {
      // fetch email if scope includes it
      let email = null
      if (decodedToken.scope.includes('email')) {
        email = await fetchAuth0Email(accessToken)
      }
      graphCoolUser = await createGraphCoolUser(decodedToken.sub, email, api)
    }

    // custom exp does not work yet, see https://github.com/graphcool/graphcool-lib/issues/19
    const token = await graphcool.generateNodeToken(
      graphCoolUser.id,
      'User',
      decodedToken.exp
    )

    return { data: { id: graphCoolUser.id, token } }
  } catch (err) {
    console.log(err)
    return { error: 'An unexpected error occured' }
  }
}

我宁愿使用官方模板。该教程采用的方法是否仍然有效?

谢谢!

1 个答案:

答案 0 :(得分:0)

教程中显示的方法(使用id_token访问api)是不安全的,不再建议使用。请参阅本文以了解为什么不应使用id_token访问API https://auth0.com/blog/why-should-use-accesstokens-to-secure-an-api/