User Auth & Podcast Subscribe
Building a GRANDstack Podcast App: Episode 2
In Episode 2 we implement user sign up, login, and subscribe to podcast functionality in our GraphQL API.
Links And Resources#
- Code on Github
- Podcast Index API
- GRANDstack docs: Cypher parameters from context
brcypt
npm packagejsonwebtoken
npm package- JWT debugger
- Random key generator
Hey folks welcome to the neo4j stream. My name is: will uh today we are picking up with episode two of building grand cast fm, which is our podcast application built using grand stack, we're going to pick up from where we left off last time. Let me link the code if you missed the last one. Is this it yeah this? Is it so i'll drop this link in the chat? So last time we started actually, let's, let's just look at this in blog post form - maybe that's a good way to review what we did last time um, so the the recording from the live stream and then also sort of a blog post write up is linked. There in the github readme, as well as the code, but basically what we did last time is. We started out with a graph data, modeling exercise where we said. Okay, we have sort of the the business requirements of our application. We know we want it to be a podcast application, so i want the user to be able to do things like search for podcasts subscribe to podcasts, create a playlist, add episodes to a playlist, this sort of thing, so we went through this graph data modeling exercise That i like to do whenever i start off an application start from the business requirements and then go through this process. Uh. Okay, what are the entities these become the nodes? How are those entities connected those become the relationships? What attributes describe these entities? Those are our node properties. What attributes do we have that are specific about how those nodes are connected? Those are relationship, properties and then sort of we use this arrows tool which i like to use, which is this fairly simple, online diagramming tool that lets us create these graph models and then the question we ask ourselves is: okay. Can i imagine a traversal through this graph that answers the questions that i have that drive the business requirements of the application? Like? Can i imagine uh with this model, how i would show a user all of the episodes in a playlist that they subscribe to uh once i start to sort of think about those traversals, if i can traverse through the graph to answer those questions? Okay, good! That's a good model to to start my implementation with. If not, then i might want to iterate the model a little bit so graph modeling, i think of as a bit of an iterative process and and that's okay. So once we came up with our graph data model last time, the next thing we did was implement podcast, search and there's lots of different ways to do this. We we could have gone out and start to build our own sort of podcast index and build search on top of that, but there are a few services that provide that the the biggest one is probably apple itunes, but there's also, this api called the podcast index That also maintains an index of podcasts and has an api that allows us to search for episodes. So we looked at that endpoint. We wrote a cipher query using a bunch of apoc procedures and functions so, namely apoc load, json params, which lets us pull down. Json data from like a file or another api endpoint, it lets us attach headers, so we need to attach authorization headers and we use some other apoc functions to be able to construct and work with that result. All within cipher we've stored our podcast, our podcast index api credentials in a configuration file. Then we pull those in using apoc static get. So we don't have to save our podcast credentials in in our cipher query. So we did that in cipher, which is kind of fun to query another api using cipher. Then we created our graphql api, so we used the create grand stack command line tool to give us the skeleton of our api application. We we chose just api, only the create grand stack, app cli can also provision front-ends for us as well using react. Flutter angular there's also a view implementation, but we just are focused on the api for now and then we took that cipher query for querying the podcast index and we attached that into our graphql schema using a cipher directive, we're going to do a bit more with Cipher directives today these are a really powerful way to add custom logic to our graphql api using cipher. So we'll see some examples of that today and then this gave us a graphql api that was then able to search for podcasts and if we query that we got back, podcast results uh, and that was it that's where we left it off last time. So, let's, first of all fire up our graphql api, local server, running 4001 graphql - and here we go. We can see, we have one entry point: query field called podcast search, that's going to give us a list of podcast search, result objects. We search for neo4j. We find the neo4j podcast. We can also bring in things like the feed, url and artwork search for other terms, search for connected yep. We get the connected, podcast cool, so that's where we left off last time. What i want to focus on this time is now allowing users to be able to subscribe to podcasts right, so we've got search. I can search for podcasts uh subscribing to a podcast is the next sort of logical thing that a user will want to do so. We'Ll implement that feature next in order to enable a user to subscribe to a podcast, though we need to have the ability for users to sign up and also to log in to our application and because we're just focusing on the api at this point, we'll we'll Still just work on the graphql api and implement these features in the graphql api. We we won't start the front end today. Maybe we'll do that that next time or maybe down the road a bit but we'll get there eventually. So we need to have the concept of a user, a user needs to be able to sign up to log in and then make authenticated requests to subscribe, because i want, i want to make sure that a user is only able to subscribe themselves right. I don't want a user to be able to subscribe some arbitrary other user to a podcast and then, when i view my list of subscribed podcasts, i only want to show the podcasts for the currently authenticated user cool. So that's what we're going to focus on today. [, Music ]: let's talk a little bit about how we're going to do that. So previously we used auth0 as our authentication service and also took care of our user database. Essentially, and when i say previously, this was in the grand the willow grand stack applications. This was our real estate search application. So if we look at, if you want to see sort of how we did this there's a couple of the recordings linked here, so taking a look at exploring authorization and then adding graphql authorization with os0. These are the recordings from the live stream for this project and we used auth0 in a few places. We used it in the front end application, so there's a great react library for auth0 that handles the the auth flow, the storing of the token uh refreshing. A token from the server when it expires all these sorts of things, so there's benefits from the front end, but also on the back end. A service like auth0 is nice, because we then don't need to worry about how to handle user passwords. If we're doing something like an oauth flow, so sign in with twitter or google, or something like that, auth00 has implemented that flow for us and then auth0 also maintains our database of users. So we don't need to really think about maintaining that and what we get from oth0 after user signs in is an authorization token, typically a jwt json web token. So i like to think of auth0 as like a jwt provider almost because really that's what i'm most interested in um is getting that token and then decoding the token to figure out okay, who is this user? What permissions do they have and so on so a question from the chat? How can i create a database in neo4j? I downloaded neo4j launched it via command line, opened the web interface in a browser, but i can't understand where to create the database, so by default. Neo4J, so if we open up browser, i have a database running at 7474 is browser, so this is uh. I think the web interface that you're referring to this is neo4j browser, which is like a query workbench for working with neo4j. So by default here you can see in this in this drop down. You have multiple databases to choose from the system database. This is one for like maintaining um lists of databases and so on. So by default you have the neo4j database. So if you want to start using neo4j, there's a default database created if you switch to system, you can do things in browser like colon dbs. That will show you the list of available databases, colon use, neo4j, and then i can do things like create database foobar. Make this a bit bigger so create database in the name of the database. Now in the drop-down i have three different databases to choose from, so i can choose which database i want to work with in the drop-down to switch back and forth, or i can do this colon use command. So now i've got a database. If i create a node here, i can create a node for myself. So here i've created this node in the fubar database. If i switch back to neo4j, that database is empty, so that is how you can create databases if you're just getting started, though you can just use the the default neo4j database. The developer guide might have some more information. This might be a good place to start to take a look at the graph platform database. Might this might be a good place to start um if you're working with uft browser? This also might be a good place to start so i'll link both of these pages. In the chat that might be helpful, you can also use neo4j sandbox, which is my almost like default way to start with a database these days, because i can just have it created and hosted for me for free and build applications against dent. I don't have to think about how to deploy my database somewhere cool hope. That'S helpful, uh uday in the chat. Hey you day, how's it going uh. He says i have auth0 implemented, but i'm looking for an alternative as it is cost prohibitive. Any recommendation yeah so um. This should be an interesting one and and relevant for you today, because we're not going to use otzero today. So i was talking about how previously we used oth0 - and it was kind of this like token provider for us that handles authorization and it handles our maintaining our user database and just gives us this token. So we don't have to think about a user has to reset their password. A user wants to sign in with a different service. This kind of thing all that's handled by auth0, which is quite nice but yeah. There are reasons why you might not want to use something like auth0. You may want to implement this functionality yourself, and so that's what we're going to do today, so we're going to add the ability for our users to to sign up to register and for our users to log in without using auth0. Instead, we are going to maintain uh our database, our user database ourselves, so we're going to store user information in our database along with our podcast information. That means we're going to have to work a little bit with user passwords and generate our own authorization tokens that, after a user logs in we can then hand the front end application so that their requests to the api will will use that token and then be Authenticated so we're going to use a couple of libraries to do that. Let'S take a look at a couple: um first, one we're going to look at is be crypt, so bcrypt is an algorithm. This is a library that implements the bcrypt. Hashing algorithm has a handy wikipedia page, like i'm sure it goes into a lot of detail. Bcrypt will hash. This is a one-way hash of a user's password. So when a user signs up we're going to ask them for their username and for a password, but we don't want to store that password in plain text in the database. If someone hacks our database, then they have everyone's password. That'S no good! So what we want to do is create a one-way hash of it, so that then, when a user goes to sign in, we take the password that they're trying to sign in with we hash it and compare it to the hashed password in the database. If it matches, then we let them log in so that's one library we'll be using. We will also be using the json web token package and jason webb token package will. Let us do two things that we're interested in one to create a token. So once a user has signed in, we then need a way to sort of cryptographically sign, um, that this user has this user id, and this username right. It'S basically like a way to cryptographically sign that. Yes, this is the user who they say they are and allow that user to make authenticated requests against our api. So we'll use this json web token package to create our token and then also when we have requests coming in to decode that token and make sure that one, the token was signed by our application and then also to inspector called the claims of the token. To figure out who the user is essentially, if you're not familiar with json web tokens, there's a great site, jwt dot io that has this fun little debugger. So here it generates this token for us. So here's here's the token and here's the decoded form. Of that token, so a token has three parts: when it's decoded it has the header, the payload and then the signature. The header just says. Basically what algorithm is was used to sign it. It'S the the payload data that we're interested in um in our application, uh. So these are sort of the the default elements of a token. These are called claims uh also in in the payload and a json web token is basically just a json object. That is then encrypted into this token, so we're just encoding this json data. In this token, and then we can pass around that token to essentially pass along this information, so we can add something like this user's name. You can see how the the token is changing. Maybe also this user has a username bobby tables, and then we can take this token and if i i'll just open up another window of this, but this is what we're going to do in our api application. We get this token. We decode it. Oh yep: here's, the username and user id i'll go look up this user in my database, so that is what we're going to use for our authorization token, these jwts json web tokens. Okay, let's go ahead and um get started then so here is the code and by the way, feel free. If you have any questions or or thoughts feel free to to drop them in the chat as we go along. So the first thing i'm going to do is go over to my graphql schema. Let'S leave the terminal up, let's just minimize that there we go so here's the graphql schema that we created last time. Let'S give ourselves a little more screen real estate there. So we have our query type that has podcast search. It has a query field. The logic for that is defined with the cipher query: that's using apoc to search the podcast index and it's returning podcast search results cool. So, let's start off by creating a mutation type, and i want two things on this mutation type to fields. The first is going to be sign up, and this will take a username and a password. I guess we should make these required strings since the user can't sign up without giving us a username and password, and this is going to return. Let'S call it an auth token, so we need to define what this auth token type is. Let'S say it just has a token field: that's a string seems like a good place to start uh. So that's for sign up and then also we'll have login that takes a username and password and returns an auth token um. Okay, i think that's all we want to change in our type definitions for now, let's jump over to index.js and let's get our terminal back. So i need to install some packages here. So, let's npm install json web token. That'S this package that we were just looking at and then also install b, crypt, which we'll use for hashing passwords and then we're going to import jwt from jason, webb token and then from bcrypt. There are two functions: we're gon na pull in here um. If we go back, there's a compare, so compare is sync or async. I think we want the synchronous version that that'll work a bit nicer. I think in our resolvers. So compare sync, so this is one when a user is logging in, and we want to compare the password that they've given us with the hashed password that we've stored in the database and then in order to create the hash password in the first place, we'll import. The hash sync or the synchronous version of the hash function. Okay, so now what we're going to need to do are implement the resolvers, so we haven't implemented any resolvers yet and if you've, if you've seen us, use the nifty, graphql js library, we we oftentimes, skip that step because we get a lot of those resolvers generated For us as part of the neo4j graphql js library, but we're going to have to resolve, write some resolvers today because we want to be able to deal with hashing passwords, generating auth tokens and so on. That'S fine! So let's start with our signup resolver. So every resolver is passed four arguments the object. This is the object, that's currently being resolved. That has been resolved up to this point, so resolvers are typically called in this nested fashion. This is the root resolver. So this first argument object is always going to be an empty object. Then we have args. So these are the field arguments in this case for the sign up field, it's going to be username and password and then the context object. This is passed to every resolver and has things like connections to the database and so on, and then this resolve info object. This has a lot of information about the graphql, schema, the query and ast abstract syntax tree representation of the query and so on. We'Re not going to do much with that today, but it is there okay. So the first thing we want to do is hash the password. Let'S just change this in place, we'll say: args dot password is going to be hashed version of the password uh and then the second parameter here is the number of salt iterations. I think it is uh hash, hash. Think salt. If subsided as a number, then assault will be generated, okay, cool and then the number of specified rounds used cool, so assault. That'S like a thing that's added to the password when it gets hashed for added security. So someone can't just do like a dictionary uh attack where they hash a whole bunch of passwords and then try to log in or compare them with the hash password in the database, because they would also have to generate the salt that gets hashed as well. Okay cool so now we're going to uh create this user in the database. So let's create a session and we're going to execute this query so session dot run and our query is going to create a user and set that user node equal to the arguments that we're passing and then we'll also generate a id for the user. So cipher has this random uuid function, so that will generate a user id and then we'll return, the user. From the cipher query. We need to pass the args object as parameters for the cipher query. That'Ll give us a promise which we can then uh close the session. We always want to make sure that we close the database session so that frees up connections to the database, and then we want the id and the username because we're going to need to return this from this mutation. So that's going to be response. Dot records, which gives us an array of results and we're just gon na, have one so we'll take the first one and we're interested in the? U column of this query. So that's you, the user we've returned here and then the javascript driver result objects. We can call dot properties on those to give us just a javascript object, representation of those properties and then we're just grabbing the id and username out of that object, and then we're ready to return an object. That has a key token remember. This is now this is returning to this mutation. This is the signup mutation that we're writing and we're returning an auth token object. That has only one field, just a token field. For now i don't know we might, we might add something to the auth token type. Um, but for now it just has a token field, and so here here we need to generate the jwt, so we'll say jwt dot sign. What do we want to sign? So this is the payload or the claims of our token. So in this case, it's going to be the user id and the username, and here we need a secret. So this is the secret that we're using to sign this token with uh by default, we're using the hs256 uh. This is bcrypt. We want jason webb token sign right, so we need the uh, the secret or the private key. I think, by default yeah by default, we're using the hs256 algorithm, which is uses a symmetric key to sign it and to decode it. If we're using something like the rsa algorithm, which is a bit more secure, we have a public and a private key. We sign it with the private key decode it with the public key in this case. Let'S keep things simple for us: we'll use the default algorithm, so we need to generate a 256 bit random string, we'll just use this random key gin that generates a bunch of random strings for us, uh, 256-bit key sure that looks good. Okay, i'm going to save this. Let'S save that file! I'M going to save this in our env file, where we have things like the connection string to the database and and so on. I'M going to create a jwt secret environment variable and store that there and then up here are we. Do we read in any environment variables? Do we? Oh we just. We just call process.e and b inline yeah. Okay, that's fine! That works here. We go okay, so we're signing our token um. We need the key process, env jwt secret, so that's the secret we're signing it with and then we need to pass some. Let'S look at the docs here, some configuration, so we can override the algorithm um, but we need an expiration. Let'S say, expires in how about 30 days. So this token is valid for 30 days. Um. Okay, is this gon na work? I think i think so. Let'S fire up our api: here's our api make sure we can still search for podcasts search for neo4j yep, there's our neo4j podcast cool. So now, if we look at the docs tab for our api, let's refresh this, maybe so yeah we needed to refresh graphql playground. So it picked up our new endpoint here, sign up and login note that it also created an auth token query field and create delete mutations as well. That'S part of by default, neo4j, graphql js! That'S this library. By default, we'll create [ Music ], create read, update, delete operations for any type that we declare in the schema. So we want to make sure down here where we're excluding types from the query and mutation augmentation process. Let'S exclude auth token, we don't want to generate oops, it should be an array of strings, not just one big string, so we restart our server refresh playgrounds, cool and those go away, and now we have sign up and login. So let's give this a try. We need to say mutation since by default. The operation type is query, but this is a mutation operation. So we're going to sign up username i'll make this a bit bigger. Maybe that's too big, uh username, how about johnny montana and our password is going to be. Let me in, and then this returns an auth token that has a single field token, so we'll use that for the selection set. So now, when i run this, this should do a couple of things. It should create a user node in the database with the username johnnymontana. It should hash the password that we've provided and save that in the database as well. The hashed version of the password, not the plain text version. It should generate a random uuid string for the user and it should create a json web token and return that token in the graphql operation. So, let's see, if that happens, no it says, do not know how to handle this type of mutation. Mutation does not follow. Naming convention: well, what did we do wrong here? Oh right, so we created this resolver, this resolvers map and then here we're creating our graphql executable schema object with the help of make augmented schema. So this is a function from neo4j graphql js up here, make augmented schema and make augmented schema, takes type definitions and graphql type definition. So that's uh, that's these type definitions and then generates resolvers automatically for us. So that's why we didn't pass resolvers before, but now we have resolvers that we've written manually, so we need to pass those as well to make augmented schema. So it didn't. It didn't know that we had written this resolver function because we weren't passing it to make augmented schema to attach to our schema object that we are then down here serving with apollo server. Okay. So, let's see if that made a difference, cool okay, so i get back a token. Let'S copy this token and take this over to jwtio, and if we paste this in, we have two things that we're interested in in the claims and the payload. We have an id for the user and the username of the user. Okay, so that that looks good. The token looks correct. The other thing we want to check is in neo4j. If we created this user node - and it looks like we did so - here's the uuid that was generated for this user - here's, the hashed password and the username change the caption here to username. So we see who this is and make it a bit bigger cool. So we can see the username okay, that's good, so we can now sign up in the graphql api. The next thing that we need to do is implement the login mutation. So so a user has signed up uh, we get the token they can start making authenticated requests to the api. Let'S do the case where they come back a week later whatever and they want to log in so the user already exists. How do we? How do we log in so let's go back to our resolvers here, so we've implemented the sign up: resolver [, Music ]. Now we're going to implement the login resolver, so a question in the chat, [ Music ] so related to our earlier discussion about creating databases. When i try creating a database. Have the message: uh error statement, not system database error yeah. So you want to make sure that you're using the system database so in the browser in fj browser you want to be connected to the system database when you're doing operations like creating databases so first either either in this drop down, make sure you've selected system and Then run the command create database, my database, i think that might be your error if you're using like the neo4j default database and you say, create database mydb2 that works too as well. What was the area you're? Getting uh not system database error, oh you're, trying to create database is that a a default um or a a bad name? Can you not have underscores in the database? Names you're saying, create database excursion agency yeah. So i think that's the that's the problem that you're having is you can't have underscores in your database name, so you might want to call it something like this excursion agency and then i can say, use excursion agency yeah. So try that try just removing the underscore from your database name but yeah. So by the error message i was. I was thinking that your air had something to do with not being connected to the system database when you make that request. But it looks like you: don't need to be anyway, cool, so try that, and let us know if that works okay, so we want to implement our login resolver. So this login resolver is passed the same arguments and in this case we're going to query the database right away. So let's create a session. So we reference the context, object and we know there's a driver instance in that context. Object. So if we look here we're creating the neo4j driver instance, this is just a drive neo4j javascript driver that is connected to the database, we're using and then here when we call apollo server. We construct the context object and we are inserting that driver instance. So we can take advantage of that in our resolver to create a connection to the database. Okay and then we're going to run our query and in this case we're going to match on the user. Let'S look them up by username and make sure just return. One user object and then username cipherparameter is going to be the username from the graphql field. Arguments so remember, our field arguments are username and password. Okay, that gives us a promise, which is past the response close our session and we have the user object. So we are interested in the id, the username and the password from the result of our query. So result records gives us an array. Uh, there's only one record that we're returning in the u column again, we'll call dot properties to give us a javascript object. Okay, so now what we're going to do so this, where it says password this is the hashed password that we stored in the database. So what we want to do is oops if compare, if not compare, sync args.password and the password so compare sync. This is a function from bcrypt that will compare our hashed password to [ Music ], the password that the user has passed so args.password. This is coming from the args object, so the field arguments on the login graphql field with password, which is the hashed password. That is stored in the database, so we're going to compare those two um. If they don't match, then i guess: let's just throw an error. We can talk about error handling later but, let's just say, authorization error. Okay. Now, if we're down here, that means that those passwords did match and now we want to generate a token. So what are we going to return we're going to return an object that has single field? Remember returning auth token right, so login mutation field returns an auth token type. It has a single field token, so we're going to use jwt dot sign to take the payload, which is the id and username for the user who just successfully logged in. We want the jwt secret to use to sign that and then we will [ Music. ] say this expires in 30 days. Okay, should we try that now? I think that should work. So here's, let's comment out our signup mutation and now we'll do a mutation to log in username, johnnymontana and our password was. Let me in let's try, let's put in the wrong password first and see what happens so. This should throw an authorization error. Can it read property of undefined hmm that doesn't sound like what we want? So i think what happened here is it got through the query and it got down here and tried to look up a user and couldn't find anything so we we should wrap this with some some proper error handling. Um. I see, i think the reason is we were looking at a property called user on the user node instead of user name, we'll talk about better error handling later, let's just get this working first of all, so, okay! So now so i'm now i'm trying to log in again with a bad password and still says, cannot get property get of undefined. So it didn't find anything here see. Let'S look at our database. Here'S our user node id password. I do have username johnnymontana match user username [, Music ]. So if i run this query and i say match where the username is johnny montana get my user okay, this is on line 65. So this is. This is before we get to the password comparison. So it's not because i'm not passing the right password. I know that that's fine did we not restart our server after i made that change. I think i did let's, let's try again same air, oh yeah. I do thanks for thanks for pointing that out. I do have a typo some reason. I couldn't see that j-o-h-n-y montana there we go thanks good to have some other folks uh. Looking at it with me, that's helpful, okay cool! So now now i get authorization error because i have a bogus password. If i change the password, i get my token cool and if i take the token we copy this and go to jwtio again, you can see oops. So it's not encoded correctly copied extra, a couple extra characters. There we go uh, it says: yep, here's, the payload! Okay cool so now i've got the ability to sign in to create users, and i can log in to uh to my api and get a token that's good um. The next thing i want to do is now start making authenticated requests to the api, so [ Music ] - let's add, let's add a query field oops - that collapse didn't work too well just created some new lines for us. That'S! Okay! Let'S add a query field here. Let'S just call this me, so this is just gon na give us the authenticated user and first we need a. We haven't added a user type. So, let's add the user type, so a user is going to have a username and an id there's also a password property. But i don't think we want to expose that and we also don't want to add the generated, query and mutation fields for this. For this user type, so, let's add user to our list of types that we are excluding from the schema augmentation process. Okay. So what is this statement statement spelling having a hard time, spelling today, uh okay? So what is this? What is this statement going to be? That is the cipher directive on this me. Maybe, instead of me, let's call it current user to make it more clear what this is. So what i want to do here is find the currently authenticated user and return that that user's username and id essentially that object well, let's take a look at the docs um. What i want to look at here, let's make this a little bigger. I want the docs uh this in the guides, perhaps yeah adding custom logic, so the cipher schema directive so far. We can use this to create uh computed fields uh. We also did this for top level. Query fields right. So that's what um our podcast search is here, and we said that the field arguments are passed into this cipher query that is attached to the graphql field as cipher parameters. So here this is like a fuzzy fuzzy text, search where we're passing in some search string. As a field argument on the graphql field and the search string is then injected into this cipher statement as a cipher parameter. So that's that's how cipher parameters are injected into those cipher directive statements? But if we take a look at the authorization middleware - and i specifically am interested in cipher parameters from context so another another way that we can inject values into the cipher statements in the cipher directives is in the context object. So we saw just a second ago how we create the context object here when we call apollo server in this case we're injecting a driver instance. But another thing we can do is treat this as kind of like middleware and when a request comes in, we can look at the headers in the request, which is typically where a authorization token that's a typical pattern is to add the authorization token. As a bearer token, so as a header in our request, we can inspect those headers grab. That token add it into the context object and then that will be available in the cipher query that we're attaching to this graphql field here. So when we make the request in playgrounds, what we'll do let's comment out this one oops so we'll say something like current user username and id and then in the headers we'll add an authorization header as a bearer token. So the convention here is to say bearer, and then we paste our token. So then, when the request comes in what i want to do in the context object. So here we're just we're returning an object, but this can also be a function. So this function is past the request object and then we can return some things, but first so, like return driver and the name of the database that we want to use. But what i want to do now is okay, so there's this this authorization token attached to the request, headers. What i want to do is grab that token and then decode the token and grab the claims out of it. So i want to grab the user id out of that, so that the user id is then passed as a parameter into this cipher query, and i can use that in my cipher query, to look up the user if that makes sense. So in this context function now uh i'm going to get the token. So it's going to be the request: dot, headers, dot authorization. So that's going to give me this piece, but i need to strip out bearer. So is that b e, a r e r space? So i need to strip out seven characters and then i'm not always. I still want to be able to make unauthenticated requests right. So i don't want to throw an error if i don't have an authorization header. So let's add the optional. Was this called optional chaining? I think so check to make sure that we have a headers object and an authorization object before we try to it. Okay, so now token should be the actual jwt token, and now what i want to do is get the user id. So i'm going to create a user id variable and say: if we have a token, then let's decode it so jwt dot verify we look at our docs for json web token, verify takes the token and our secret and if a callback is not supplied and acts, Asynchronously and returns the payload decoded. If the signature is valid, if it's not valid, then it throws an error, okay, cool, so we can try to decode it with the token and our secret is process, emv, jwt secret and then we'll say the user id is decoded dot id and then this is The object i want to return, let's format that and i want to return the driver and then in the in the docs for grand stack where's that here it says anything in the cipher params object is injected into our cipher query as a cipher parameter called cipher. Params, so i want cipher params all right, i'm passing in. Let'S make it. Let'S do camel case here, i think to be consistent. Okay, does that make sense? So if we have an authorization token in the headers, then we'll decode that if it happens to be a bogus token like if it's not actually signed by our application, then we'll throw an error. That'S good! That'S what we want! We don't want someone trying to send us a bogus token, we'll decode it we'll grab the user id out of the token payload and now, in our context, object we'll add the cipher params object that has the user id from the token. So now, if we go back to our graphql schema, we should be able to say match user by id where the user id is cipher, params, dot, user id and return the user node. So let's refresh playground so that it picks up our schema change. So yep cool we've got current user, that's going to give us user objects and we added the user type to the list of types that we're excluding from the schema augmentation process for neo4j graphql js. So, that's why we don't have the crud graphql operations for the user type, even though we added that into our schema here, we've got our authorization token from our previous login request, so that should should still be valid and we're going to make this current user request. Cool looks like that worked, so it says: here's your user id and the current user is johnny montana. Let'S try to let's create a different user, make sure this works so we'll go through this process again. So here, let's sign up to create a new user uh called this one will be username. Jennycat password is feed me. So, let's also, we have the authorization token that we're sending here, which i guess is okay, um. Let'S just take that out since that that seems kind of odd to be sending an authorization token when we are going through the sign up. I don't think i don't think that will impact anything like we will still go through the decoding process of the token and it'll be passed along as a cipher parameter, but in the context, but we're not accessing it, but it seems odd, so we'll remove that anyway. So, okay, let's run that and cool it says here. Here'S your token for jenny cat. If we go to the database and now we look at our user nodes, cool yep, we've got one for jenny, cat with that user's generated id and the salted password along with johnny montana. And if we take this token and add that as an authorization header and now we don't need to do login because we we have the token that's fine, although we can test it. But let's do current user passing the token and we should now have username jennycat cool so that works okay, cool, so we've implemented user, login and user sign up um. We said we also wanted to implement subscribe. So we wanted to be able uh. We want a user to be able to subscribe to uh to podcasts right. So, let's see what we can do in our schema is now add a mutation uh, let's say subscribe to podcast, and this is going to take a itunes id. I think, and let's have it return a podcast type which we haven't added yet so let's create a type podcast. So if you remember, let's, let's uh do a podcast search here, which i don't have an example. That'S fine podcast search search term neo4j, title feed, url description, itunes id so because itunes has the largest podcast index. We treat that as the canonical id for a podcast. So in our application, we're going to have users searching for podcasts by search term they're, going to see this like list of here, all the podcasts that you may be interested in and then you're going to say yep. I want to subscribe to that and on the back end, what's going to happen, is we're going to send this itunes id to this subscribe to podcast mutation, and that is going to update the database? It'S going to do a couple of things, one! It'S going to grab the currently authenticated user, using the cipher parameters and the auth token that's passed as a header and then it's going to merge the podcast node because remember the podcast search this query field. This isn't actually creating any data in the database. This is just going out to the podcast index and searching and passing through results, but we haven't actually created any podcast nodes in the database, so we want to make sure that our subscribe to podcast mutation merges on the podcast node. So if it doesn't exist, we want to create it so we'll use the itunes id to refer to that podcast so anyway. So the the fields that we need are an itunes id podcasts, have a title: um a link so like a web url, they have a feed, url uh. What else description and image? That'S probably probably good enough for now. So then our subscribe to podcast. This is going to be a cypher directive field in our cipher statements. Well, the first thing it's going to do is match on the user look up user by id and we have cypherparams.userid to find the user and then we're going to merge on the podcast. Since we may not, we, we may not have saved this podcast in the database. Yet so that's why we're doing a merge, so merge is going to create it if it doesn't exist, but not create a duplicate. If it already exists, that's the benefit of merge and the value for this is going to be itunes id as a cipher parameter. That'S this value here that the user is passing in and then we want to create a relationship say that the user subscribes to this podcast and then we need to return the podcast. Let'S save that and refresh playground to pick up our schema changes, and now we can say mutation subscribe to podcast itunes id. Oh, we need an itunes id. Let'S go back here, search for [, Music, ], the neo4j podcast, so we're going to subscribe to the neo4j podcast with our currently authenticated user, which this should be the jennycat user, and that gives me a podcast object. So let's get the itunes id. So if this works, we should create a new podcast node in the database to represent the neo4j graphistania podcast and then create a relationship from jennycat to that podcast saying that jennycat has subscribed to it. So it returns the itunes id. Let'S check the database to see if we create anything, yep cool, so jenny cat now subscribes to this podcast that just has an itunes id. So there's something that we're missing here, which is we? The only information we have about this podcast is its itunes id, which is not very helpful for building our application right. It'S like we want the the name of the podcast. We want to know the feed url because eventually we're going to have to go out and parse the feed url to find the actual episodes right. So let's take advantage of the podcast index so here we're making a call to the podcast index to search for podcasts by term, but i think, let's take a look at the documentation. I think we can look up a podcast by feed url yeah by itunes id yeah. Here we go so here we can just look up a single podcast by itunes id at this end point. So, let's change this mutation for subscribe to podcast. Let'S also make a call to the podcast index api to look up the podcast by itunes id and basically get more information about the podcast that we can then store in the database to make available to the application. So we have i'm just going to do a little copy paste because we have here constructing the authorization header. That'S those i think the most work uh, the authorization header for the podcast api using cipher. So let's do that. Let'S do that as the first part of the query um so api.podcastindex.org1. What is it not search? It'S podcasts by itunes id by itunes, id got ta be extra careful on my spelling today. Uh and the query parameter is not q, it's id and then apox text. Url and code yeah, we can still url encode it. That'S fine, um itunes, id headers are gon na, be the same. It yields a value and then what is it yield? We don't need to unwind over anything because there's just a single object. So let's say with value dot: what is it value dot feed as feed? Okay? So now what do we match on the user? That'S still good uh, we merged on the podcast by itunes id that's good, but now we can set to update some properties on this podcast node from the data from the podcast index. So, let's set the uh what it would say title it's going to be feed.title uh the. What did we say here? Title link, feed, url description image, so link feed dot. We have link an original, url and url. So so link is like a link to like the web page for the podcast. I guess and url is i'm guessing the the rss feed yeah, so link is like the landing webpage and then url is the rss feed, so link feed, dot, link, description, feed, dot, description, uh? What else? Oh feed, url and image we said p dot, feed, url, equals feed, dot, url and image equals feed.what. Artwork, i think, is what they call it here: image and artwork. What'S the difference, that's the same valid the same image. Yeah is artwork like a higher resolution. Version of image - maybe i don't know um, let's go with artwork okay, and then i like to line up our equal signs here, so i can what's going on a little bit: better, okay cool! So that's itunes, id title link, feed, url description image. So that's what we're updating as properties in the database and then we create the subscribes to relationship return, the podcast yeah. I think that should be good. Let'S save that and go back to graphql playgrounds over here um. I can do this again. I can subscribe to this podcast again and it shouldn't create any duplicate data because we're merging on itunes id. But if i refresh this query now, we can see that we've stored some podcast detail information. So now we have the feed url, the image description and so on for our podcast cool um. The other thing we need well, i guess i should say now. I can also add other fields here so description, title, feed, url and so on. Did i get back from that graphql mutation neat, so the other thing that we're missing, i think, is being able to get a list of my subscribed podcasts as a user. So we'll add another [ Music ] query field: should we call this subscribed podcasts? So this is going to be a query field, so i send in my request that has a authorization token we're going to look up the user in the database and we're going to see what podcasts they subscribe to if any and return those so again, we'll take Use of the cipher params, if you see some flashes in the background, that's just a a light bulb that i have that's going out. So don't don't worry. It'S not uh, lightning or anything. Um. Okay, so subscribe. Podcasts gives me a list of podcast objects and i'm gon na write a cipher query to fetch podcasts for a user. So first we're gon na match on the user by id using cipher, params dot user id. And then i want to know what podcasts they subscribe to and i'm just going to return those and something went wrong with the parser. Oh, i need an argument here called statement for my directive, so we'll refresh graphql playground to pick up our schema changes and we look at the docs. We should have a new query field called subscribed, podcasts. You can also call it my podcast or something like that. That might be something a bit more fun, so we're passing this authorization token. This is for jenny cat. What podcast is does she subscribe to? She subscribes to graphistania cool. Let'S, let's test this um, let's go back to podcast search uh, let's search for the connected podcast. What'S the here's the itunes url for that one and we'll do our subscribe to podcast so jennycat now wants to subscribe to connected. If i check in the database cool in the database now i have connected and its information description, feed, url and so on. Jenny cat subscribes to and if i now say now show me my subscribed podcasts i get graphistania and connected cool um. That seems to be working. Let'S test this now for a different user. So i'm going to go back to [, Music ], the login mutation. So for the user johnny montana i'm going to log in here's a token that i'm then going to attach to each graphql request. So we'll update that in the http headers down here that we're sending as a bearer token and now, if i say current user, i should get johnny montana instead of jenny cat. If i go to my list of subscribed podcasts, i should get an empty list because i don't i don't subscribe to any podcasts. We can check that in the database, but i don't for this user. Let'S go and subscribe to the connected podcast and now, if i request my subscribed podcasts i get just connected and it's specific for the user johnny montana, which is the currently authenticated user, because that user is passing the correct jwt token that is cryptographically signed by our Application and includes the correct payload with the user id for that object for that for that user, and if i come back here, i can see that yep johnny montana subscribes to this podcast now in the database cool. So i think that is the functionality that we wanted to implement. We now are able to have users sign up to our application, at least in the api. Again, we haven't looked at the front end, yet users can sign up, they can log in, they can now subscribe to podcasts and they can see their list of subscribed podcasts and we've hydrated those podcasts in the database with data from the podcast search index, but only For the podcast that our users have subscribed to because search goes out to this podcast index service, we don't really care about storing in the database any podcast that a user one of our users hasn't subscribed to cool. So i think that uh is what we wanted to get done today, so i think we'll leave it there. I will push up the code to github as soon as we're done i'll link in the chat that github url. So you have that um next time. I'M not sure what what we should cover next time: [ Music ]. We have a few things that we need to dig into. We need to [ Music ] parse, the feed urls, somehow, because the next thing that a user is going to want to do is see the episodes for a podcast. We also need to think about playlists, so a user can create a playlist and add episodes to it. We also need to think about the front end of our application um, because we haven't really done anything with the front end yet so i don't know we'll we'll. Take a look at some of those or say at least one of those features. Uday says we need episode recommender with graph data science yeah we do definitely. That is something we will definitely get to along the way. Once we once we have some episodes. I was thinking it would be interesting to try to transcribe some of these. So is there some uh not text to speech, but the other way speech to text that we could do and then we could apply some nlp techniques, maybe some graph algorithms for recommendations. So we'll get there eventually, but we need to build out some of the other functionality uh first before we start digging into things like that, but definitely want to take a look at that. Well, cool! That'S all! For today, thanks uh for joining everyone. We will see you next thursday, at 2 p.m, pacific on the neo4j stream. So thanks a lot for joining uh. Let me know on twitter. If you have ideas for what the next episode should be, which feature we should focus on um, but otherwise thanks for joining and we'll see you next time cheers
Subscribe To Will's Newsletter
Want to know when the next blog post or video is published? Subscribe now!