35 | )
36 | }
37 | }
38 |
39 | // We need to export a Relay container that wraps around
40 | // the top-level ConferenceApp component
41 | exports.Container = Relay.createContainer(ConferenceApp, {
42 | // We initially want to get the first user's conferences
43 | initialVariables: {
44 | userToShow: 1
45 | },
46 | fragments: {
47 | // Results from this query will be placed on this.props for access in
48 | // our component
49 | user: () => Relay.QL`
50 | fragment on User {
51 | name,
52 | conferences(userToShow: $userToShow) {
53 | edges {
54 | node {
55 | id,
56 | name,
57 | description
58 | },
59 | },
60 | },
61 | }
62 | `,
63 | },
64 | });
65 |
66 | // The queries to be used by the root container
67 | exports.queries = {
68 | name: 'ConferenceQueries',
69 | params: {},
70 | queries: {
71 | // user in this case matches the fragment in the container above
72 | user: () => Relay.QL`query { user }`
73 |
74 | },
75 | }
76 |
77 | // If we want to do authentication on our GraphQL endpoint, we can pass
78 | // a JWT that will be picked up by the express-jwt middleware on the server.
79 | // You first need to fetch and store a JWT in local storage. Once it's in there,
80 | // you can send it as a header when you make calls to GraphQL.
81 |
82 | // How you implement the JWT fetching is at your discretion, but you can use
83 | // Auth0's Lock for an easy solution
84 |
85 | // var token = localStorage.getItem('id_token');
86 |
87 | // Relay.injectNetworkLayer(
88 | // new Relay.DefaultNetworkLayer('http://localhost:3000/graphql', {
89 | // headers: {
90 | // Authorization: 'Bearer ' + token
91 | // }
92 | // })
93 | // );
94 |
95 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2015 Michael Hart (michael.hart.au@gmail.com)
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7 | of the Software, and to permit persons to whom the Software is furnished to do
8 | so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Relay Auth
2 |
3 | This is the code for [Auth0's Relay tutorial](). It shows how to set up a simple Relay app that reads data from a GraphQL endpoint. It also shows how to optionally add authentication middleware to the endpoint and how to send JWTs to it for access.
4 |
5 | This sample is based off of [Michael Hart](https://twitter.com/hichaelmart)'s [simple-relay-starter](https://github.com/mhart/simple-relay-starter) repo.
6 |
7 | ## Installation
8 |
9 | ```bash
10 | npm install
11 | npm run dev
12 | ```
13 |
14 | ## Authentication
15 |
16 | The `/graphql` endpoint is protected with **express-jwt** middlware.
17 |
18 | ```js
19 | // server.js
20 |
21 | var authenticate = jwt({
22 | secret: new Buffer(process.env.AUTH0_CLIENT_SECRET, 'base64'),
23 | audience: process.env.AUTH0_CLIENT_ID
24 | });
25 |
26 |
27 | app.use('/graphql', authenticate, graphqlHttp({schema: schema}));
28 | ```
29 |
30 | You can send a JWT to the server from the front end using Relay's network layer.
31 |
32 | ```js
33 | // ConferenceApp.js
34 |
35 | var token = localStorage.getItem('id_token');
36 |
37 | Relay.injectNetworkLayer(
38 | new Relay.DefaultNetworkLayer('http://localhost:3000/graphql', {
39 | headers: {
40 | Authorization: 'Bearer ' + token
41 | }
42 | })
43 | );
44 | ```
45 |
46 | ## License
47 | MIT
48 |
49 | ## What is Auth0?
50 |
51 | Auth0 helps you to:
52 |
53 | * Add authentication with [multiple authentication sources](https://docs.auth0.com/identityproviders), either social like **Google, Facebook, Microsoft Account, LinkedIn, GitHub, Twitter, Box, Salesforce, amont others**, or enterprise identity systems like **Windows Azure AD, Google Apps, Active Directory, ADFS or any SAML Identity Provider**.
54 | * Add authentication through more traditional **[username/password databases](https://docs.auth0.com/mysql-connection-tutorial)**.
55 | * Add support for **[linking different user accounts](https://docs.auth0.com/link-accounts)** with the same user.
56 | * Support for generating signed [Json Web Tokens](https://docs.auth0.com/jwt) to call your APIs and **flow the user identity** securely.
57 | * Analytics of how, when and where users are logging in.
58 | * Pull data from other sources and add it to the user profile, through [JavaScript rules](https://docs.auth0.com/rules).
59 |
60 | ## Create a Free Auth0 Account
61 |
62 | 1. Go to [Auth0](https://auth0.com) and click Sign Up.
63 | 2. Use Google, GitHub or Microsoft Account to login.
--------------------------------------------------------------------------------
/browser.js:
--------------------------------------------------------------------------------
1 | /* eslint-env es6 */
2 | var React = require('react')
3 | var ReactDOM = require('react-dom')
4 | var Relay = require('react-relay')
5 | var ConferenceApp = require('./ConferenceApp')
6 |
7 | // This file is the entry point on the browser – browserify will compile it, as
8 | // well as App.js and any other client-side dependencies and create
9 | // public/bundle.js which will be requested by public/index.html
10 |
11 | ReactDOM.render(
12 | // At the top of a Relay tree is the root container, which we pass our
13 | // wrapped App component to, as well as the query configuration ("route"). If
14 | // we need to render a different component, say as a result of a navigation
15 | // event, then we would update it here.
16 | // We also illustrate the use of the onReadyStateChange handler in case
17 | // there's a network error, etc
18 | { if (error) console.error(error) }} />,
22 |
23 | document.getElementById('content')
24 | )
25 |
26 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "simple-relay-starter",
3 | "version": "1.2.1",
4 | "description": "A very simple example of React Relay using Browserify",
5 | "main": "server.js",
6 | "repository": "mhart/simple-relay-starter",
7 | "keywords": [
8 | "react",
9 | "reactjs",
10 | "relay",
11 | "browserify",
12 | "graphql"
13 | ],
14 | "author": "Michael Hart ",
15 | "license": "MIT",
16 | "dependencies": {
17 | "body-parser": "^1.14.1",
18 | "dotenv": "^1.2.0",
19 | "express": "^4.13.3",
20 | "express-graphql": "^0.3.0",
21 | "express-jwt": "^3.1.0",
22 | "graphql": "^0.4.4",
23 | "graphql-relay": "^0.3.2",
24 | "react": "^0.14.0-rc1",
25 | "react-dom": "^0.14.0-rc1",
26 | "react-relay": "^0.3.2"
27 | },
28 | "devDependencies": {
29 | "babel-relay-plugin": "^0.2.5",
30 | "babelify": "^6.3.0",
31 | "browserify": "^11.1.0",
32 | "browserify-shim": "^3.8.10",
33 | "nodemon": "^1.5.1",
34 | "onchange": "^2.0.0",
35 | "parallelshell": "^2.0.0"
36 | },
37 | "browserify-shim": {
38 | "react": "global:React",
39 | "react-dom": "global:ReactDOM",
40 | "react-relay": "global:Relay"
41 | },
42 | "browserify": {
43 | "transform": [
44 | [
45 | "babelify",
46 | {
47 | "plugins": [
48 | "./utils/babelRelayPlugin"
49 | ]
50 | }
51 | ],
52 | "browserify-shim"
53 | ]
54 | },
55 | "scripts": {
56 | "start": "node server.js",
57 | "dev": "npm run build && npm run watch",
58 | "build": "npm run build:schema && npm run build:browser",
59 | "build:schema": "node ./utils/updateSchema.js",
60 | "build:browser": "browserify browser.js -o public/bundle.js",
61 | "watch": "parallelshell 'npm run watch:schema' 'npm run watch:browser' 'npm run watch:server'",
62 | "watch:schema": "onchange schema/schema.js -- npm run build:schema",
63 | "watch:browser": "onchange browser.js ConferenceApp.js schema/schema.json -- npm run build:browser",
64 | "watch:server": "nodemon --watch server.js --watch 'schema/*.js' server.js"
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/schema/database.js:
--------------------------------------------------------------------------------
1 | function User(id, name) {
2 | this.id = id.toString();
3 | this.name = name;
4 | }
5 |
6 | function Framework(id, name) {
7 | this.id = id.toString();
8 | this.name = name;
9 | }
10 |
11 | function Conference(id, frameworkId, name, description, attendees) {
12 | this.id = id.toString();
13 | this.framework = frameworkId;
14 | this.name = name;
15 | this.description = description;
16 | this.attendees = attendees;
17 | }
18 |
19 |
20 | var users = [
21 | new User(1, 'Ryan'),
22 | new User(2, 'George')
23 | ];
24 |
25 | var frameworks = [
26 | new Framework(1, 'AngularJS'),
27 | new Framework(2, 'React'),
28 | new Framework(3, 'JavaScript'),
29 | new Framework(4, 'NodeJS')
30 | ];
31 |
32 | var conferences = [
33 | new Conference(1, 1, 'ng-conf', 'The world\'s best Angular conference', [1,2]),
34 | new Conference(2, 2, 'React Rally', 'Conference focusing on Facebook\'s React', [1]),
35 | new Conference(3, 1, 'ng-Vegas', 'Two days jam-packed with Angular goodness with a focus on Angular 2', [2]),
36 | new Conference(4, 3, 'Midwest JS', 'Midwest JS is a premier technology conference focused on the JavaScript ecosystem.', [2]),
37 | new Conference(5, 4, 'NodeConf', 'NodeConf is the longest running community driven conference for the Node community.', [1,2])
38 | ];
39 |
40 | module.exports = {
41 | User: User,
42 | Framework: Framework,
43 | Conference: Conference,
44 |
45 | getUser: function(id) {
46 | return users.filter(function(user) {
47 | return user.id == id
48 | })[0]
49 | },
50 |
51 | getConference: function(id) {
52 | return conferences.filter(function(conference) {
53 | return conference.id == id
54 | })[0]
55 | },
56 |
57 | getUsers: function() {
58 | return users[0]
59 | },
60 |
61 | getConferencesByUser: function(userId) {
62 | var confs = [];
63 | conferences.forEach(function(conf) {
64 | conf.attendees.forEach(function(user) {
65 | if (user == userId) {
66 | confs.push(conf);
67 | }
68 | });
69 | });
70 | return confs;
71 | }
72 | }
73 |
74 |
--------------------------------------------------------------------------------
/schema/schema.js:
--------------------------------------------------------------------------------
1 | var GraphQL = require('graphql')
2 | var GraphQLRelay = require('graphql-relay')
3 | var db = require('./database')
4 |
5 | // The schema describes the types and queries for our data and
6 | // is the spot to register them
7 |
8 | // We need to set up our node definitions to provide a node interface.
9 | // Relay uses global ids for entities
10 |
11 | var nodeDefinitions = GraphQLRelay.nodeDefinitions(function(globalId) {
12 | var idInfo = GraphQLRelay.fromGlobalId(globalId)
13 | if (idInfo.type == 'User') {
14 | return db.getUser(idInfo.id)
15 | } else if(idInfo == 'Conference') {
16 | return db.getConference(idInfo.id)
17 | }
18 | return null;
19 | });
20 |
21 | var userType = new GraphQL.GraphQLObjectType({
22 | name: 'User',
23 | description: 'A person who uses our app',
24 | isTypeOf: function(obj) { return obj instanceof db.User },
25 |
26 | fields: function() {
27 | return {
28 | id: GraphQLRelay.globalIdField('User'),
29 | name: {
30 | type: GraphQL.GraphQLString,
31 | description: 'The name of the user',
32 | },
33 |
34 | // We can set up a relationship between users and conferences here
35 | conferences: {
36 | description: 'A listing of the user\'s conferences',
37 |
38 | // Relay gives us helper functions to define the Connection and its args
39 | type: GraphQLRelay.connectionDefinitions({name: 'Conference', nodeType: conferenceType}).connectionType,
40 | args: {
41 | // argument to tell GraphQL which user to pass back
42 | // in the resolve block
43 | userToShow: {type: GraphQL.GraphQLInt}
44 | },
45 |
46 | // The resolve block will complete a query and pass back
47 | // data for the user id supplied by the arguments we pass in
48 | resolve: function(user, args) {
49 |
50 | return GraphQLRelay.connectionFromArray(db.getConferencesByUser(args.userToShow), args)
51 | },
52 | }
53 | }
54 | },
55 | interfaces: [nodeDefinitions.nodeInterface],
56 | });
57 |
58 | var conferenceType = new GraphQL.GraphQLObjectType({
59 | name: 'Conference',
60 | description: 'A conference',
61 |
62 | // Relay will use this function to determine if an object in your system is
63 | // of a particular GraphQL type
64 | isTypeOf: function(obj) { return obj instanceof db.Conference },
65 |
66 | fields: {
67 | id: GraphQLRelay.globalIdField('Conference'),
68 | name: {
69 | type: GraphQL.GraphQLString,
70 | description: 'The name of the conference',
71 | },
72 | description: {
73 | type: GraphQL.GraphQLString,
74 | description: 'The description of the conference'
75 | }
76 | },
77 | // This declares this GraphQL type as a Node
78 | interfaces: [nodeDefinitions.nodeInterface],
79 | });
80 |
81 | var frameworkType = new GraphQL.GraphQLObjectType({
82 | name: 'Framework',
83 | description: 'A popular programming framework',
84 | isTypeOf: function(obj) { return obj instanceof db.Framework },
85 |
86 | fields: function() {
87 | return {
88 | id: GraphQLRelay.globalIdField('Framework'),
89 | name: {
90 | type: GraphQL.GraphQLString,
91 | description: 'The name of the framework'
92 | },
93 | }
94 | },
95 | });
96 |
97 | // Types and queries are exported with GraphQLSchema
98 | module.exports = new GraphQL.GraphQLSchema({
99 | query: new GraphQL.GraphQLObjectType({
100 | name: 'Query',
101 | fields: {
102 | // Relay needs this to query Nodes using global IDs
103 | node: nodeDefinitions.nodeField,
104 | // Root queries
105 | user: {
106 | type: userType,
107 | resolve: function() {
108 | return db.getUser(1);
109 | },
110 | }
111 | },
112 | }),
113 | });
114 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var graphqlHttp = require('express-graphql');
3 | var schema = require('./schema/schema');
4 | var jwt = require('express-jwt');
5 | var dotenv = require('dotenv');
6 |
7 | // The server is just a simple Express app
8 | var app = express()
9 |
10 | dotenv.load();
11 |
12 | var authenticate = jwt({
13 | secret: new Buffer(process.env.AUTH0_CLIENT_SECRET, 'base64'),
14 | audience: process.env.AUTH0_CLIENT_ID
15 | });
16 |
17 |
18 | // We respond to all GraphQL requests from `/graphql` using the
19 | // `express-graphql` middleware, which we pass our schema to.
20 | app.use('/graphql', graphqlHttp({schema: schema}));
21 |
22 | // If we want to protect our GraphQL route with authentication, we
23 | // can pass in the authenticate middleware
24 |
25 | // app.use('/graphql', authenticate, graphqlHttp({schema: schema}));
26 |
27 | // The rest of the routes are just for serving static files
28 | app.use('/relay', express.static('./node_modules/react-relay/dist'))
29 | app.use('/', express.static('./public'))
30 |
31 | app.listen(3000, function() { console.log('Listening on 3000...') })
32 |
--------------------------------------------------------------------------------
/utils/babelRelayPlugin.js:
--------------------------------------------------------------------------------
1 | module.exports = require('babel-relay-plugin')(require('../schema/schema.json').data)
2 |
--------------------------------------------------------------------------------
/utils/updateSchema.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs')
2 | var path = require('path')
3 | var graphql = require('graphql').graphql
4 | var introspectionQuery = require('graphql/utilities').introspectionQuery
5 | var schema = require('../schema/schema')
6 |
7 | graphql(schema, introspectionQuery).then(function(result) {
8 | if (result.errors) return console.error(result.errors)
9 | fs.writeFileSync(path.join(__dirname, '../schema/schema.json'), JSON.stringify(result, null, 2))
10 | })
11 |
--------------------------------------------------------------------------------