Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork16
Local and OAuth authentication middleware for Deno
License
oslabs-beta/dashport
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Local authentication and OAuth 2.0 middleware forDeno
- Dashport classes that handle authentication and sessions based on the server framework (currently only Oak supported).
- Alocal strategy module.
- Strategy modules that allow developers to use third-party OAuth 2.0
- Written in TypeScript.
- Require Application instance for Oak to be passed in when instantiating DashportOak, removing the need for developers to manually call app.use(dashport.initialize).
- Instead of passing in the name of the server framework being used when Dashport is instantiated, Dashport now has different classes for different server frameworks. This is to support better modularity.
- Added a Dashport class for Oak, DashportOak.
- A template Dashport has been provided for any developer to create their own Dashport for a server framework.
- Refactored authenticate to take three arguments instead of one - the strategy, the serializer, and the deserializer to be used vs the name of the strategy.
- Removed add/remove serializer/deserializer methods.
- Merged deserializer's functionality into authenticate.
Dashport is a module that simplifies authentication in Deno. Currently, there is only Dashport support for the server framework Oak, but there is a template Dashport class available for anyone to create their own compatible Dashport.
Dashport was inspired byPassport, the golden standard of authentication middleware for Node.js.
Below is an example using the Oak server framework. To get started, import Dashport into the server file.
// 'server.ts' fileimport{DashportOak}from'https://deno.land/x/dashport@v1.2.1/mod.ts';
In the future, additional Dashports can be imported from the same mod.ts file. For example if Express was supported in Deno:
import{DashportExpress}from'https://deno.land/x/dashport@v1.2.1/mod.ts';
After importing Dashport, import Application from Oak and instantiate it.
// 'server.ts' fileimport{DashportOak}from'https://deno.land/x/dashport@v1.2.1/mod.ts';import{Application}from"https://deno.land/x/oak/mod.ts";constapp=newApplication();
Then instantiate Dashport and pass in the Application instance. This is needed in order to add a Dashport property onto Oak's context object. This specific instance of Dashport will then need to be used when calling the methods authenticate and logOut, so sessions for users can be maintained.
// 'server.ts' fileimport{DashportOak}from'https://deno.land/x/dashport@v1.2.1/mod.ts';import{Application}from"https://deno.land/x/oak/mod.ts";constapp=newApplication();constdashport=newDashportOak(app);
Routes can then be added as needed
import{DashportOak}from'https://deno.land/x/dashport@v1.2.1/mod.ts';import{Application,Router}from'https://deno.land/x/oak/mod.ts';constapp=newApplication();constrouter=newRouter();constdashport=newDashportOak(app);// add routes hereapp.use(router.routes());app.use(router.allowedMethods());app.addEventListener("error",(evt)=>{console.log(evt.error);});console.log('running on port',port);awaitapp.listen({ port});
In a separate file, add Dashport configurations for astrategy,serializer, anddeserializer. Developers can configure as many strategies, serializers, and deserializers, as they want, as long as they follow the specific rules for each one.
// 'dashportConfig.ts' fileimportGoogleStrategyfrom'https://deno.land/x/dashport_google/mod.ts';importGitHubStrategyfrom'https://deno.land/x/dashport_github/mod.ts'exportconstgoogStrat=newGoogleStrategy({client_id:'client-id-here',client_secret:'client-secret-here',redirect_uri:'http://localhost:8000/privatepage',response_type:'code',scope:'profile email openid',grant_type:'authorization_code',});exportconstghStrat=newGitHubStrategy({client_id:'client-id-here',client_secret:'client-secret-here',redirect_uri:'http://localhost:8000/privatepage',})exportconstserializerA=async(userInfo:any)=>{constserializedId=Math.floor(Math.random()*1000000000);userInfo.id=serializedId;try{awaitexampleDbCreateUpsert(userInfo);returnserializedId;}catch(err){returnerr;// or return new Error(err);}};exportconstserializerB=async(userInfo:any)=>{ ...}exportconstdeserializerA=async(serializedId:(string|number))=>{try{constuserInfo=awaitexampleDbFind({id:serializedId});returnuserInfo;}catch(err){returnerr;// or return new Error(err);}};exportconstdeserializerB=async(serializedId:(string|number))=>{ ...}
Import these configurations into the server.ts file and Dashport is now ready to authenticate. Dashport's authenticate method acts as middleware, so it can be used like so:
import{googStrat,serializerA,deserializerA}from'./dashportConfig.ts';router.get('/privatepage',dashport.authenticate(googStrat,serializerA,deserializerA),async(ctx:any,next:any)=>{ctx.response.body='This is a private page!';})
After authentication, Dashport will have created an ID based on the developer's defined serializer and manipulated the data however the developer specified (such as storing user info in a database). The ID then gets stored by Dashport to create a persistent session. Dashport will then invoke the developer's defined deserializer and pass in the ID. The deserializer will take this ID and manipulate it however the developer specified (such as fetching user info from the database). The returned data from the deserializer then gets stored onctx.locals so the next middleware can access it. If an error occurred in the deserializer function and gets returned, the error will be stored onctx.locals.
router.get('/user-favorites',dashport.authenticate(googStrat,serializerA,deserializerA),async(ctx:any,next:any)=>{if(ctx.localsinstanceofError){ctx.response.body='An Error occurred!';}else{constdisplayName=ctx.locals.displayName;ctx.response.body=`Welcome${displayName}!`;}})
In order to end a session, a log out button can be routed to an endpoint that calls Dashport's logOut method. Here's an example use with Oak:
router.get('/log-out',dashport.logOut,async(ctx:any,next:any)=>{ctx.response.body="You've logged out";})
- Strategies are the modules that can be imported with Dashport to allow third-party OAuth. They specify the authentication logic for a given OAuth service provider.
- Strategy classes need to follow two rules:
- Have a router method that ultimately returns an Error or the user information returned by the third-party OAuth in the form of Dashport's definedAuthData interface. AuthData needs to have auserInfo property in the form ofUserProfile and atokenData property in the form ofTokenData.
- When instantiated, take in the options that are needed for the specific third-party OAuth to authenticate.
constgoogStrat=newGoogleStrategy({client_id:'client-id-here',client_secret:'client-secret-here',redirect_uri:'http://localhost:8000/privatepage',response_type:'code',scope:'profile email openid',grant_type:'authorization_code',});
- After a successful authentication, Dashport will pass the obtained user information to a serializer. It is up to the developer to define serializer functions that specify what to do with user information. User information will be passed in the form of Dashport's definedAuthData interface.
- Serializer functions need to follow four rules:
- Accept one argument: the user data in the form of an object.
- Specify how to create a serialized ID.
- Specify what the developer wants to do with the user data (e.g. store it in a database).
- Return the serialized ID or an Error.
constserializerA=async(userInfo:any)=>{constserializedId=Math.floor(Math.random()*1000000000);userInfo.id=serializedId;try{awaitexampleDbCreateUpsert(userInfo);returnserializedId;}catch(err){returnerr;// or return new Error(err);}};
- After a successful authentication, Dashport will pass the serialized ID generated from the serializer to the deserializer. The developer should specify what the deserializer should do with the ID and return any data that should be available for access in the next middleware. The shape of the return value is up to the developer. In Oak, if the deserialization is successful, the data returned is stored onctx.locals. If the deserialization is not successful, an Error will be stored onctx.locals.
- Deserializer functions need to follow three rules:
- Accept one argument: the serialized ID.
- Specify what the developer wants to do with the serialized ID (e.g. fetch user info from a database).
- Return the data (e.g. user info) or an Error.
constdeserializerA=async(serializedId:(string|number))=>{try{constuserInfo=awaitexampleDbFind({id:serializedId});returnuserInfo;}catch(err){returnerr;// or return new Error(err);}};
- Exact functionality depends on which Dashport class is being used
- Authenticate is the middleware function that powers Dashport. It takes in three arguments: the instantiation of a strategy to be used, a serializer function, and a deserializer function. Authenticate first checks if a session exists. If a session does not exist, it begins the authentication process for the specified strategy.
// Oak exampleimport{DashportOak}from'https://deno.land/x/dashport@v1.2.1/mod.ts';import{Application,Router}from'https://deno.land/x/oak/mod.ts';import{ghStrat,serializerB,deserializerB}from'./dashportConfig.ts';constapp=newApplication();constrouter=newRouter();constdashport=newDashportOak(app);app.use(router.routes());app.use(router.allowedMethods());router.get('/secretpage',dashport.authenticate(ghStrat,serializerB,deserializerB),async(ctx:any,next:any)=>{ctx.response.body='This is a secret page!';})
- Exact functionality depends on which Dashport class is being used
- logOut is a middleware function that ends a session. If a user logs out and logOut is used, the user will have to reauthenticate. Here is an example use with Oak:
router.get('/log-out',dashport.logOut,async(ctx:any,next:any)=>{ctx.response.body="You've logged out";})
When a strategy successfully authenticates a user, the information given by the third-party provider should be returned in the form AuthData. The object should have an optionaltokenData property and a required userInfo property in the form ofUserProfile. This contains the information for theauthenticate method to use. The interface for AuthData is as below:
interfaceAuthData{tokenData:TokenData;userInfo:UserProfile;}
Any relevant token data the developer wishes to receive from the third-party OAuth should be stored in an object with the below interface:
interfaceTokenData{access_token:string;expires_in:number;scope:string;token_type:string;id_token:string;}
Since every OAuth provider returns information in different names and shapes, it is up to each strategy to conform the data returned into Dashport's defined UserProfile interface. This should contain all data the developer wishes to use.
exportinterfaceUserProfile{provider:string;providerUserId:string;displayName?:string;name?:{familyName?:string;givenName?:string;middleName?:string;};emails?:Array<string>;}
- Add more strategies.
- Add support for other server frameworks.
We would love to hear your experience and get your feedback on our modules. Feel free to send us any issues, concerns, or suggestions, in our Issues section, or simply contact us through LinkedIn.
Alvin Cheng ::LinkedIn |GitHub
Edward Deng ::LinkedIn |GitHub
Sam Portelance ::LinkedIn |GitHub
This project is licensed under theMIT License
About
Local and OAuth authentication middleware for Deno
Topics
Resources
License
Code of conduct
Security policy
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Sponsor this project
Uh oh!
There was an error while loading.Please reload this page.
Packages0
Contributors5
Uh oh!
There was an error while loading.Please reload this page.