Webtask

Documentation

Authentication

Protect access to webtasks with Auth0

Authentication and Authorization

You can protect access to your webtasks with Auth0. Auth0 allows you to authenticate your callers with any number of identity providers. You can use social identity providers like Google, Facebook, or GitHub, custom databases, LDAP based enterprise identity systems, and more.

Authenticated callers can be further authorized using simple authorization rules based on verified e-mail addresses.

Upon successful caller authentication, the Auth0 issued token can be used to authenticate subsequent calls to the webtask on behalf of the authenticated caller. The JWT token can be submitted to the webtask using URL query parameters or the Authorization HTTP header and is therefore useful for making ajax calls. You can also issue your own access token if you want to apply custom authorization policies at the time of authentication.

Webtask authentication is only available from within named webtasks. It is not available from code submitted for execution as part of the HTTP POST request body.

Baseline application

Consider the following, unauthenticated Express-based webtask:

var app = new (require('express'))();
var wt = require('webtask-tools');

app.get('/', function (req, res) {
    res.end('Hello, world!');
});

module.exports = wt.fromExpress(app);

Assuming the webtask code is stored in the hello.js file, the wt-cli tool can be used to create the webtask as follows:

wt create hello.js

Authentication

To use Auth0 to authenticate all calls to your webtask, modify the code as follows:

// ...

app.get('/', function (req, res) {
    res.end('Hello, ' + req.user.name);
});

module.exports = wt.fromExpress(app).auth0();

Note the following:

  • The .auth0() method is used to secure your express application with Auth0. At runtime, all unauthenticated HTTP calls will be first redirected to Auth0 for authentication, and your Express handlers will only ever be invoked for already authenticated callers.
  • The req.user property is now available within your handler code. It contains information about the authenticated user.

In order to associate your webtask with your Auth0 application, the Auth0 client ID, client secret, and domain name must be specified during webtask creation:

wt create hello.js \
  -s AUTH0_CLIENT_ID=... \
  -s AUTH0_CLIENT_SECRET=... \
  -s AUTH0_DOMAIN=...

You can obtain this information from Auth0 when you create a free account.

Selective authentication

You can indicate that specific endpoints of your application are excluded from authentication and should be available to anonymous callers:

// ...

module.exports = wt.fromExpress(app).auth0({
  exclude: [
    '/',
    '/public'
  ]
});

The exclude option can be a single string, an array of strings, or a (ctx, req, appPath) => boolean function (true means exclude).

Authorization

You can specify simple authorization rules that further restrict access to your webtask to a subset of authenticated callers. For example, you can indicate that only callers with a specific, verified e-mail address, or people with an e-mail address from a specific domain can access the webtask. You can also write code to perform custom authorization checks:

// ...

module.exports = wt.fromExpress(app).auth0({
  authorized: [
    'tomasz@janczuk.org', 
    '@auth0.com'
  ]
});

The authorized option can be a string, an array of strings, or a (ctx, req) => boolean function. When the function is invoked, the ctx.user property will contain the profile of the authenticated user. Returning true means that the user's access is authorized.

User profile properties

The req.user property of an authenticated request as well as the ctx.user property of the webtask context contain the profile information for the authenticated user. The ctx.accessToken property of the webtask context contains the JWT access token. Both are accesible in your Express webtask code:

// ...

app.get('/', function (req, res) {
    // req.user contains user profile
    // req.webtaskContext.accessToken contains JWT access token
});

as well as code of a webtask using the low level programming model:

// ...

function webtask(ctx, req, res) {
  // req.user contains user profile
  // ctx.accessToken contains JWT API key
}

module.exports = wt.auth0(webtask);

By default, only email, email_verified, and name properties are requested from Auth0. You can request additional properties to be included in the profile using the scopes option:

// ...

module.exports = wt.fromExpress(app).auth0({
  scope: 'nickname picture'
});

You should note, however, that more properties result in a larger JWT access token that will be issued to represent the caller.

Auth0 credentials

The Auth0 client ID, client secret, and domain, can be specified through options as simple mapping functions. Default implementations extract these values from the webtask token secrets specified during webtask creation as shown:

