heroesapplication lets anyone create or view the same set of heroes. We will continue to build on the last chapter's project,
heroes, requiring a user to log in before viewing or creating heroes.
conduit document clientand tests.
Authorizationheader. There are a number of security risks involved in doing this, so OAuth 2.0 takes another approach: you send your credentials once, and get a 'access token' in return. You then send this access token in each request. Because the server grants the token, it knows that you've already entered your credentials (you've authenticated) and it remembers who the token belongs to. It's effectively the same thing as sending your credentials each time, except that the token has a time limit and can be revoked when things go wrong.
conduitpackage, but it is a separate library named
conduit/managed_auth. It takes a few steps to set up that might be difficult to understand if you are not familiar with OAuth 2.0, but you'll get a well-tested, secure authorization implementation.
model/user.dartand enter the following code:
package:conduit/managed_auth.dartcontains types that use the ORM to store users, tokens and other OAuth 2.0 related data. One of those types is
ResourceOwnerTableDefinition, the superclass of our user's table definition. This type contains all of the required fields that Conduit needs to implement authentication.
ResourceOwnerTableDefinitionlooks like this:
User's table definition, our
Usertable has all of these database columns.
ManagedAuthResourceOwner<_User>- this is a requirement of any OAuth 2.0 resource owner type when using
AuthServer. This type has all of the logic needed to authentication and authorize users. For example, an
AuthServercan generate a new token if given valid user credentials.
channel.dart, add the following imports to the top of your file:
authServerproperty in your channel and initialize it in
AuthServerhandles the logic of authentication and authorization, it doesn't know how to store or fetch the data it uses for those tasks. Instead, it relies on a delegate object to handle storing and fetching data from a database. In our application, we use
package:conduit/managed_auth- as the delegate. This type uses the ORM for these tasks; the type argument must be our application's user object.
conduit/managed_auth, we've added a few more managed objects to our application (to store tokens and other authentication data) and we also have a new
Usermanaged object. It's a good time to run a database migration. From your project directory, run the following commands:
POSTrequests that contain a username and password in the body. It will insert a new user into the database and securely hash the user's password.
Userobject, it needs a password field, but we don't want to store the password in the database without first hashing it.
Serializeannotation to it. Add this property to your
Userhas a transient property
passwordthat can be read on input (from a request body), but is not sent on output (to a response body). We don't have to run a database migration because transient properties are not stored in a database.
controller/register_controller.dartand enter the following code:
channel.dart, let's link this controller - don't forget to import it!
curlfrom the command-line. (We'll specify
-n1to designate using one isolate and speed up startup.)
channel.dartto add an
AuthControllerfor the route
AuthControllerfollows the OAuth 2.0 specification for granting access tokens when given valid user credentials. To understand how a request to this endpoint must be structured, we need to discuss OAuth 2.0 clients. In OAuth 2.0, a client is an application that is allowed to access your server on behalf of a user. A client can be a browser application, a mobile application, another server, a voice assistant, etc. A client always has an identifier string, typically something like 'com.conduit.dart.account_app.mobile'.
conduit auth add-clientCLI. Run the following command from your project directory:
grant_type=passwordis included in the request body
application/x-www-form-urlencoded; this means the request body is effectively a query string (e.g.
channel.dart, link an
Authorizerin the middle of the
Authorizerprotects a channel from unauthorized requests by validating the
Authorizationheader of a request. When created with
Authorizer.bearer, it ensures that the authorization header contains a valid access token. Restart your application and try and access the
/heroesendpoint without including any authorization:
Authorizercan validate access token scopes and basic authorization credentials. You'll see examples of these in a later exercise.