module.exports = wt.fromExpress(app).auth0({
    clientId: (ctx, req) => ctx.secrets.AUTH0_CLIENT_ID,
    clientSecret: (ctx, req) => ctx.secrets.AUTH0_CLIENT_SECRET,
    domain: (ctx, req) => ctx.secrets.AUTH0_DOMAIN
});

Successful Auth0 login

Upon successful login, a JWT access token is created that can be later used to authenticate calls to the webtask APIs, e.g. through Ajax. By default the Auth0 issued id token representing the caller is used verbatim as the access token. However, if you wish to modify its contents, for example to apply custom authorization policies, you can use the createToken option:

// ...

module.exports = wt.fromExpress(app).auth0({
    createToken: function (ctx, req, idToken, accessToken) {
        // idToken is the Auth0 issued JWT id_token representing the caller
        // accessToken is the Auth0 issued access_token
        return idToken;
    }
});

Note that if you customize the createToken logic, you will also likely need to provide a custom logic to validate your token in validateToken described below.

After the access token is established, the loginSuccess callback is invoked. In that callback you have access to the user profile, the created JWT access token, and the request and response objects. With that information you can generate an application-specific response to the caller, for example redirect the user to a specific URL or render an HTML page.

If you don't specify loginSuccess callback, the default implementation will send an HTTP 302 redirect to the{baseUrl}?access_token={accessToken} URL, where the {baseUrl} is the base URL of your webtask:

// ...

module.exports = wt.fromExpress(app).auth0({
    loginSuccess: function (ctx, req, res, baseUrl) {
        // ctx.accessToken has the issued JWT access token
        // ctx.user and req.user have the user profile
    }
});

Using this mechanism, your code can propagate the JWT API key back to the client agent in an application-specific way.

Failed login

When authentication, authorization, or a login attempt fails, the loginError function is called. In that callback you have access to error object, the webtask context, and the request and response objects. With that information you can generate an application-specific response to the caller, for example a custom error page or a redirect to the /login endpoint:

// ...

module.exports = wt.fromExpress(app).auth0({
    loginError: function (error, ctx, req, res, baseUrl) {
        // error will at minimum have error.code
    }
});

If you don't specify loginError callback, the default implementation behaves as follows:

  • For GET requests without JWT access token, client agent is redirected to /login endpoint with an HTTP 302.
  • For GET requests with a JWT access token that cannot be validated, or if authorization check fails, or if Auth0 calls the /callback endpoint with an error during OAuth2 exchange, an HTTP 401/403 with text/html response containing a generic "Not authorized" message is returned. Details of the error can be gleaned from the x-wt-error HTTP response header.
  • All other requests receive the application/json error response in the response body with an HTTP 401/403 status code as appropriate.

Access token validation

Calls to webtask endpoints other than /login or /callback can be authenticated with the JWT access token issued during prior login attempt to avoid the requests triggering the Auth0 OAuth exchange. This mechanism is useful for authenticating Ajax requests or form posts that follow an original Auth0 login. By default, the JWT access token can be provided either as the Authorization: Bearer {accessToken} HTTP request header or the access_token={accessToken} URL query parameter. Default mechanism of extracting the access token from the request can be customized with the getAccessToken callback:

// ...

module.exports = wt.fromExpress(app).auth0({
    getAccessToken: function (ctx, req) {
        return 'accessToken';
    }
});

In particular, when the webtask is hosted over custom domain, the access token can be extracted from the cookie header.

After the JWT access token is obtained, it needs to be validated. You can provide your own validation method using the validateToken callback:

// ...

module.exports = wt.fromExpress(app).auth0({
    validateToken: function (ctx, req, token, cb) {
      cb(null, { email: 'foo@bar.com', email_verified: true });
    }
});

The implementation must perform any necessary validation and then call the provided callback with either an error or an object representing the user. The object is then stored in ctx.user as well as req.user properties.

If you don't provide your own implementation of validateToken, the default implementation will ensure the token is an id_token issued by Auth0 using the client secret configured when creating the webtask. All claims of the token will be used to construct the user profile.