diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 53392a8113..0000000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.gitignore b/.gitignore index 8b6cdb2283..dfb5661bd2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ _drafts/ _site/ _site +.DS_Store +.idea +.bundle/ +.sass-cache diff --git a/.ruby-version b/.ruby-version index e1b9401f57..7ec1d6db40 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.0.0-p195 +2.1.0 diff --git a/.sass-cache/3a8df122a58988f2f88b0accafc153339997228e/list-post.scssc b/.sass-cache/3a8df122a58988f2f88b0accafc153339997228e/list-post.scssc new file mode 100644 index 0000000000..5c0f3b6aa0 Binary files /dev/null and b/.sass-cache/3a8df122a58988f2f88b0accafc153339997228e/list-post.scssc differ diff --git a/.sass-cache/3a8df122a58988f2f88b0accafc153339997228e/sidebar.scssc b/.sass-cache/3a8df122a58988f2f88b0accafc153339997228e/sidebar.scssc new file mode 100644 index 0000000000..d3578cce63 Binary files /dev/null and b/.sass-cache/3a8df122a58988f2f88b0accafc153339997228e/sidebar.scssc differ diff --git a/Dockerfile b/Dockerfile index e3241b2d2a..6987375e4e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,20 +1,28 @@ FROM jfromaniello/jekyll -ADD . /data - -WORKDIR /data - -RUN bundle install -RUN jekyll build +EXPOSE 80 +# install nodejs, npm and git RUN \ apt-get update && \ - apt-get install -y nginx && \ + apt-get install -y nginx nodejs npm git git-core # 2016-01-25 + +# Set up nginx as no-daemon +RUN \ echo "\ndaemon off;" >> /etc/nginx/nginx.conf && \ chown -R www-data:www-data /var/lib/nginx ADD nginx.conf /etc/nginx/nginx.conf -CMD /usr/sbin/nginx -c /etc/nginx/nginx.conf +ADD . /data +WORKDIR /data +COPY Gemfile /data + +# install gems +RUN bundle install + +ENV JEKYLL_ENV production + +RUN jekyll build -EXPOSE 80 \ No newline at end of file +CMD ["/usr/sbin/nginx", "-c", "/etc/nginx/nginx.conf"] diff --git a/Gemfile b/Gemfile index 75d3df72b9..40e42bdf10 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,14 @@ source 'https://rubygems.org' -gem 'jekyll', '1.5.1' +gem 'jekyll', '2.4.0' gem 'psych', '~> 2.0.5' gem 'rdiscount', '~> 2.1.7.1' -gem 'stringex', '~> 2.5.2' \ No newline at end of file +gem 'stringex', '~> 2.5.2' +gem 'stylus', '~> 1.0.1' +gem 'deb-s3' +gem 'fpm' +gem 'rake' +gem 'rake_text' +gem 'fastimage' +gem 'nokogiri' +gem 'highline' \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index 17a911293b..e48b3ca5f2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,49 +1,104 @@ GEM remote: https://rubygems.org/ specs: + arr-pm (0.0.10) + cabin (> 0) + aws-sdk (1.66.0) + aws-sdk-v1 (= 1.66.0) + aws-sdk-v1 (1.66.0) + json (~> 1.4) + nokogiri (>= 1.4.4) + backports (3.6.8) blankslate (2.1.2.4) - classifier (1.3.4) - fast-stemmer (>= 1.0.0) + cabin (0.8.1) + celluloid (0.16.0) + timers (~> 4.0.0) + childprocess (0.5.9) + ffi (~> 1.0, >= 1.0.11) + clamp (0.6.5) + classifier-reborn (2.0.1) + fast-stemmer (~> 1.0) + coffee-script (2.3.0) + coffee-script-source + execjs + coffee-script-source (1.8.0) colorator (0.1) - commander (4.1.6) - highline (~> 1.6.11) + deb-s3 (0.7.1) + aws-sdk (~> 1.18) + thor (~> 0.18.0) + execjs (2.2.2) fast-stemmer (1.0.2) - ffi (1.9.3) - highline (1.6.21) - jekyll (1.5.1) - classifier (~> 1.3) + fastimage (2.1.0) + ffi (1.9.6) + fpm (1.4.0) + arr-pm (~> 0.0.10) + backports (>= 2.6.2) + cabin (>= 0.6.0) + childprocess + clamp (~> 0.6) + ffi + json (>= 1.7.7) + highline (1.7.10) + hitimes (1.2.2) + jekyll (2.4.0) + classifier-reborn (~> 2.0) colorator (~> 0.1) - commander (~> 4.1.3) - liquid (~> 2.5.5) - listen (~> 1.3) - maruku (= 0.7.0) - pygments.rb (~> 0.5.0) - redcarpet (~> 2.3.0) + jekyll-coffeescript (~> 1.0) + jekyll-gist (~> 1.0) + jekyll-paginate (~> 1.0) + jekyll-sass-converter (~> 1.0) + jekyll-watch (~> 1.1) + kramdown (~> 1.3) + liquid (~> 2.6.1) + mercenary (~> 0.3.3) + pygments.rb (~> 0.6.0) + redcarpet (~> 3.1) safe_yaml (~> 1.0) toml (~> 0.1.0) - liquid (2.5.5) - listen (1.3.1) + jekyll-coffeescript (1.0.1) + coffee-script (~> 2.2) + jekyll-gist (1.1.0) + jekyll-paginate (1.1.0) + jekyll-sass-converter (1.2.1) + sass (~> 3.2) + jekyll-watch (1.1.1) + listen (~> 2.7) + json (1.8.3) + kramdown (1.5.0) + liquid (2.6.1) + listen (2.7.11) + celluloid (>= 0.15.2) rb-fsevent (>= 0.9.3) rb-inotify (>= 0.9) - rb-kqueue (>= 0.2) - maruku (0.7.0) + mercenary (0.3.4) + mini_portile2 (2.3.0) + nokogiri (1.8.1) + mini_portile2 (~> 2.3.0) parslet (1.5.0) blankslate (~> 2.0) - posix-spawn (0.3.8) + posix-spawn (0.3.9) psych (2.0.5) - pygments.rb (0.5.4) + pygments.rb (0.6.0) posix-spawn (~> 0.3.6) yajl-ruby (~> 1.1.0) + rake (11.1.1) + rake_text (0.0.1) rb-fsevent (0.9.4) - rb-inotify (0.9.3) - ffi (>= 0.5.0) - rb-kqueue (0.2.2) + rb-inotify (0.9.5) ffi (>= 0.5.0) rdiscount (2.1.7.1) - redcarpet (2.3.0) - safe_yaml (1.0.2) + redcarpet (3.2.0) + safe_yaml (1.0.4) + sass (3.4.6) stringex (2.5.2) - toml (0.1.1) + stylus (1.0.1) + execjs + stylus-source + stylus-source (0.49.3) + thor (0.18.1) + timers (4.0.1) + hitimes + toml (0.1.2) parslet (~> 1.5.0) yajl-ruby (1.1.0) @@ -51,7 +106,18 @@ PLATFORMS ruby DEPENDENCIES - jekyll (= 1.5.1) + deb-s3 + fastimage + fpm + highline + jekyll (= 2.4.0) + nokogiri psych (~> 2.0.5) + rake + rake_text rdiscount (~> 2.1.7.1) stringex (~> 2.5.2) + stylus (~> 1.0.1) + +BUNDLED WITH + 1.16.1 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..81d8ce5f6a --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Auth0, Inc. (https://auth0.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100755 index 0000000000..7fb5b1aae5 --- /dev/null +++ b/Makefile @@ -0,0 +1,36 @@ +# +# auth0-blog Makefile +# + +build_deb: bundle build_pages check-version-variable check-deb-variables + # + # Accepted variables to be passed + # WORKSPACE , GIT_URL , VERSION_NUMBER , GIT_BRANCH , GIT_COMMIT + # + + fpm -C $(WORKSPACE) --deb-user www-data --deb-group www-data \ + --prefix /opt/auth0 \ + --url ' $(GIT_URL)' --version $(VERSION_NUMBER) -n auth0-blog \ + -x '**/.git*' -x '*.tgz' -x '**/test/*' \ + --description 'Auth0 Blog $(VERSION_NUMBER) - git commit $(GIT_BRANCH)-$(GIT_COMMIT)' \ + -t deb -s dir auth0-blog + + git checkout . + +build_pages: + jekyll build --destination auth0-blog --trace + +check-version-variable: +ifndef VERSION_NUMBER + $(error VERSION_NUMBER is undefined) +endif + +check-deb-variables: +ifndef WORKSPACE + $(error WORKSPACE is undefined) +endif + +bundle: + gem list + gem install bundler + bundle install diff --git a/Rakefile b/Rakefile index 68786b91b5..20436857a0 100644 --- a/Rakefile +++ b/Rakefile @@ -1,4 +1,5 @@ require "stringex" +require 'highline/import' new_post_ext = "markdown" posts_dir = "_posts" @@ -37,20 +38,39 @@ task :new_post, :title do |t, args| title = args.title filename = "#{posts_dir}/#{Time.now.strftime('%Y-%m-%d')}-#{title.to_url}.#{new_post_ext}" if File.exist?(filename) - abort("rake aborted!") if ask("#{filename} already exists. Do you want to overwrite?", ['y', 'n']) == 'n' + abort("Rake aborted!") if ask("#{filename} already exists. \nDo you want to overwrite? Y/n", ['y', 'n', 'Y', 'N']).downcase == 'n' end puts "Creating new post: #{filename}" open(filename, 'w') do |post| - post.puts "---" - post.puts "layout: post" - post.puts "title: \"#{title.gsub(/&/,'&')}\"" - post.puts "date: #{Time.now.strftime('%Y-%m-%d %H:%M')}" - post.puts "author: " - post.puts " name: " - post.puts " url: " - post.puts " mail: " - post.puts "tags: " - post.puts "- foo" - post.puts "---" + post.puts <<-POST +--- +layout: post +title: #{title.gsub(/&/,'&')} +description: +longdescription: +date: #{Time.now.strftime('%Y-%m-%d %H:%M')} +category: +press_release: +is_non-tech: +author: + name: + url: + mail: + avatar: +design: + bg_color: + image: +tags: +- foo +related: +- +--- + +**TL;DR:** A brief synopsis that includes link to a [github repo](http://www.github.com/). + +--- +POST end end + + diff --git a/_ add-alias-field.js b/_ add-alias-field.js new file mode 100644 index 0000000000..e3a6688172 --- /dev/null +++ b/_ add-alias-field.js @@ -0,0 +1,54 @@ +var fs = require('fs'); +var path = require('path'); + +const loadPostIndex = + () => fs.readdirSync(path.join(__dirname, '/_posts')) + .filter(post => post.indexOf('.markdown') > -1) +; + +const extractPostAlias = + (name) => name.substr(0, 11).replace(/\-/g, '/') + name.substr(11).replace('.markdown', '') +; + +const extractContent = + (name) => fs.readFileSync(path.join(__dirname, '/_posts', name), 'utf8') +; + +const extractAliasNameAndContent = + (name) => ({ + name, + alias: extractPostAlias(name), + content: extractContent(name) + }) +; + +const writePost = + (name, content) => fs.writeFileSync(path.join(__dirname, '/_posts', name), content, 'utf8') +; + +const includeAlias = + (name) => { + var alias = '\nalias: /' + extractPostAlias(name) + '/'; + var content = extractContent(name); + + if (content.indexOf('alias:') > -1) { + return content; + } + + if (content.indexOf('permalink:') > -1) { + return content.replace('permalink:', 'alias:'); + } + + return content.replace('\nauthor:', alias + '\nauthor:'); + } +; + +const processFiles = + () => { + loadPostIndex() + .forEach((file) => writePost(file, includeAlias(file))) + ; + } +; + +processFiles(); diff --git a/_config.yml b/_config.yml index b7c1ca5d4e..dcf8261fa8 100644 --- a/_config.yml +++ b/_config.yml @@ -1,11 +1,39 @@ +title: 'Auth0 Blog' +description: 'Company Updates & Technology Articles' +url: 'https://auth0.com' +baseurl: '/blog' markdown: rdiscount -paginate: 15 +paginate: 5 exclude: [ Gemfile, Gemfile.lock, Procfile, vendor, Rakefile] -permalink: pretty +permalink: /:title/ highlighter: pygments excerpt_separator: '' -baseurl: '/blog' encoding: utf-8 +ampdir: 'amp' +is_non-tech: false +non-tech_dir: 'business' +tech_dir: 'tech' only_first_p: show_read_more_link: true - read_more_link_text: 'Read more' \ No newline at end of file + read_more_link_text: 'Read more' +stylus: + compress: true + path: ./css +values: + is_post_index: false +defaults: + - + scope: + path: "" + type: "posts" + values: + design: + image_default: https://cdn.auth0.com/styleguide/components/1.0.8/media/logos/img/badge.png + - + scope: + path: "" + values: + related: + - 2015-05-14-creating-your-first-real-world-angular-2-app-from-authentication-to-calling-an-api-and-everything-in-between + - 2015-04-09-adding-authentication-to-your-react-flux-app + - 2015-03-31-critical-vulnerabilities-in-json-web-token-libraries diff --git a/_includes/ambassador_program.html b/_includes/ambassador_program.html new file mode 100644 index 0000000000..b579a4a46e --- /dev/null +++ b/_includes/ambassador_program.html @@ -0,0 +1,34 @@ +
+ + +
diff --git a/_includes/amp-archive_post.html b/_includes/amp-archive_post.html new file mode 100644 index 0000000000..d2454ae8f8 --- /dev/null +++ b/_includes/amp-archive_post.html @@ -0,0 +1,51 @@ +{% comment %} +This is the template used to show an entry in archives. +Archives include: "by tags" and "by time" archives. +{% endcomment %} + +{% assign current = page %} +{% assign is_banner_post = false %} + +{% if page.is_post_index %} + {% assign current = post %} +{% endif %} + + diff --git a/_includes/amp-styles.scss b/_includes/amp-styles.scss new file mode 100644 index 0000000000..f5cfaaf4b2 --- /dev/null +++ b/_includes/amp-styles.scss @@ -0,0 +1,332 @@ +@import 'amp/sidebar'; +@import 'amp/list-post'; + +body{ + font-family: fakt-web,Montserrat,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,Arial,sans-serif; +} + +p { + margin: 0 0 14px; +} +.container{ + margin-right: auto; + margin-left: auto; + padding-left: 15px; + padding-right: 15px; + margin-top: 40px; +} + +a { + background-color: transparent; + color: #000; + text-decoration: none; +} + +ul { + list-style: none +} + +.wrapper-header{ + height: 100vh +} + +.banner{ + text-align: center; + h1 { + color: #fff; + font-size: 2.5rem; + margin-bottom: 0; + } + + .post-title { + font-size: 25px; + color: #fff; + margin: 3.5px 0 18.5px 0; + padding: 0 29px 0 30px; + } + +} + +.banner-post{ + background: #354171; + padding: 30px 0 20px 0; + margin-bottom: 0; + h2 { + font-size: 25px; + color: #fff; + margin: 3.5px 0 18.5px 0; + padding: 0 29px 0 30px; + font-family: "fakt-web"; + } + + .banner-tag{ + position: absolute; + top: -10px; + left: 40.5px; + right: 40.5px; + + .tag, .date{ + text-transform: uppercase; + color: #fff; + font-weight: 200; + } + .tag{ + float: left; + display: inline-block; + } + .date{ + float: right; + display: inline-block; + i { + margin-right: 3px; + svg { + margin-bottom: -3px; + width: 18px; + height: 18px; + } + } + } + } + a { + color: white; + } + + .banner-subtitle{ + font-size: 14px; + opacity: 0.7; + margin: 0; + } + + .entry-content{ + font-size: 18px; + line-height: 2; + margin: 0 auto; + p + { + color: white; + font-size: 17px; + font-family: "fakt-web"; + line-height: 1.5; + font-weight: 200; + margin: 0px 30.5px 56.5px 36.5px; + letter-spacing: 0; + } + } + + .entry-title { + line-height: 1.3; + font-size: 24px; + margin-top: 10px; + a { + font-weight: 300; + font-family: AvenirNext-Medium; + } + } + + .entry-thumbnail { + margin: 0 auto; + margin-top: 48px; + display: inline-block; + border-radius: 50%; + overflow: hidden; + position: relative; + background: #EAEEF3; + height: 100px; + width: 100px; + opacity: 1 ; + } + +} + +.blog-single { + .entry-content{ + font-size: 18px; + line-height: 2; + margin: 0 auto; + font-family: "minion-pro",serif; + + h1, h2, h3 { + font-weight: 500; + line-height: 1.5; + font-family: "Avenir Next",avenir-next-web,"Helvetica Neue",Hevetica,sans-serif; + } + + h2 { + font-size: 24px; + margin-bottom: 15px; + margin-top: 30px; + } + + h3{ + font-size: 22px; + margin-bottom: 20px; + } + + a { + color: rgba(0,0,0,0.87); + text-decoration: none; + background-repeat: repeat-x; + background-size: 2px 2px; + background-position: 0 23px; + border-bottom: 1px solid rgba(0,0,0,0.5); + } + + ul { + li { + list-style: none; + position: relative; + padding-left: 25px; + &:before { + content: ""; + display: inline-block; + height: 6px; + width: 6px; + background: #eb5422; + position: absolute; + top: .8em; + border-radius: 3px; + left: 0; + margin-right: 20px; + } + } + } + strong{ + font-weight: 700; + } + + blockquote { + font-size: 108%; + border-left: 1px solid #eee; + font-style: normal; + } + pre{ + code { + font-size: 13px; + background: none; + border: 0; + padding: 20px; + white-space: pre; + display: block; + overflow-x: auto; + color: #e6e1dc; + } + } + + code { + border-radius: 4px; + font-size: 75%; + padding: 3px 8px; + color: rgba(0,0,0,0.87); + background: rgba(230,230,230,0.87); + } + } +} + +.post-info { + text-align: justify; + text-transform: uppercase; + padding: 0 28px 0 29.5px; + .author{ + display: inline-block; + } + .share-container { + display: inline-block; + float: right; + + .social-stats{ + color: white; + .network{ + color: white; + svg { + fill: currentColor; + } + } + } + } +} + + +code, kbd, pre, samp { + font-family: "Roboto Mono",Menlo,Monaco,Consolas,'Courier New',monospace; +} + +pre { + color: #fff; + background-color: #373d43; + word-wrap: normal; + padding: 0; + border: 0; +} + +.col{ + position: relative; + min-height: 1px; + padding-left: 15px; + padding-right: 15px; +} +.content-column{ + margin-right: -1px; +} + +blockquote { + padding: 14px 28px; + margin: 0 0 28px; + font-size: 17.5px; + border-left: 5px solid #eee; + font-style: italic; +} + +.block-entry-thumbnail { + background: rgba(255,255,255,0.1); + margin: 0px -15px; + padding-bottom: 20px; + position: relative; + height: 172.5px; +} + +.amp-social-share-linkedin { + background-image: url('https://cdn.auth0.com/blog/amp/linkedin.svg'); + margin-right: 2px; +} + +.amp-social-share-facebook{ + background-image: url('https://cdn.auth0.com/blog/amp/facebook.svg'); + margin-right: 5px; +} + +.amp-social-share-twitter{ + background-image: url('https://cdn.auth0.com/blog/amp/twitter.svg'); + margin-right: 5px; +} + +.amp-social-share-facebook, +.amp-social-share-twitter, +.amp-social-share-linkedin{ + background-color: transparent; + } + +.pointer-header{ + position: fixed; + bottom: 2%; + background-color: rgba(0, 0, 0, 0.08); + width: 30px; + height: 30px; + right: 2%; + border-radius: 10%; + i{ + padding: 11px; + &:before{ + content: ""; + border-width: 0px 0px 1px 1px; + border-style: solid; + width: 7px; + height: 7px; + color: rgba(0, 0, 0, 0.5); + transform: rotate(135deg); + display: inline-block; + vertical-align: baseline; + padding: 0; + } + } + +} diff --git a/_includes/amp/amp_sidebar.html b/_includes/amp/amp_sidebar.html new file mode 100644 index 0000000000..2e1772744f --- /dev/null +++ b/_includes/amp/amp_sidebar.html @@ -0,0 +1,74 @@ + +
+
+ + + Created with Sketch. + + + + + + +
+
+ + + SING UP + +
diff --git a/_includes/amp/banner_post.html b/_includes/amp/banner_post.html new file mode 100644 index 0000000000..e85c9ccc97 --- /dev/null +++ b/_includes/amp/banner_post.html @@ -0,0 +1,50 @@ +{% assign current = page %} +{% assign is_banner_post = true %} + +{% if page.is_post_index %} + {% assign current = post %} +{% endif %} + + +{% if current.tags %} + {% assign tag = current.tags[0] %} +{% else %} + {% assign tag = 'Blog' %} +{% endif %} + + diff --git a/_includes/amp/entry_image.html b/_includes/amp/entry_image.html new file mode 100644 index 0000000000..5a5cf62465 --- /dev/null +++ b/_includes/amp/entry_image.html @@ -0,0 +1,69 @@ +{% capture opacity %} + {% if {{current.design.image_opacity}} %} + opacity: {{current.design.image_opacity}}; + {% endif %} +{% endcapture %} + +{% capture size %} + {% if current.design.image %} + {% if {{current.design.image_size}} %} + max-width: {{current.design.image_size}}; + {% endif %} + {% else %} + max-width: 50%; + top: 53%; + {% endif %} +{% endcapture %} + +{% capture top %} + {% if {{current.design.image_top}} %} + top: {{current.design.image_top}}; + {% endif %} +{% endcapture %} + +{% capture left %} + {% if {{current.design.image_left}} %} + left: {{current.design.image_left}}; + {% endif %} +{% endcapture %} + +{% capture bg_merge %} +{% if current.design.bg_merge and is_banner_post %} + border-radius: 0; +{% endif %} +{% endcapture %} + +{% capture image_bg_color %} +{% if current.design.image_bg_color %} + background: {{current.design.image_bg_color}}; +{% endif %} +{% endcapture %} + +
+ + + +
+ + + + +
+
+
diff --git a/_includes/amp/nav-bar.html b/_includes/amp/nav-bar.html new file mode 100644 index 0000000000..197ed4d57c --- /dev/null +++ b/_includes/amp/nav-bar.html @@ -0,0 +1,35 @@ + diff --git a/_includes/amp/utility_bar.html b/_includes/amp/utility_bar.html new file mode 100644 index 0000000000..ff7693f32d --- /dev/null +++ b/_includes/amp/utility_bar.html @@ -0,0 +1,7 @@ +
+
+ +
+
diff --git a/_includes/archive_post.html b/_includes/archive_post.html index 2c80f8eb06..223f0b40ae 100644 --- a/_includes/archive_post.html +++ b/_includes/archive_post.html @@ -2,22 +2,50 @@ This is the template used to show an entry in archives. Archives include: "by tags" and "by time" archives. {% endcomment %} -
  • -
    -
    -

    - {{ post.title }} - -

    - {% include entry_metadata_home.html %} - {% if post.excerpt %} -
    - {{ post.excerpt | markdownify }} - Continue → -
    - {% else %} -
    {{ post.content }}
    + +{% assign current = page %} +{% assign is_banner_post = false %} + +{% if page.is_post_index %} + {% assign current = post %} +{% endif %} + +
  • \ No newline at end of file + diff --git a/_includes/asides/README.md b/_includes/asides/README.md new file mode 100644 index 0000000000..fd5c85e912 --- /dev/null +++ b/_includes/asides/README.md @@ -0,0 +1,59 @@ +# Auth0 Asides + +Auth0 Aside includes may be used when: + +* it is illogical to integrate Auth0 into the tutorial's main content +* tutorial has no application sample (is conceptual or is a technical announcement) +* business article doesn't require a tutorial aside, but does need information on Auth0 (in this case, use the [about-auth0.markdown](https://github.com/auth0/blog/blob/master/_includes/asides/about-auth0.markdown) aside) + +> ⚠ **IMPORTANT:** It is _strongly preferred_ that Auth0 be integrated into the main content of your post, or that Asides be specifically tailored to the app sample used in the tutorial. These generic Aside includes should be used _sparingly_. + +## How to Use Asides in Posts + +To include an Auth0 Aside in your post markdown, use the following syntax: + +
    +{% include asides/{technology}.markdown %}
    +
    + +## Technical Aside Requirements + +Technical Auth0 Asides **must**: + +* have instructions on how to set up an Auth0 application +* authenticate the app using universal login page +* have a supporting sample repo at [auth0-blog](https://github.com/auth0-blog) + +Technical Auth0 Asides **should**: + +* have one image, [such as the one here](https://cdn2.auth0.com/blog/angular-aside/angular-aside-login.jpg) +* show authentication of an API as well as an Application, if reasonable +* have links to supporting documentation or articles where users can find more information on Auth0 and how it works + +## Maintenance + +If you are the author of a generic Aside, you must **make sure** you keep the content and the supporting sample repo up-to-date. + +## Author List (for contact) + +When you create a new Aside, add a link to it here along with author's name so all writers know who to contact in case they need to use your Aside and have questions. + +### Non-Technical Aside + +* [about-auth0.markdown](https://github.com/auth0/blog/blob/master/_includes/asides/about-auth0.markdown) - Jeana + +### Market Basket Links Aside (semi-technical, but no code) + +* [market-basket-links-about.markdown](https://github.com/auth0/blog/blob/master/_includes/asides/market-basket-links-about.markdown) - Bruno + +### Technical Asides + +* [angular.markdown](https://github.com/auth0/blog/blob/master/_includes/asides/angular.markdown) - Kim +* [javascript-at-auth0.markdown](https://github.com/auth0/blog/blob/master/_includes/asides/javascript-at-auth0.markdown) - Bruno & Sebastian +* [python.markdown](https://github.com/auth0/blog/blob/master/_includes/asides/python.markdown) - Bruno +* [go.markdown](https://github.com/auth0/blog/blob/master/_includes/asides/go.markdown) - Prosper +* [php.markdown](https://github.com/auth0/blog/blob/master/_includes/asides/php.markdown) - Prosper +* [ruby.markdown](https://github.com/auth0/blog/blob/master/_includes/asides/ruby.markdown) - Prosper +* [react.markdown](https://github.com/auth0/blog/blob/master/_includes/asides/react.markdown) - Bruno +* [vue.markdown](https://github.com/auth0/blog/blob/master/_includes/asides/vue.markdown) - Prosper +* [spring-boot.markdown](https://github.com/auth0/blog/blob/master/_includes/asides/spring-boot.markdown) - Bruno diff --git a/_includes/asides/about-auth0.markdown b/_includes/asides/about-auth0.markdown new file mode 100644 index 0000000000..b9e1f3be24 --- /dev/null +++ b/_includes/asides/about-auth0.markdown @@ -0,0 +1,5 @@ +## About Auth0 + +Auth0, a global leader in Identity-as-a-Service (IDaaS), provides thousands of enterprise customers with a Universal Identity Platform for their web, mobile, IoT, and internal applications. Its extensible platform seamlessly authenticates and secures more than 1.5B logins per month, making it loved by developers and trusted by global enterprises. The company's U.S. headquarters in Bellevue, WA, and additional offices in Buenos Aires, London, Tokyo, and Sydney, support its customers that are located in 70+ countries. + +For more information, visit [https://auth0.com](https://auth0.com/) or follow [@auth0 on Twitter](https://twitter.com/auth0). diff --git a/_includes/asides/android.markdown b/_includes/asides/android.markdown new file mode 100644 index 0000000000..12a5933263 --- /dev/null +++ b/_includes/asides/android.markdown @@ -0,0 +1,161 @@ +## Aside: Securing Android Apps with Auth0 + +Securing applications with Auth0 is very easy and brings a lot of great features to the table. With Auth0, we only have to write a few lines of code to get solid [identity management solution](https://auth0.com/user-management), +[single sign-on](https://auth0.com/docs/sso/single-sign-on), support for [social identity providers (like Facebook, GitHub, Twitter, etc.)](https://auth0.com/docs/identityproviders), and support for [enterprise identity providers (Active Directory, LDAP, SAML, custom, etc.)](https://auth0.com/enterprise). + +In the following sections, we are going to learn how to use Auth0 to secure Android apps. As we will see, the process is simple and fast. + +### Dependencies + +To secure Android apps with Auth0, we just need to import the [Auth0.Android](https://github.com/auth0/Auth0.Android) library. This library is a toolkit that let us communicate with many of the basic Auth0 API functions in a neat way. + +To import this library, we have to include the following dependency in our `build.gradle` file: + +```groovy +dependencies { + compile 'com.auth0.android:auth0:1.12.0' +} +``` + +After that, we need to open our app's `AndroidManifest.xml` file and add the following permission: + +```xml + +``` + +### Create an Auth0 Application + +After importing the library and adding the permission, we need to register the application in our Auth0 dashboard. By the way, if we don't have an Auth0 account, this is a great time to create a **free** one. + +In the Auth0 dashboard, we have to go to _Applications_ and then click on the _Create Application_ button. In the form that is shown, we have to define a name for the application and select the _Native_ type for it. After that, we can hit the _Create_ button. This will lead us to a screen similar to the following one: + +![Android application on Auth0's dashboard](https://cdn2.auth0.com/docs/media/articles/angularjs/app_dashboard.png) + +On this screen, we have to configure a callback URL. This is a URL in our Android app where Auth0 redirects the user after they have authenticated. + +We need to whitelist the callback URL for our Android app in the _Allowed Callback URLs_ field in the _Settings_ page of our Auth0 application. If we do not set any callback URL, our users will see a mismatch error when they log in. + +```bash +demo://bkrebs.auth0.com/android/OUR_APP_PACKAGE_NAME/callback +``` + +Let's not forget to replace OUR_APP_PACKAGE_NAME with our Android application's package name. We can find this name in the `applicationId` attribute of the `app/build.gradle` file. + +### Set Credentials + +Our Android application needs some details from Auth0 to communicate with it. We can get these details from the _Settings_ section for our Auth0 application in the [Auth0 dashboard](https://manage.auth0.com/#applications). + +We need the following information: + +- Client ID +- Domain + +It's suggested that we do not hardcode these values as we may need to change them in the future. Instead, let's use String Resources, such as `@string/com_auth0_domain`, to define the values. + +Let's edit our `res/values/strings.xml` file as follows: + +```xml + + 2qu4Cxt4h2x9In7Cj0s7Zg5FxhKpjooK + bkrebs.auth0.com + +``` + +These values have to be replaced by those found in the _Settings_ section of our Auth0 application. + +### Android Login + +To implement the login functionality in our Android app, we need to add manifest placeholders required by the SDK. These placeholders are used internally to define an `intent-filter` that captures the authentication callback URL configured previously. + +To add the manifest placeholders, let's add the next line: + +```groovy +apply plugin: 'com.android.application' +android { + compileSdkVersion 25 + buildToolsVersion "25.0.3" + defaultConfig { + applicationId "com.auth0.samples" + minSdkVersion 15 + targetSdkVersion 25 + //... + + //---> Add the next line + manifestPlaceholders = [auth0Domain: "@string/com_auth0_domain", auth0Scheme: "demo"] + //<--- + } +} +``` + +After that, we have to run **Sync Project with Gradle Files** inside Android Studio or execute `./gradlew clean assembleDebug` from the command line. + +### Start the Authentication Process + +The [Auth0 login page](https://auth0.com/docs/hosted-pages/login) is the easiest way to set up authentication in our application. It's recommended using the Auth0 login page for the best experience, best security, and the fullest array of features. + +Now we have to implement a method to start the authentication process. Let's call this method `login` and add it to our `MainActivity` class. + +```java +private void login() { + Auth0 auth0 = new Auth0(this); + auth0.setOIDCConformant(true); + WebAuthProvider.init(auth0) + .withScheme("demo") + .withAudience(String.format("https://%s/userinfo", getString(R.string.com_auth0_domain))) + .start(MainActivity.this, new AuthCallback() { + @Override + public void onFailure(@NonNull Dialog dialog) { + // Show error Dialog to user + } + + @Override + public void onFailure(AuthenticationException exception) { + // Show error to user + } + + @Override + public void onSuccess(@NonNull Credentials credentials) { + // Store credentials + // Navigate to your main activity + } + }); +} +``` + +As we can see, we had to create a new instance of the Auth0 class to hold user credentials. We can use a constructor that receives an Android Context if we have added the following String resources: + +- `R.string.com_auth0_client_id` +- `R.string.com_auth0_domain` + +If we prefer to hardcode the resources, we can use the constructor that receives both strings. Then, we can use the `WebAuthProvider` class to authenticate with any connection enabled on our application in the [Auth0 dashboard](https://manage.auth0.com/#applications). + +After we call the `WebAuthProvider#start` function, the browser launches and shows the Auth0 login page. Once the user authenticates, the callback URL is called. The callback URL contains the final result of the authentication process. + +### Capture the Result + +After authentication, the browser redirects the user to our application with the authentication result. The SDK captures the result and parses it. + +> We do not need to declare a specific `intent-filter` for our activity because we have defined the manifest placeholders with we Auth0 Domain and Scheme values. + +The `AndroidManifest.xml` file should look like this: + +```xml + + + + + + + + + + + +``` + +That's it, we now have an Android application secured with Auth0. To learn more about this, we can check the [official documentation](https://auth0.com/docs/quickstart/native/android/). There, we will find more topics like [Session Handling](https://auth0.com/docs/quickstart/native/android/03-session-handling) and [Fetching User Profile](User Profile). diff --git a/_includes/asides/angular.markdown b/_includes/asides/angular.markdown new file mode 100644 index 0000000000..800dc12c0d --- /dev/null +++ b/_includes/asides/angular.markdown @@ -0,0 +1,309 @@ +## Aside: Authenticate an Angular App and Node API with Auth0 + +We can protect our applications and APIs so that only authenticated users can access them. Let's explore how to do this with an Angular application and a Node API using [Auth0](https://auth0.com). You can clone this sample app and API from the [angular-auth0-aside repo on GitHub](https://github.com/auth0-blog/angular-auth0-aside). + +![Auth0 login screen](https://cdn.auth0.com/blog/resources/auth0-centralized-login.jpg) + +### Features + +The [sample Angular application and API](https://github.com/auth0-blog/angular-auth0-aside) has the following features: + +* Angular application generated with [Angular CLI](https://github.com/angular/angular-cli) and served at [http://localhost:4200](http://localhost:4200) +* Authentication with [auth0.js](https://auth0.com/docs/libraries/auth0js/v8) using a login page +* Node server protected API route `http://localhost:3001/api/dragons` returns JSON data for authenticated `GET` requests +* Angular app fetches data from API once user is authenticated with Auth0 +* Profile page requires authentication for access using route guards +* Authentication service uses a subject to propagate authentication status events to the entire app +* User profile is fetched on authentication and stored in authentication service +* Access token, profile, and token expiration are stored in local storage and removed upon logout + +### Sign Up for Auth0 + +You'll need an [Auth0](https://auth0.com) account to manage authentication. You can sign up for a free account here. Next, set up an Auth0 application and API so Auth0 can interface with an Angular app and Node API. + +### Set Up an Auth0 Application + +1. Go to your [**Auth0 Dashboard**](https://manage.auth0.com/#/) and click the "[create a new application](https://manage.auth0.com/#/applications/create)" button. +2. Name your new app and select "Single Page Web Applications". +3. In the **Settings** for your new Auth0 app, add `http://localhost:4200/callback` to the **Allowed Callback URLs**. Click the "Save Changes" button. +4. If you'd like, you can [set up some social connections](https://manage.auth0.com/#/connections/social). You can then enable them for your app in the **Application** options under the **Connections** tab. The example shown in the screenshot above utilizes username/password database, Facebook, Google, and Twitter. For production, make sure you set up your own social keys and do not leave social connections set to use Auth0 dev keys. + +> **Note:** Under the **OAuth** tab of **Advanced Settings** (at the bottom of the **Settings** section) you should see that the **JsonWebToken Signature Algorithm** is set to `RS256`. This is the default for new applications. If it is set to `HS256`, please change it to `RS256`. You can [read more about RS256 vs. HS256 JWT signing algorithms here](https://community.auth0.com/questions/6942/jwt-signing-algorithms-rs256-vs-hs256). + +### Set Up an API + +1. Go to [**APIs**](https://manage.auth0.com/#/apis) in your Auth0 dashboard and click on the "Create API" button. Enter a name for the API. Set the **Identifier** to your API endpoint URL. In this example, this is `http://localhost:3001/api/`. The **Signing Algorithm** should be `RS256`. +2. You can consult the Node.js example under the **Quick Start** tab in your new API's settings. We'll implement our Node API in this fashion, using [Express](https://expressjs.com/), [express-jwt](https://github.com/auth0/express-jwt), and [jwks-rsa](https://github.com/auth0/node-jwks-rsa). + +We're now ready to implement Auth0 authentication on both our Angular client and Node backend API. + +### Dependencies and Setup + +The Angular app utilizes the [Angular CLI](https://github.com/angular/angular-cli). Make sure you have the CLI installed globally: + +```bash +$ npm install -g @angular/cli +``` + +Once you've cloned [the project](https://github.com/auth0-blog/angular-auth0-aside), install the Node dependencies for both the Angular app and the Node server by running the following commands in the root of your project folder: + +```bash +$ npm install +$ cd server +$ npm install +``` + +The Node API is located in the [`/server` folder](https://github.com/auth0-blog/angular-auth0-aside/tree/master/server) at the root of our sample application. + +Find the [`config.js.example` file](https://github.com/auth0-blog/angular-auth0-aside/blob/master/server/config.js.example) and **remove** the `.example` extension from the filename. Then open the file: + +```js +// server/config.js (formerly config.js.example) +module.exports = { + CLIENT_DOMAIN: '[AUTH0_CLIENT_DOMAIN]', // e.g. 'you.auth0.com' + AUTH0_AUDIENCE: 'http://localhost:3001/api/' +}; +``` + +Change the `AUTH0_CLIENT_DOMAIN` identifier to your Auth0 application domain and set the `AUTH0_AUDIENCE` to your audience (in this example, this is `http://localhost:3001/api/`). The `/api/dragons` route will be protected with [express-jwt](https://github.com/auth0/express-jwt) and [jwks-rsa](https://github.com/auth0/node-jwks-rsa). + +> **Note:** To learn more about RS256 and JSON Web Key Set, read [Navigating RS256 and JWKS](https://auth0.com/blog/navigating-rs256-and-jwks/). + +Our API is now protected, so let's make sure that our Angular application can also interface with Auth0. To do this, we'll activate the [`src/app/auth/auth0-variables.ts.example` file](https://github.com/auth0-blog/angular-auth0-aside/blob/master/src/app/auth/auth0-variables.ts.example) by deleting `.example` from the file extension. Then open the file and change the `[AUTH0_CLIENT_ID]` and `[AUTH0_CLIENT_DOMAIN]` strings to your Auth0 information: + +```js +// src/app/auth/auth0-variables.ts (formerly auth0-variables.ts.example) +... +export const AUTH_CONFIG: AuthConfig = { + CLIENT_ID: '[AUTH0_CLIENT_ID]', + CLIENT_DOMAIN: '[AUTH0_CLIENT_DOMAIN]', + ... +``` + +Our app and API are now set up. They can be served by running `ng serve` from the root folder and `node server.js` from the `/server` folder. + +With the Node API and Angular app running, let's take a look at how authentication is implemented. + +### Authentication Service + +Authentication logic on the front end is handled with an `AuthService` authentication service: [`src/app/auth/auth.service.ts` file](https://github.com/auth0-blog/angular-auth0-aside/blob/master/src/app/auth/auth.service.ts). + +```js +// src/app/auth/auth.service.ts +import { Injectable } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; +import * as auth0 from 'auth0-js'; +import { AUTH_CONFIG } from './auth0-variables'; +import { UserProfile } from './profile.model'; + +(window as any).global = window; + +@Injectable() +export class AuthService { + // Create Auth0 web auth instance + // @TODO: Update AUTH_CONFIG and remove .example extension in src/app/auth/auth0-variables.ts.example + private _auth0 = new auth0.WebAuth({ + clientID: AUTH_CONFIG.CLIENT_ID, + domain: AUTH_CONFIG.CLIENT_DOMAIN, + responseType: 'token', + redirectUri: AUTH_CONFIG.REDIRECT, + audience: AUTH_CONFIG.AUDIENCE, + scope: AUTH_CONFIG.SCOPE + }); + userProfile: UserProfile; + accessToken: string; + + // Create a stream of logged in status to communicate throughout app + loggedIn: boolean; + loggedIn$ = new BehaviorSubject(this.loggedIn); + + constructor() { + // You can restore an unexpired authentication session on init + // by using the checkSession() endpoint from auth0.js: + // https://auth0.com/docs/libraries/auth0js/v9#using-checksession-to-acquire-new-tokens + } + + private _setLoggedIn(value: boolean) { + // Update login status subject + this.loggedIn$.next(value); + this.loggedIn = value; + } + + login() { + // Auth0 authorize request + this._auth0.authorize(); + } + + handleLoginCallback() { + // When Auth0 hash parsed, get profile + this._auth0.parseHash((err, authResult) => { + if (authResult && authResult.accessToken) { + window.location.hash = ''; + this.getUserInfo(authResult); + } else if (err) { + console.error(`Error: ${err.error}`); + } + }); + } + + getUserInfo(authResult) { + // Use access token to retrieve user's profile and set session + this._auth0.client.userInfo(authResult.accessToken, (err, profile) => { + this._setSession(authResult, profile); + }); + } + + private _setSession(authResult, profile) { + const expTime = authResult.expiresIn * 1000 + Date.now(); + // Save session data and update login status subject + localStorage.setItem('expires_at', JSON.stringify(expTime)); + this.accessToken = authResult.accessToken; + this.userProfile = profile; + this._setLoggedIn(true); + } + + logout() { + // Remove token and profile and update login status subject + localStorage.removeItem('expires_at'); + this.accessToken = undefined; + this.userProfile = undefined; + this._setLoggedIn(false); + } + + get authenticated(): boolean { + // Check if current date is greater than expiration + // and user is currently logged in + const expiresAt = JSON.parse(localStorage.getItem('expires_at')); + return (Date.now() < expiresAt) && this.loggedIn; + } + +} +``` + +This service uses the config variables from `auth0-variables.ts` to instantiate an `auth0.js` WebAuth instance. + +An [RxJS `BehaviorSubject`](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/subjects/behaviorsubject.md) is used to provide a stream of authentication status events that you can subscribe to anywhere in the app. + +The `login()` method authorizes the authentication request with Auth0 using your config variables. A login page will be shown to the user and they can then log in. + +> **Note:** If it's the user's first visit to our app _and_ our callback is on `localhost`, they'll also be presented with a consent screen where they can grant access to our API. A first party client on a non-localhost domain would be highly trusted, so the consent dialog would not be presented in this case. You can modify this by editing your [Auth0 Dashboard API](https://manage.auth0.com/#/apis) **Settings**. Look for the "Allow Skipping User Consent" toggle. + +We'll receive `accessToken` and `expiresIn` in the hash from Auth0 when returning to our app. The `handleLoginCallback()` method uses Auth0's `parseHash()` method callback to get the user's profile (`getUserInfo()`) and set the session (`_setSession()`) by saving the token, profile, and token expiration and updating the `loggedIn$` subject so that any subscribed components in the app are informed that the user is now authenticated. + +> **Note:** The profile takes the shape of [`profile.model.ts`](https://github.com/auth0-blog/angular-auth0-aside/blob/master/src/app/auth/profile.model.ts) from the [OpenID standard claims](https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims). + +Finally, we have a `logout()` method that clears data from and updates the `loggedIn$` subject. We also have an `authenticated` accessor to return current authentication status based on presence of a token and the token's expiration. + +Once [`AuthService` is provided in `app.module.ts`](https://github.com/auth0-blog/angular-auth0-aside/blob/master/src/app/app.module.ts#L32), its methods and properties can be used anywhere in our app, such as the [home component](https://github.com/auth0-blog/angular-auth0-aside/tree/master/src/app/home). + +### Callback Component + +The [callback component](https://github.com/auth0-blog/angular-auth0-aside/tree/master/src/app/callback) is where the app is redirected after authentication. This component simply shows a loading message until the login process is completed. It executes the `handleLoginCallback()` method to parse the hash and extract authentication information. It subscribes to the `loggedIn$` Behavior Subject from our Authentication service in order to redirect back to the home page once the user is logged in, like so: + +```js +// src/app/callback/callback.component.ts +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Subscription } from 'rxjs'; +import { AuthService } from './../auth/auth.service'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'app-callback', + templateUrl: './callback.component.html', + styleUrls: ['./callback.component.css'] +}) +export class CallbackComponent implements OnInit, OnDestroy { + loggedInSub: Subscription; + + constructor(private auth: AuthService, private router: Router) { + // Parse authentication hash + auth.handleLoginCallback(); + } + + ngOnInit() { + this.loggedInSub = this.auth.loggedIn$.subscribe( + loggedIn => loggedIn ? this.router.navigate(['/']) : null + ) + } + + ngOnDestroy() { + this.loggedInSub.unsubscribe(); + } + +} +``` + +### Making Authenticated API Requests + +In order to make authenticated HTTP requests, we need to add an `Authorization` header with the access token in our [`api.service.ts` file](https://github.com/auth0-blog/angular-auth0-aside/blob/master/src/app/api.service.ts). + +```js +// src/app/api.service.ts +import { Injectable } from '@angular/core'; +import { throwError, Observable } from 'rxjs'; +import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http'; +import { catchError } from 'rxjs/operators'; +import { AuthService } from './auth/auth.service'; + +@Injectable() +export class ApiService { + private baseUrl = 'http://localhost:3001/api/'; + + constructor( + private http: HttpClient, + private auth: AuthService + ) { } + + getDragons$(): Observable { + return this.http + .get(`${this.baseUrl}dragons`, { + headers: new HttpHeaders().set( + 'Authorization', `Bearer ${this.auth.accessToken}` + ) + }) + .pipe( + catchError(this._handleError) + ); + } + + private _handleError(err: HttpErrorResponse | any) { + const errorMsg = err.message || 'Unable to retrieve data'; + return throwError(errorMsg); + } + +} +``` + +### Final Touches: Route Guard and Profile Page + +A [profile page component](https://github.com/auth0-blog/angular-auth0-aside/tree/master/src/app/profile) can show an authenticated user's profile information. However, we only want this component to be accessible if the user is logged in. + +With an [authenticated API request and login/logout](https://github.com/auth0-blog/angular-auth0-aside/blob/master/src/app/home/home.component.ts) implemented, the final touch is to protect our profile route from unauthorized access. The [`auth.guard.ts` route guard](https://github.com/auth0-blog/angular-auth0-aside/blob/master/src/app/auth/auth.guard.ts) can check authentication and activate routes conditionally. The guard is implemented on specific routes of our choosing in the [`app-routing.module.ts` file](https://github.com/auth0-blog/angular-auth0-aside/blob/master/src/app/app-routing.module.ts) like so: + +```js +// src/app/app-routing.module.ts +... +import { AuthGuard } from './auth/auth.guard'; +... + { + path: 'profile', + component: ProfileComponent, + canActivate: [ + AuthGuard + ] + }, +... +``` + +### More Resources + +That's it! We have an authenticated Node API and Angular application with login, logout, profile information, and protected routes. To learn more, check out the following resources: + +* [Why You Should Always Use Access Tokens to Secure an API](https://auth0.com/blog/why-should-use-accesstokens-to-secure-an-api/) +* [Navigating RS256 and JWKS](https://auth0.com/blog/navigating-rs256-and-jwks/) +* [Access Token](https://auth0.com/docs/tokens/access-token) +* [Verify Access Tokens](https://auth0.com/docs/api-auth/tutorials/verify-access-token) +* [Call APIs from Client-side Web Apps](https://auth0.com/docs/api-auth/grant/implicit) +* [How to implement the Implicit Grant](https://auth0.com/docs/api-auth/tutorials/implicit-grant) +* [Auth0.js Documentation](https://auth0.com/docs/libraries/auth0js) +* [OpenID Standard Claims](https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims) diff --git a/_includes/asides/angularjs.markdown b/_includes/asides/angularjs.markdown new file mode 100644 index 0000000000..7d5598b879 --- /dev/null +++ b/_includes/asides/angularjs.markdown @@ -0,0 +1,268 @@ +## Aside: Authenticate an AngularJS App with Auth0 + +We can protect our applications and APIs so that only authenticated users can access them. Let's explore how to do this with an Angular application using [Auth0](https://auth0.com). You can clone this sample app from the [repo on GitHub](https://github.com/auth0-samples/auth0-angularjs-samples/blob/master/01-Login). + +![Auth0 login screen](https://cdn.auth0.com/blog/resources/auth0-centralized-login.jpg) + +### Sign Up for Auth0 + +You'll need an [Auth0](https://auth0.com) account to manage authentication. You can sign up for a free account here. Next, set up an Auth0 application and API so Auth0 can interface with an Angular app and Node API. + +### Set Up an Auth0 Application + +1. Go to your [**Auth0 Dashboard**](https://manage.auth0.com/#/) and click the "[create a new application](https://manage.auth0.com/#/applications/create)" button. +2. Name your new app and select "Single Page Web Applications". +3. In the **Settings** for your new Auth0 app, add `http://localhost:3000/callback` to the **Allowed Callback URLs**. Click the "Save Changes" button. +4. If you'd like, you can [set up some social connections](https://manage.auth0.com/#/connections/social). You can then enable them for your app in the **Application** options under the **Connections** tab. The example shown in the screenshot above utilizes username/password database, Facebook, Google, and Twitter. For production, make sure you set up your own social keys and do not leave social connections set to use Auth0 dev keys. + +> **Note:** Under the **OAuth** tab of **Advanced Settings** (at the bottom of the **Settings** section) you should see that the **JsonWebToken Signature Algorithm** is set to `RS256`. This is the default for new applications. If it is set to `HS256`, please change it to `RS256`. You can [read more about RS256 vs. HS256 JWT signing algorithms here](https://community.auth0.com/questions/6942/jwt-signing-algorithms-rs256-vs-hs256). + +### Dependencies and Setup + +Once you've cloned [the project](https://github.com/auth0-samples/auth0-angularjs-samples/blob/master/01-Login), install the dependencies for both the AngularJS app and the Node server by running the following commands in the root of your project folder: + +```bash +$ npm install +``` + +Start the app via the express server with: + +```bash +$ npm start +``` + + +Find the [`auth0-variables.js.example` file](https://github.com/auth0-samples/auth0-angularjs-samples/blob/master/01-Login/auth0-variables.js.example) and **remove** the `.example` extension from the filename. Then open the file: + +```js +var AUTH0_CLIENT_ID='{CLIENT_ID}'; +var AUTH0_DOMAIN='{DOMAIN}'; +var AUTH0_CALLBACK_URL='http://localhost:3000/callback'; +``` + +Change the `AUTH0_DOMAIN` identifier to your Auth0 application domain. + +> **Note:** To learn more about RS256 and JSON Web Key Set, read [Navigating RS256 and JWKS](https://auth0.com/blog/navigating-rs256-and-jwks/). + +Change the `AUTH0_CLIENT_ID` to your Auth0 information. + +let's take a look at how authentication is implemented. + +### Authentication Service + +Authentication logic on the front end is handled with an `AuthService` authentication service: [`src/app/auth/auth.service.js` file](https://github.com/auth0-samples/auth0-angularjs-samples/blob/master/01-Login/app/auth/auth.service.js). + +```js +(function () { + + 'use strict'; + + angular + .module('app') + .service('authService', authService); + + authService.$inject = ['$state', 'angularAuth0', '$timeout']; + + function authService($state, angularAuth0, $timeout) { + + function login() { + angularAuth0.authorize(); + } + + function handleAuthentication() { + angularAuth0.parseHash(function(err, authResult) { + if (authResult && authResult.accessToken && authResult.idToken) { + setSession(authResult); + $state.go('home'); + } else if (err) { + $timeout(function() { + $state.go('home'); + }); + console.log(err); + alert('Error: ' + err.error + '. Check the console for further details.'); + } + }); + } + + function setSession(authResult) { + // Set the time that the access token will expire at + let expiresAt = JSON.stringify((authResult.expiresIn * 1000) + new Date().getTime()); + localStorage.setItem('access_token', authResult.accessToken); + localStorage.setItem('id_token', authResult.idToken); + localStorage.setItem('expires_at', expiresAt); + } + + function logout() { + // Remove tokens and expiry time from localStorage + localStorage.removeItem('access_token'); + localStorage.removeItem('id_token'); + localStorage.removeItem('expires_at'); + $state.go('home'); + } + + function isAuthenticated() { + // Check whether the current time is past the + // access token's expiry time + let expiresAt = JSON.parse(localStorage.getItem('expires_at')); + return new Date().getTime() < expiresAt; + } + + return { + login: login, + handleAuthentication: handleAuthentication, + logout: logout, + isAuthenticated: isAuthenticated + } + } +})(); +``` + +The `login()` method authorizes the authentication request with Auth0 using your config variables. A login page will be shown to the user and they can then log in. + +* **handleAuthentication:** looks for the result of authentication in the URL hash. Then, the result is processed with the parseHash method from auth0.js +* **setSession:** sets the user's Access Token and ID Token, and the Access Token's expiry time +* **logout:** removes the user's tokens and expiry time from browser storage +* **isAuthenticated:** checks whether the expiry time for the user's Access Token has passed + +> **Note:** If it's the user's first visit to our app _and_ our callback is on `localhost`, they'll also be presented with a consent screen where they can grant access to our API. A first party client on a non-localhost domain would be highly trusted, so the consent dialog would not be presented in this case. You can modify this by editing your [Auth0 Dashboard API](https://manage.auth0.com/#/apis) **Settings**. Look for the "Allow Skipping User Consent" toggle. + +### Login Control + +Provide a component with controls for the user to log in and log out. + +_app/navbar/navbar.html_ + +{% highlight html %} +{% raw %} + +{% endraw %} +{% endhighlight %} + +### Directive + +```js +// app/navbar/navbar.directive.js + +(function() { + + 'use strict'; + + angular + .module('app') + .directive('navbar', navbar); + + function navbar() { + return { + templateUrl: 'app/navbar/navbar.html', + controller: navbarController, + controllerAs: 'vm' + } + } + + navbarController.$inject = ['authService']; + + function navbarController(authService) { + var vm = this; + vm.auth = authService; + } + +})(); +``` + +Depending on whether the user is authenticated or not, they see the Log Out or Log In button. The `ng-click` events on the buttons make calls to the `authService` service to let the user log in or out. When the user clicks **Log In**, they are redirected to the login page. + + +### Callback Component + +The [callback component](https://github.com/auth0-samples/auth0-angularjs-samples/tree/master/01-Login/app/callback) is where the app is redirected after authentication. This component simply shows a loading message until the login process is completed. After the session is set up, the users are redirected to the `/home` route. + +```js +// app/callback/callback.controller.js + +(function () { + + 'use strict'; + + angular + .module('app') + .controller('CallbackController', callbackController); + + function callbackController() {} + +})(); +``` + + +_app/callback/callback.html_ + +{% highlight html %} +{% raw %} +
    + loading +
    +{% endraw %} +{% endhighlight %} + +### Process Authentication Result + +When a user authenticates at the login page, they are redirected to your application. Their URL contains a hash fragment with their authentication information. The `handleAuthentication` method in the `authService` service processes the hash. + +Call the `handleAuthentication` method in your app's run block. The method processess the authentication hash while your app loads. + +```js +// app/app.run.js + +(function () { + + 'use strict'; + + angular + .module('app') + .run(run); + + run.$inject = ['authService']; + + function run(authService) { + // Handle the authentication + // result in the hash + authService.handleAuthentication(); + } + +})(); +``` + +### More Resources + +That's it! We have an authenticated AngularJS application with login, logout, and protected routes. To learn more, check out the following resources: + +* [Why You Should Always Use Access Tokens to Secure an API](https://auth0.com/blog/why-should-use-accesstokens-to-secure-an-api/) +* [Navigating RS256 and JWKS](https://auth0.com/blog/navigating-rs256-and-jwks/) +* [Access Token](https://auth0.com/docs/tokens/access-token) +* [Verify Access Tokens](https://auth0.com/docs/api-auth/tutorials/verify-access-token) +* [Call APIs from Client-side Web Apps](https://auth0.com/docs/api-auth/grant/implicit) +* [How to implement the Implicit Grant](https://auth0.com/docs/api-auth/tutorials/implicit-grant) +* [Auth0.js Documentation](https://auth0.com/docs/libraries/auth0js) +* [OpenID Standard Claims](https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims) diff --git a/_includes/asides/express-gateway.markdown b/_includes/asides/express-gateway.markdown new file mode 100644 index 0000000000..a8ecece429 --- /dev/null +++ b/_includes/asides/express-gateway.markdown @@ -0,0 +1,73 @@ +### Aside: Configure Express Gateway to use Auth0 Identity Management + +Express Gateway and Auth0 play very well together when it comes to security. + +Let's now configure Auth0 to work as our [user management system](https://auth0.com/user-management). + +With Auth0, we only have to write a few lines of code to get solid [identity management solution](https://auth0.com/user-management), +[single sign-on](https://auth0.com/docs/sso/single-sign-on), support for [social identity providers (like Facebook, GitHub, Twitter, etc.)](https://auth0.com/docs/identityproviders), and support for [enterprise identity providers (Active Directory, LDAP, SAML, custom, etc.)](https://auth0.com/enterprise). + +If you don't already have an Auth0 account, sign up for a free one now. + +From the [Auth0 management dashboard](https://manage.auth0.com/), click on the **APIs** menu item, and then on the **Create API** button. You will need to give your API a name and an identifier. The name can be anything you choose, so make it as descriptive as you want. The identifier will be used to identify your API, this field cannot be changed once set. + +For our example, I'll name the API **billings** and identify it as `http://orders`. I'll also leave the signing algorithm as `RS256` and click on the **Create API** button. + +![Creating billings API on Auth0](https://cdn.auth0.com/blog/express-gateway/create-auth0-api.png) + +Now, point your browser to `https://yourAPI.auth0.com/pem` (where `yourAPI` is the Auth0 domain that you chose when creating your account) and download the **public key** file. + +This is the key that we will use to verify that the JSON Web Tokens (JWTs) issued by Auth0 are valid. Save it as `pubKey.pem` and place it in the same directory specified in `secretOrPublicKeyFile` parameter of the `jwt` policy (that is, in a directory called `key` in the project root). + +The API Gateway has now been configured correctly to handle the scenarios. + +### Enable JWT verification in Express Gateway + +Express Gateway can be configured to validate tokens provided by Auth0 by installing the `JWT` policy in any of the [pipelines](https://www.express-gateway.io/docs/core-concepts/#pipelines). + +```yml + policies: + # Other policies + - jwt: + - action: + secretOrPublicKeyFile: ./key/pubKey.pem + checkCredentialExistence: false +``` + +### Test Drive + +Start the gateway using `npm start` in the project root. Once running, let's try to issue a couple of requests to it: + +```shell +$ curl http://localhost:8080 +$ Unauthorized +``` + +You can see that the first request has been denied with `Unauthorized` status. That's because we didn't provide any JWT with the request, so it didn't go through. + +Now grab any HTTP client and let's configure it to start an OAuth 2.0 authorization process against Auth0. We can grab all the necessary parameters going on _Applications_ -> _Billings (Test Application)_ -> _Settings_ + +In my case, I am going to use `curl`, but you can use the client you prefer: + +```bash +curl --request POST \ + --url https://{AUTH0_DOMAIN}.auth0.com/oauth/token \ + --header 'content-type: application/json' \ + --data '{ + "client_id":"{AUTH0_CLIENT_ID}", + "client_secret":"{AUTH0_CLIENT_SECRET}", + "audience":"http://orders", + "grant_type":"client_credentials" +}' +``` + +**Note:** Make sure to replace all the placeholders with real values provided by Auth0. + +Now, by simply copying the `access_token` attribute from the response, we will be able to communicate with the API through Express Gateway (you can verify the returned token by using [JWT.io](https://jwt.io/#debugger)). This is the token to be used in order to access the protected resource. So, just try to issue requests making sure that the token is now sent as a `Bearer` Authorization to the endpoint. The response should hopefully be `200`. + +```bash +export JWT="ey...the-rest-of-the-token" +curl -H "Authorization: Bearer "$JWT http://localhost:8080 +``` + +We made it! Now all the request that go in any pipelines using the `JWT` policy will be checked and verified. diff --git a/_includes/asides/go.markdown b/_includes/asides/go.markdown new file mode 100644 index 0000000000..29067af21e --- /dev/null +++ b/_includes/asides/go.markdown @@ -0,0 +1,178 @@ +## Aside: Securing Go APIs with Auth0 + +Securing Go APIs with Auth0 is really very easy. No sweating. No hitting of heads on the wall. Just straight-forward. With Auth0, we only have to write a few lines of code to get: + +- A solid [identity management solution](https://auth0.com/user-management), including [single sign-on](https://auth0.com/docs/sso/single-sign-on) +- [User management](https://auth0.com/docs/user-profile) +- Support for [social identity providers (like Facebook, GitHub, Twitter, etc.)](https://auth0.com/docs/identityproviders) +- [Enterprise identity providers (Active Directory, LDAP, SAML, etc.)](https://auth0.com/enterprise) +- Our [own database of users](https://auth0.com/docs/connections/database/mysql) + +### Sign Up for Auth0 + +You'll need an [Auth0](https://auth0.com) account to manage authentication. You can sign up for a free account here. Next, set up an Auth0 API. + +### Set Up an API + +1. Go to [**APIs**](https://manage.auth0.com/#/apis) in your Auth0 dashboard and click on the **Create API** button. Enter a name for the API. Set the **Identifier** to a URL(_existent or non-existent URL_). The **Signing Algorithm** should be `RS256`. + +![Create API on Auth0 dashboard](https://cdn2.auth0.com/docs/media/articles/api-auth/create-api.png) +_Create API on Auth0 dashboard_ + +We're now ready to implement Auth0 authentication on our Go backend API. + +### Dependencies and Setup + +Install the following packages like so: + +```bash +go get "gopkg.in/square/go-jose.v2" +go get "github.com/auth0-community/go-auth0" +go get "github.com/gorilla/mux" +``` + +### JWT Verification Service + +```go +// main.go + +const JWKS_URI = "{AUTH0_DOMAIN}/.well-known/jwks.json" // e.g https://kabiyesi.auth0.com +const AUTH0_API_ISSUER = "AUTH0_DOMAIN" // e.g https://kabiyesi.auth0.com + +var AUTH0_API_AUDIENCE = []string{"API_AUDIENCE"} // e.g. https://api.mysite.com + +func checkJwt(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + client := auth0.NewJWKClient(auth0.JWKClientOptions{URI: JWKS_URI}) + audience := AUTH0_API_AUDIENCE + + configuration := auth0.NewConfiguration(client, audience, AUTH0_API_ISSUER, jose.RS256) + validator := auth0.NewValidator(configuration) + + token, err := validator.ValidateRequest(r) + + if err != nil { + fmt.Println("Token is not valid or missing token") + + response := Response{ + Message: "Missing or invalid token.", + } + + w.WriteHeader(http.StatusUnauthorized) + json.NewEncoder(w).Encode(response) + + } else { + h.ServeHTTP(w, r) + } + }) +} +``` + +Change the `AUTH0_DOMAIN` variable to your Auth0 domain and set the `API_AUDIENCE` to the **Identifier** you chose while creating the API from the Auth0 dashboard. + +> **Note:** To learn more about RS256 and JSON Web Key Set, read [Navigating RS256 and JWKS](https://auth0.com/blog/navigating-rs256-and-jwks/). + +### Configure Scopes + +The `checkJwt` middleware above verifies that the `access_token` included in the request is valid; however, it doesn't yet include any mechanism for checking that the token has the sufficient scope to access the requested resources. + +Scopes provide a way for you to define which resources should be accessible by the user holding a given `access_token`. For example, you might choose to permit `read` access to a `messages` resource if a user has a **manager** access level, or a `write` access to that resource if they are an administrator. + +To configure scopes in your Auth0 dashboard, navigate to your API and choose the **Scopes** tab. In this area you can apply any scopes you wish, including one called `read:messages`, which will be used in this example. + +Let's extend our backend to check and ensure the `access_token` has the correct scope before returning a successful response. + +```go +// main.go + +func checkScope(r *http.Request, validator *auth0.JWTValidator, token *jwt.JSONWebToken) bool { + claims := map[string]interface{}{} + err := validator.Claims(r, token, &claims) + + if err != nil { + fmt.Println(err) + return false + } + + if strings.Contains(claims["scope"].(string), "read:messages") { + return true + } else { + return false + } +} +``` + +Next, let's implement this `checkScope` function in our middleware. + +```go +// main.go + +func checkJwt(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Validate the access_token + if err != nil { + // Handle invalid token case + } else { + // Ensure the token has the correct scope + result := checkScope(r, validator, token) + if result == true { + // If the token is valid and we have the right scope, we'll pass through the middleware + h.ServeHTTP(w, r) + } else { + response := Response{ + Message: "You do not have the read:messages scope.", + } + w.WriteHeader(http.StatusUnauthorized) + json.NewEncoder(w).Encode(response) + } + } + }) +} +``` + +### Configure Authenticated Routes + +Then use it to secure your API endpoints like so: + +```go +// main.go + +func main() { + r := mux.NewRouter() + + // This route is always accessible + r.Handle("/api/public", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + response := Response{ + Message: "Hello from a public endpoint! You don't need to be authenticated to see this.", + } + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(response) + })) + + // This route is only accessible if the user has a valid access_token with the read:messages scope + // We are wrapping the checkJwt middleware around the handler function which will check for a + // valid token and scope. + r.Handle("/api/private", checkJwt(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + response := Response{ + Message: "Hello from a private endpoint! You need to be authenticated and have a scope of read:messages to see this.", + } + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(response) + }))) +} +``` + +Once a user hits the endpoint `/api/private`, a valid JWT `access_token` will be required and `read:messages` scope before the resource can be released. + +### More Resources + +That's it! We have an authenticated Go API with protected routes. To learn more, check out the following resources: + +* [Why You Should Always Use Access Tokens to Secure an API](https://auth0.com/blog/why-should-use-accesstokens-to-secure-an-api/) +* [Navigating RS256 and JWKS](https://auth0.com/blog/navigating-rs256-and-jwks/) +* [Access Token](https://auth0.com/docs/tokens/access-token) +* [Verify Access Tokens](https://auth0.com/docs/api-auth/tutorials/verify-access-token) +* [Call APIs from Client-side Web Apps](https://auth0.com/docs/api-auth/grant/implicit) +* [How to implement the Implicit Grant](https://auth0.com/docs/api-auth/tutorials/implicit-grant) +* [Auth0.js Documentation](https://auth0.com/docs/libraries/auth0js) +* [OpenID Standard Claims](https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims) \ No newline at end of file diff --git a/_includes/asides/javascript-at-auth0.markdown b/_includes/asides/javascript-at-auth0.markdown new file mode 100644 index 0000000000..938c0b6f41 --- /dev/null +++ b/_includes/asides/javascript-at-auth0.markdown @@ -0,0 +1,90 @@ +## Aside: Auth0 Authentication with JavaScript + +At [Auth0](https://auth0.com/), we make heavy use of full-stack JavaScript to help our customers to [manage user identities including password resets, creating and provisioning, blocking and deleting users](https://auth0.com/user-management). We also created a serverless platform, called [Auth0 Extend](https://auth0.com/extend/), that enables customers to run arbitrary JavaScript functions securely. Therefore, it must come as no surprise that using our identity management platform on JavaScript web apps is a piece of cake. + +[Auth0 offers a **free tier**](https://auth0.com/pricing) to get started with modern authentication. Check it out, or sign up for a free Auth0 account here! + +![Auth0 Login Page](https://cdn2.auth0.com/docs/media/articles/web/hosted-login.png) + +It's as easy as installing the [`auth0-js`](https://github.com/auth0/auth0.js) and [`jwt-decode`](https://github.com/auth0/jwt-decode) node modules like so: + +```bash +npm install jwt-decode auth0-js --save +``` + +Then implement the following in your JS app: + +```js +const auth0 = new auth0.WebAuth({ + clientID: "YOUR-AUTH0-CLIENT-ID", // E.g., you.auth0.com + domain: "YOUR-AUTH0-DOMAIN", + scope: "openid email profile YOUR-ADDITIONAL-SCOPES", + audience: "YOUR-API-AUDIENCES", // See https://auth0.com/docs/api-auth + responseType: "token id_token", + redirectUri: "http://localhost:9000" //YOUR-REDIRECT-URL +}); + +function logout() { + localStorage.removeItem('id_token'); + localStorage.removeItem('access_token'); + window.location.href = "/"; +} + +function showProfileInfo(profile) { + var btnLogin = document.getElementById('btn-login'); + var btnLogout = document.getElementById('btn-logout'); + var avatar = document.getElementById('avatar'); + document.getElementById('nickname').textContent = profile.nickname; + btnLogin.style.display = "none"; + avatar.src = profile.picture; + avatar.style.display = "block"; + btnLogout.style.display = "block"; +} + +function retrieveProfile() { + var idToken = localStorage.getItem('id_token'); + if (idToken) { + try { + const profile = jwt_decode(idToken); + showProfileInfo(profile); + } catch (err) { + alert('There was an error getting the profile: ' + err.message); + } + } +} + +auth0.parseHash(window.location.hash, (err, result) => { + if (err || !result) { + // Handle error + return; + } + + // You can use the ID token to get user information in the frontend. + localStorage.setItem('id_token', result.idToken); + // You can use this token to interact with server-side APIs. + localStorage.setItem('access_token', result.accessToken); + retrieveProfile(); +}); + +function afterLoad() { + // buttons + var btnLogin = document.getElementById('btn-login'); + var btnLogout = document.getElementById('btn-logout'); + + btnLogin.addEventListener('click', function() { + auth0.authorize(); + }); + + btnLogout.addEventListener('click', function() { + logout(); + }); + + retrieveProfile(); +} + +window.addEventListener('load', afterLoad); +``` + +Get [the full example using this code](https://github.com/auth0-blog/es2015-rundown-example). + +Go ahead and check out our [Quick Start tutorials](https://auth0.com/docs/quickstarts) to learn how to implement authentication using different languages and frameworks in your apps. diff --git a/_includes/asides/laravel-backend.markdown b/_includes/asides/laravel-backend.markdown new file mode 100644 index 0000000000..2759bce96f --- /dev/null +++ b/_includes/asides/laravel-backend.markdown @@ -0,0 +1,163 @@ +## Aside: Securing Laravel APIs with Auth0 + +Securing Laravel APIs with Auth0 is very easy and brings a lot of great features to the table. With Auth0, we only have to write a few lines of code to get: + +- A solid [identity management solution](https://auth0.com/user-management), including [single sign-on](https://auth0.com/docs/sso/single-sign-on) +- [User management](https://auth0.com/docs/user-profile) +- Support for [social identity providers (like Facebook, GitHub, Twitter, etc.)](https://auth0.com/docs/identityproviders) +- [Enterprise identity providers (Active Directory, LDAP, SAML, etc.)](https://auth0.com/enterprise) +- Our [own database of users](https://auth0.com/docs/connections/database/mysql) + +### Sign Up for Auth0 + +You'll need an [Auth0](https://auth0.com) account to manage authentication. You can sign up for a free account here. Next, set up an Auth0 API. + +### Set Up an API + +Go to [**APIs**](https://manage.auth0.com/#/apis) in your Auth0 dashboard and click on the "Create API" button. Enter a name for the API. Set the **Identifier** to a URL(_existent or non-existent URL_). The **Signing Algorithm** should be `RS256`. + +![Create API on Auth0 dashboard](https://cdn2.auth0.com/docs/media/articles/api-auth/create-api.png) +_Create API on Auth0 dashboard_ + +We're now ready to implement Auth0 authentication on our Laravel backend API. + +### Dependencies and Setup + +Install the `laravel-auth0` package via composer like so: + +```bash +composer require auth0/login:"~5.0" +``` + +Generate the `laravel-auth0` package config file like so: + +```bash +php artisan vendor:publish +``` + +After the file is generated, it will be located at `config/laravel-auth0.php`. Ensure you replace the placeholder values with the authentic values from the Auth0 Admin Dashboard. Double check your values with [laravel-auth0](https://github.com/auth0-samples/auth0-laravel-api-samples/blob/master/01-Authorization-RS256/config/laravel-auth0.php). + +Your `.env` file should have the `AUTH0_DOMAIN`, `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET` and `AUTH0_CALLBACK_URL` values like so: + +```bash +AUTH0_DOMAIN=kabiyesi.auth0.com +AUTH0_CLIENT_ID=xxxxxxxxxxxxxxxxxx +AUTH0_CLIENT_SECRET=xxxxxxxxxxxxxxxxx +AUTH0_AUDIENCE=http://mylaravelapi.com +AUTH0_CALLBACK_URL=null +``` + +### Activate Provider and Facade + +The `laravel-auth0` package comes with a provder called `LoginServiceProvider`. Add this to the list of application `providers`. + +```php +// config/app.php +'providers' => array( + // ... + \Auth0\Login\LoginServiceProvider::class, +); +``` + +If you would like to use the `Auth0` Facade, add it to the list of `aliases`. + +``` +// config/app.php + +'aliases' => array( + // ... + 'Auth0' => \Auth0\Login\Facade\Auth0::class, +); +``` + +The user information can now be accessed with `Auth0::getUser()`. Finally, you need to bind a class that provides a user (your app model user) each time the user is logged in or an `access_token` is decoded. You can use the `Auth0UserRepository` provided by this package or you can build your own class. + +To use `Auth0UserRepository`, add the following lines to your app's `AppServiceProvider`: + +```php +// app/Providers/AppServiceProvider.php + +public function register() +{ + + $this->app->bind( + \Auth0\Login\Contract\Auth0UserRepository::class, + \Auth0\Login\Repository\Auth0UserRepository::class + ); + +} +``` + +### Configure Authentication Driver + +The `laravel-auth0` package comes with an authentication driver called `auth0`. This driver defines a user structure that wraps the normalized user profile defined by Auth0. It doesn't actually persist the object but rather simply stores it in the session for future calls. + +This is adequate for basic testing or if you don't have a requirement to persist the user. At any point you can call `Auth::check()` to determine if there is a user logged in and `Auth::user()` to retreive the wrapper with the user information. + +Configure the driver in `config/auth.php` to use `auth0`. + +```php +// app/config/auth.php + +// ... +'providers' => [ + 'users' => [ + 'driver' => 'auth0' + ], +], +``` + +### Secure API Routes + +Your API routes are defined in `routes/api.php` for Laravel 5.3+ apps. + +```php +// routes/api.php + +json(["message" => "Hello from a public endpoint! You don't need any token to access this URL..Yaaaay!"]); +}); + +Route::get('/wakanda', function (Request $request) { + return response()->json(["message" => "Access token is valid. Welcome to this private endpoint. You need elevated scopes to access Vibranium."]); +})->middleware('auth:api'); +``` + +Now, you can send a request to your protected endpoint which includes an `access_token`. + +```bash +curl --request GET \ + --url http://localhost:8000/api/wakanda \ + --header 'authorization: Bearer ' +``` + +Once a user hits the `api/wakanda` endpoint, a valid JWT `access_token` will be required before the resource can be released. With this in place, private routes can be secured. + +### More Resources + +That's it! We have an authenticated Laravel API with protected routes. To learn more, check out the following resources: + +* [Why You Should Always Use Access Tokens to Secure an API](https://auth0.com/blog/why-should-use-accesstokens-to-secure-an-api/) +* [Laravel backend Quickstart](https://auth0.com/docs/quickstart/backend/laravel) +* [Navigating RS256 and JWKS](https://auth0.com/blog/navigating-rs256-and-jwks/) +* [Access Token](https://auth0.com/docs/tokens/access-token) +* [Verify Access Tokens](https://auth0.com/docs/api-auth/tutorials/verify-access-token) +* [Call APIs from Client-side Web Apps](https://auth0.com/docs/api-auth/grant/implicit) +* [How to implement the Implicit Grant](https://auth0.com/docs/api-auth/tutorials/implicit-grant) +* [Auth0.js Documentation](https://auth0.com/docs/libraries/auth0js) +* [OpenID Standard Claims](https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims) \ No newline at end of file diff --git a/_includes/asides/lumen.markdown b/_includes/asides/lumen.markdown new file mode 100644 index 0000000000..44db6ca51b --- /dev/null +++ b/_includes/asides/lumen.markdown @@ -0,0 +1,145 @@ +## Aside: Securing Lumen APIs with Auth0 + +Securing Lumen APIs with Auth0 is very easy and brings a lot of great features to the table. With Auth0, we only have to write a few lines of code to get: + +- A solid [identity management solution](https://auth0.com/user-management), including [single sign-on](https://auth0.com/docs/sso/single-sign-on) +- [User management](https://auth0.com/docs/user-profile) +- Support for [social identity providers (like Facebook, GitHub, Twitter, etc.)](https://auth0.com/docs/identityproviders) +- [Enterprise identity providers (Active Directory, LDAP, SAML, etc.)](https://auth0.com/enterprise) +- Our [own database of users](https://auth0.com/docs/connections/database/mysql) + +### Sign Up for Auth0 + +You'll need an [Auth0](https://auth0.com) account to manage authentication. You can sign up for a free account here. Next, set up an Auth0 API. + +### Set Up an API + +Go to [**APIs**](https://manage.auth0.com/#/apis) in your Auth0 dashboard and click on the "Create API" button. Enter a name for the API. Set the **Identifier** to a URL(_existent or non-existent URL_). The **Signing Algorithm** should be `RS256`. + +![Create API on Auth0 dashboard](https://cdn2.auth0.com/docs/media/articles/api-auth/create-api.png) +_Create API on Auth0 dashboard_ + +We're now ready to implement Auth0 authentication on our Lumen API. + +### Dependencies and Setup + +Install the Auth0 PHP SDK package via composer like so: + +```bash +composer require auth0/auth0-php:~5.0 +``` + +### JWT Verification Service + +Create the JWT Middleware service in the `app/Http/Middleware` directory. + +```php +// app/Http/Middleware/Auth0Middleware.php + +hasHeader('Authorization')) { + return response()->json('Authorization Header not found', 401); + } + + $token = $request->bearerToken(); + + if($request->header('Authorization') == null || $token == null) { + return response()->json('No token provided', 401); + } + + $this->retrieveAndValidateToken($token); + + return $next($request); + } + + public function retrieveAndValidateToken($token) + { + try { + $verifier = new JWTVerifier([ + 'supported_algs' => ['RS256'], + 'valid_audiences' => [$AUTH0_API_AUDIENCE'], e.g https://mylumenapi.com + 'authorized_iss' => [$AUTH0_DOMAIN'] // e.g https://kabiyesi.auth0.com/ + ]); + + $decoded = $verifier->verifyAndDecode($token); + } + catch(\Auth0\SDK\Exception\CoreException $e) { + throw $e; + }; + } + +} +``` + + +In the `retrieveAndValidateToken()` method, we created an instance of `JWTVerifier` to verify the token coming from the Authorization header. It checks the algorithm, the API audience, and the issuer to ensure the token is a valid one issued by Auth0. + +Change the `$AUTH0_DOMAIN` variable to your Auth0 domain and set the `$AUTH0_API_AUDIENCE` to the **Identifier** you chose while creating the API from the Auth0 dashboard. Ensure that there is a trailing slash in `authorized_iss`'s value. An example is `https://kabiyesi.auth0.com/`. + +> **Note:** To learn more about RS256 and JSON Web Key Set, read [Navigating RS256 and JWKS](https://auth0.com/blog/navigating-rs256-and-jwks/). + +### Activate Auth0 JWT Middleware + +Assign the middleware a short-hand key in `bootstrap/app.php` file's call to the `$app->routeMiddleware()` method. + +**Note:** By default, the `$app->routeMiddleware()` function is commented. Uncomment it and modify it to the code below. + +```php +$app->routeMiddleware([ + 'auth' => App\Http\Middleware\Auth0Middleware::class, +]); +``` + +### Secure API Routes + +We can use the middleware `auth` key defined in the `routeMiddleware()` function in the route options array in the `routes/web.php` file. + +```php +$router->group(['prefix' => 'api', 'middleware' => 'auth'], function () use ($router) { + $router->get('/protected', function (Request $request) { + return response()->json(["message" => "Welcome to the dashboard. Access token is valid!"]); + }); + + $router->get('/private', function (Request $request) { + return response()->json(["message" => "Access token is valid. Welcome to this private endpoint."]); + }); +}); +``` + +```php +$router->get('/public', function (Request $request) { + return response()->json(["message" => "Hello from a public endpoint! You don't need any token to access this URL..Yaaaay!"]); +}); +``` + +We just secured the API `protected` and `private` endpoints with JWT. If a user accesses the endpoints without a valid access token or no token at all, it returns an error. + +### More Resources + +That's it! We have an authenticated Lumen API with protected routes. To learn more, check out the following resources: + +* [Why You Should Always Use Access Tokens to Secure an API](https://auth0.com/blog/why-should-use-accesstokens-to-secure-an-api/) +* [Navigating RS256 and JWKS](https://auth0.com/blog/navigating-rs256-and-jwks/) +* [Access Token](https://auth0.com/docs/tokens/access-token) +* [Verify Access Tokens](https://auth0.com/docs/api-auth/tutorials/verify-access-token) +* [Call APIs from Client-side Web Apps](https://auth0.com/docs/api-auth/grant/implicit) +* [How to implement the Implicit Grant](https://auth0.com/docs/api-auth/tutorials/implicit-grant) +* [Auth0.js Documentation](https://auth0.com/docs/libraries/auth0js) +* [OpenID Standard Claims](https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims) \ No newline at end of file diff --git a/_includes/asides/market-basket-links-about.markdown b/_includes/asides/market-basket-links-about.markdown new file mode 100644 index 0000000000..dfeeec7c08 --- /dev/null +++ b/_includes/asides/market-basket-links-about.markdown @@ -0,0 +1,5 @@ +## Aside: Securing Applications with Auth0 + +Are you building a [B2C](https://auth0.com/b2c-customer-identity-management), [B2B](https://auth0.com/b2b-enterprise-identity-management), or [B2E](https://auth0.com/b2e-identity-management-for-employees) tool? Auth0, can help you focus on what matters the most to you, the special features of your product. [Auth0](https://auth0.com/) can improve your product's security with state-of-the-art features like [passwordless](https://auth0.com/passwordless), [breached password surveillance](https://auth0.com/breached-passwords), and [multifactor authentication](https://auth0.com/multifactor-authentication). + +[We offer a generous **free tier**](https://auth0.com/pricing) so you can get started with modern authentication. diff --git a/_includes/asides/node.markdown b/_includes/asides/node.markdown new file mode 100644 index 0000000000..b9aa5762e5 --- /dev/null +++ b/_includes/asides/node.markdown @@ -0,0 +1,134 @@ +## Aside: Securing Node.js Applications with Auth0 + +Securing Node.js applications with Auth0 is easy and brings a lot of great features to the table. With [Auth0](https://auth0.com/), we only have to write a few lines of code to get solid [identity management solution](https://auth0.com/user-management), [single sign-on](https://auth0.com/docs/sso/single-sign-on), support for [social identity providers (like Facebook, GitHub, Twitter, etc.)](https://auth0.com/docs/identityproviders), and support for [enterprise identity providers (like Active Directory, LDAP, SAML, custom, etc.)](https://auth0.com/enterprise). + +In the following sections, we are going to learn how to use Auth0 to secure Node.js APIs written with [Express](https://expressjs.com/). + +### Creating the Express API + +Let's start by defining our Node.js API. With Express and Node.js we can do this in two simple steps. The first one is to use [NPM](https://www.npmjs.com/) to install three dependencies: `npm i express body-parser cors`. The second one is to create a Node.js script with the following code: + +```javascript +// importing dependencies +const express = require('express'); +const bodyParser = require('body-parser'); +const cors = require('cors'); + +// configuring Express +const app = express(); +app.use(bodyParser.json()); +app.use(cors()); + +// defining contacts array and endpoints to manipulate it +const contacts = [ + { name: 'Bruno Krebs', phone: '+555133334444' }, + { name: 'John Doe', phone: '+191843243223' } +]; +app.get('/contacts', (req, res) => res.send(contacts)); +app.post('/contacts', (req, res) => { + contacts.push(req.body); + res.send(); +}); + +// starting Express +app.listen(3000, () => console.log('Example app listening on port 3000!')); +``` + +The code above creates the Express application and adds two middleware to it: `body-parser` to parse JSON requests, and `cors` to signal that the app accepts requests from any origin. The app also registers two endpoints on Express to deal with POST and GET requests. Both endpoints use the `contacts` array as some sort of in-memory database. + +We can run and test our application by issuing `node index` in the project root and then by submitting requests to it. For example, with [cURL](https://curl.haxx.se/), we can send a GET request by issuing `curl localhost:3000/contacts`. This command will output the items in the `contacts` array. + +### Registering the API at Auth0 + +After creating our application, we can focus on securing it. Let's start by registering an API on Auth0 to represent our app. To do this, let's head to [the API section of our management dashboard](https://manage.auth0.com/#/apis) (we can create a [free account](https://auth0.com/signup)) if needed) and click on "Create API". On the dialog that appears, we can name our API as "Contacts API" (the name isn't really important) and identify it as `https://contacts.mycompany.com/` (we will use this value later). + +After creating it, we have to go to the "Scopes" tab of the API and define the desired scopes. For this sample, we will define two scopes: `read:contacts` and `add:contacts`. They will represent two different operations (read and add) over the same entity (contacts). + +![Defining OAuth scopes in the new Auth0 API](https://cdn.auth0.com/blog/spring-boot-aside/defining-oauth-scopes.png) + +### Securing Express with Auth0 + +Now that we have registered the API in our Auth0 account, let's secure the Express API with Auth0. Let's start by installing three dependencies with NPM: `npm i express-jwt jwks-rsa express-jwt-authz`. Then, let's create a file called `auth0.js` and use these dependencies: + +```javascript +const jwt = require('express-jwt'); +const jwksRsa = require('jwks-rsa'); +const jwtAuthz = require('express-jwt-authz'); + +const tokenGuard = jwt({ + // Fetch the signing key based on the KID in the header and + // the singing keys provided by the JWKS endpoint. + secret: jwksRsa.expressJwtSecret({ + cache: true, + rateLimit: true, + jwksUri: `https://${process.env.AUTH0_DOMAIN}/.well-known/jwks.json` + }), + + // Validate the audience and the issuer. + audience: process.env.AUTH0_AUDIENCE, + issuer: `https://${process.env.AUTH0_DOMAIN}/`, + algorithms: ['RS256'] +}); + +module.exports = function (scopes) { + const scopesGuard = jwtAuthz(scopes || []); + return function mid(req, res, next) { + tokenGuard(req, res, (err) => { + err ? res.status(500).send(err) : scopesGuard(req, res, next); + }); + } +}; +``` + +The goal of this script is to export an [Express middleware](http://expressjs.com/en/guide/using-middleware.html) that guarantees that requests have an `access_token` issued by a trust-worthy party, in this case Auth0. The middleware also accepts an array of scopes. When filtering requests, this middleware will check that these scopes exist in the `access_token`. Note that this script expects to find two environment variables: + +- `AUTH0_AUDIENCE`: the identifier of our API (`https://contacts.mycompany.com/`) +- `AUTH0_DOMAIN`: our domain at Auth0 (in my case `bk-samples.auth0.com`) + +We will set these variable soon, but it is important to understand that the domain variable defines how the middleware finds the signing keys. + +After creating this middleware, we can update our `index.js` file to import and use it: + +```javascript +// ... other requires +const auth0 = require('./auth0'); + +app.get('/contacts', auth0(['read:contacts']), (req, res) => res.send(contacts)); +app.post('/contacts', auth0(['add:contacts']), (req, res) => { + contacts.push(req.body); + res.send(); +}); +``` + +In this case, we have replaced the previous definition of our endpoints to use the new middleware. We also restricted their access to users that contain the right combination of scopes. That is, to get contacts users must have the `read:contacts` scope and to create new records they must have the `add:contacts` scope. + +Running the application now is slightly different, as we need to set the environment variables: + +``` +export AUTH0_DOMAIN=bk-samples.auth0.com +export AUTH0_AUDIENCE="https://contacts.mycompany.com/" +node index +``` + +Let's keep this API running before moving on. + +### Creating an Auth0 Application + +As the focus of this section is to secure Node.js applications with Auth0, [we are going to use a live Angular app that has a configurable Auth0 application](http://auth0.digituz.com.br/?clientID=ssII6Fu1qfFI4emuNeXeadMv8iTQn1hJ&domain=bk-samples.auth0.com&audience=https:%2F%2Fcontacts.mycompany.com%2F&scope=read:contacts). Before using this app, we need to create an Auth0 application that represents it. Let's head to the ["Applications" section of the management dashboard](https://manage.auth0.com/#/applications) and click on the "Create Application" button. + +On the popup shown, let's set the name of this new application as "Contacts Application" and choose "Single Page Web App" as the application type. After hitting the "Create" button, we have to go to the "Settings" tab and set `http://auth0.digituz.com.br/callback` in the "Allowed Callback URLs" field. + +Now we can save the application and head to [the sample Angular app secured with Auth0](http://auth0.digituz.com.br/?clientID=ssII6Fu1qfFI4emuNeXeadMv8iTQn1hJ&domain=bk-samples.auth0.com&audience=https:%2F%2Fcontacts.mycompany.com%2F&scope=read:contacts). To use this app, we need to set the correct values for four properties: + +- `clientID`: We have to copy this value from the "Client ID" field of the "Settings" tab of "Contacts Application". +- `domain`: We can also copy this value from the "Settings" tab of "Contacts Application". +- `audience`: We have to set this property to meet the identifier of the "Contacts API" that we created earlier. +- `scope`: This property will define the `authority` that the `access_token` will get access to in the backend API. For example: `read:contacts` or both `read:contacts add:contacts`. + +Then we can hit the "Sign In with Auth0" button. + +![Using the Angular app with the configurable Auth0 application](https://cdn.auth0.com/blog/angular-generic-client/signing-in.png) + +After signing in, we can use the application to submit requests to our secured Node.js API. For example, if we issue a GET request to `http://localhost:3000/contacts/`, the Angular app will include the `access_token` in the `Authorization` header and our API will respond with a list of contacts. + +![Getting a response from a secure Node.js API](https://cdn.auth0.com/blog/node-aside/client-app-issuing-request.png) diff --git a/_includes/asides/php.markdown b/_includes/asides/php.markdown new file mode 100644 index 0000000000..333395ff88 --- /dev/null +++ b/_includes/asides/php.markdown @@ -0,0 +1,165 @@ +## Aside: Securing PHP APIs with Auth0 + +Securing PHP APIs with Auth0 is very easy and brings a lot of great features to the table. With Auth0, we only have to write a few lines of code to get: + +- A solid [identity management solution](https://auth0.com/user-management), including [single sign-on](https://auth0.com/docs/sso/single-sign-on) +- [User management](https://auth0.com/docs/user-profile) +- Support for [social identity providers (like Facebook, GitHub, Twitter, etc.)](https://auth0.com/docs/identityproviders) +- [Enterprise identity providers (Active Directory, LDAP, SAML, etc.)](https://auth0.com/enterprise) +- Our [own database of users](https://auth0.com/docs/connections/database/mysql) + +### Sign Up for Auth0 + +You'll need an [Auth0](https://auth0.com) account to manage authentication. You can sign up for a free account here. Next, set up an Auth0 API. + +### Set Up an API + +1. Go to [**APIs**](https://manage.auth0.com/#/apis) in your Auth0 dashboard and click on the "Create API" button. Enter a name for the API. Set the **Identifier** to a URL(_existent or non-existent URL_). The **Signing Algorithm** should be `RS256`. +2. You can consult the PHP example under the **Quick Start** tab in your new API's settings. We'll implement our PHP API in this fashion, using [Auth0 PHP SDK](https://github.com/auth0/auth0-PHP). + +![Create API on Auth0 dashboard](https://cdn2.auth0.com/docs/media/articles/api-auth/create-api.png) +_Create API on Auth0 dashboard_ + +We're now ready to implement Auth0 authentication on our PHP backend API. + +### Dependencies and Setup + +Install the following packages via composer like so: + +```bash +composer require bramus/router:dev-master auth0/auth0-php:~5.0 +``` + +### JWT Verification Service + +```php + ['RS256'], + 'valid_audiences' => [$AUTH0_API_AUDIENCE], + 'authorized_iss' => [$AUTH0_DOMAIN] + ]); + + $this->token = $token; + $this->tokenInfo = $verifier->verifyAndDecode($token); + } + catch(\Auth0\SDK\Exception\CoreException $e) { + throw $e; + } + } + + public function privatePing() { + return [ + "status" => "ok", + "message" => "Hello from a private endpoint! You do need to be authenticated to see this." + ]; + } + +} +``` + +Change the `$AUTH0_DOMAIN` variable to your Auth0 domain and set the `$AUTH0_API_AUDIENCE` to the **Identifier** you chose while creating the API from the Auth0 dashboard. + +> **Note:** To learn more about RS256 and JSON Web Key Set, read [Navigating RS256 and JWKS](https://auth0.com/blog/navigating-rs256-and-jwks/). + +### Configure Authenticated Routes + +Then use it to secure your API endpoints like so: + +```php +// Require composer autoloader +require __DIR__ . '/vendor/autoload.php'; + +// Read .env +try { + $dotenv = new Dotenv\Dotenv(__DIR__); + $dotenv->load(); +} catch(InvalidArgumentException $ex) { + // Ignore if no dotenv +} + +$app = new \App\Main(); + +// Create Router instance +$router = new \Bramus\Router\Router(); + +// Activate CORS +function sendCorsHeaders() { + header("Access-Control-Allow-Origin: *"); + header("Access-Control-Allow-Headers: Authorization"); + header("Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE"); +} + +$router->options('/.*', function() { + sendCorsHeaders(); +}); + +sendCorsHeaders(); + +// Check JWT on /secured routes +$router->before('GET', '/api/private/.*', function() use ($app) { + + $requestHeaders = apache_request_headers(); + + if (!isset($requestHeaders['authorization']) && !isset($requestHeaders['Authorization'])) { + header('HTTP/1.0 401 Unauthorized'); + echo "No token provided."; + exit(); + } + + $authorizationHeader = isset($requestHeaders['authorization']) ?? $requestHeaders['Authorization']; // PHP 7 null coalescing operator is used here + + if ($authorizationHeader == null) { + header('HTTP/1.0 401 Unauthorized'); + echo "No authorization header sent"; + exit(); + } + + $authorizationHeader = str_replace('bearer ', '', $authorizationHeader); + $token = str_replace('Bearer ', '', $authorizationHeader); + + try { + $app->setCurrentToken($token); + } + catch(\Auth0\SDK\Exception\CoreException $e) { + header('HTTP/1.0 401 Unauthorized'); + echo $e; + exit(); + } + +}); +``` + +Once a user hits any endpoint which includes `api/private`, a valid JWT `access_token` will be required before the resource can be released. With this in place, private routes can be defined. For example, + +```php +$router->get('/api/private/ping', function() use ($app){ + echo json_encode($app->privatePing()); +}); +``` + +### More Resources + +That's it! We have an authenticated PHP API with protected routes. To learn more, check out the following resources: + +* [Why You Should Always Use Access Tokens to Secure an API](https://auth0.com/blog/why-should-use-accesstokens-to-secure-an-api/) +* [Navigating RS256 and JWKS](https://auth0.com/blog/navigating-rs256-and-jwks/) +* [Access Token](https://auth0.com/docs/tokens/access-token) +* [Verify Access Tokens](https://auth0.com/docs/api-auth/tutorials/verify-access-token) +* [Call APIs from Client-side Web Apps](https://auth0.com/docs/api-auth/grant/implicit) +* [How to implement the Implicit Grant](https://auth0.com/docs/api-auth/tutorials/implicit-grant) +* [Auth0.js Documentation](https://auth0.com/docs/libraries/auth0js) +* [OpenID Standard Claims](https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims) \ No newline at end of file diff --git a/_includes/asides/python.markdown b/_includes/asides/python.markdown new file mode 100644 index 0000000000..108685404f --- /dev/null +++ b/_includes/asides/python.markdown @@ -0,0 +1,112 @@ +## Securing Python APIs with Auth0 + +Securing Python APIs with Auth0 is very easy and brings a lot of great features to the table. With Auth0, we only have to write a few lines of code to get: + +- A solid [identity management solution](https://auth0.com/user-management), including [single sign-on](https://auth0.com/docs/sso/single-sign-on) +- [User management](https://auth0.com/docs/user-profile) +- Support for [social identity providers (like Facebook, GitHub, Twitter, etc.)](https://auth0.com/docs/identityproviders) +- [Enterprise identity providers (Active Directory, LDAP, SAML, etc.)](https://auth0.com/enterprise) +- Our [own database of users](https://auth0.com/docs/connections/database/mysql) + +For example, to secure Python APIs written with Flask, we can simply create a `requires_auth` decorator: + +```python +# Format error response and append status code + +def get_token_auth_header(): + """Obtains the access token from the Authorization Header + """ + auth = request.headers.get("Authorization", None) + if not auth: + raise AuthError({"code": "authorization_header_missing", + "description": + "Authorization header is expected"}, 401) + + parts = auth.split() + + if parts[0].lower() != "bearer": + raise AuthError({"code": "invalid_header", + "description": + "Authorization header must start with" + " Bearer"}, 401) + elif len(parts) == 1: + raise AuthError({"code": "invalid_header", + "description": "Token not found"}, 401) + elif len(parts) > 2: + raise AuthError({"code": "invalid_header", + "description": + "Authorization header must be" + " Bearer token"}, 401) + + token = parts[1] + return token + +def requires_auth(f): + """Determines if the access token is valid + """ + @wraps(f) + def decorated(*args, **kwargs): + token = get_token_auth_header() + jsonurl = urlopen("https://"+AUTH0_DOMAIN+"/.well-known/jwks.json") + jwks = json.loads(jsonurl.read()) + unverified_header = jwt.get_unverified_header(token) + rsa_key = {} + for key in jwks["keys"]: + if key["kid"] == unverified_header["kid"]: + rsa_key = { + "kty": key["kty"], + "kid": key["kid"], + "use": key["use"], + "n": key["n"], + "e": key["e"] + } + if rsa_key: + try: + payload = jwt.decode( + token, + rsa_key, + algorithms=ALGORITHMS, + audience=API_AUDIENCE, + issuer="https://"+AUTH0_DOMAIN+"/" + ) + except jwt.ExpiredSignatureError: + raise AuthError({"code": "token_expired", + "description": "token is expired"}, 401) + except jwt.JWTClaimsError: + raise AuthError({"code": "invalid_claims", + "description": + "incorrect claims," + "please check the audience and issuer"}, 401) + except Exception: + raise AuthError({"code": "invalid_header", + "description": + "Unable to parse authentication" + " token."}, 400) + + _app_ctx_stack.top.current_user = payload + return f(*args, **kwargs) + raise AuthError({"code": "invalid_header", + "description": "Unable to find appropriate key"}, 400) + return decorated +``` + +Then use it in our endpoints: + +```python +# Controllers API + +# This doesn't need authentication +@app.route("/ping") +@cross_origin(headers=['Content-Type', 'Authorization']) +def ping(): + return "All good. You don't need to be authenticated to call this" + +# This does need authentication +@app.route("/secured/ping") +@cross_origin(headers=['Content-Type', 'Authorization']) +@requires_auth +def secured_ping(): + return "All good. You only get this message if you're authenticated" +``` + +[To learn more about securing *Python APIs* with Auth0, take a look at this tutorial](https://auth0.com/docs/quickstart/backend/python). Alongside with tutorials for backend technologies (like Python, Java, and PHP), [the *Auth0 Docs* webpage also provides tutorials for *Mobile/Native apps* and *Single-Page applications*](https://auth0.com/docs). diff --git a/_includes/asides/react.markdown b/_includes/asides/react.markdown new file mode 100644 index 0000000000..d5ba9276e3 --- /dev/null +++ b/_includes/asides/react.markdown @@ -0,0 +1,173 @@ +## Aside: Authenticate a React App with Auth0 + +We can protect our applications and APIs so that only authenticated users can access them. Let's explore how to do this with a React application using [Auth0](https://auth0.com). + +![Auth0 login screen](https://cdn.auth0.com/blog/resources/auth0-centralized-login.jpg) + +We'll need an [Auth0](https://auth0.com) account to manage authentication. [To sign up for a free account, we can follow this link](https://auth0.com/signup). Next, let's set up an Auth0 application and API so Auth0 can interface with a React App. + +### Setting Up an Auth0 Application + +1. Let's go to our [**Auth0 Dashboard**](https://manage.auth0.com/#/) and click the "[create a new application](https://manage.auth0.com/#/applications/create)" button. +2. Let's call our app as "React Demo" and select "Single Page Web Applications". +3. In the **Settings** for our new Auth0 application, let's add `http://localhost:3000/callback` to the **Allowed Callback URLs**. +4. If desired, we can [set up some social connections](https://manage.auth0.com/#/connections/social). We can then enable them for our app in the **Application** options under the **Connections** tab. The example shown in the screenshot above utilizes username/password database, Facebook, Google, and Twitter. For production, make sure to set up the correct social keys and do not leave social connections set to use Auth0 dev keys. + +### Set Up an API + +1. Go to [**APIs**](https://manage.auth0.com/#/apis) in your Auth0 dashboard and click on the "Create API" button. Enter a name for the API. Set the **Identifier** to your API endpoint URL. In this example, this is `http://localhost:3001/api/`. The **Signing Algorithm** should be `RS256`. +2. You can consult the Node.js example under the **Quick Start** tab in your new API's settings. We'll implement our Node API in this fashion, using [Express](https://expressjs.com/), [express-jwt](https://github.com/auth0/express-jwt), and [jwks-rsa](https://github.com/auth0/node-jwks-rsa). + +We're now ready to implement Auth0 authentication on both our React client and Node backend API. + +### Dependencies and Setup + +There are only two dependencies that we really need to install: [`auth0.js`](https://github.com/auth0/auth0.js) and [`history`](https://github.com/ReactTraining/history). To do that, let's issue `npm install --save auth0-js history` in the project root. + +> **Note:** As we want the best security available, we are going to rely on the [Auth0 login page](https://auth0.com/docs/hosted-pages/login). This method consists of redirecting users to a login page hosted by Auth0 that is easily customizable right from the [Dashboard](https://manage.auth0.com/). + +After installing it, we can create an authentication service to interface with the `auth0.js` script. Let's call this service `Auth` and create it in the `src/Auth/` directory with the following code: + +```js +import history from '../history'; +import auth0 from 'auth0-js'; + +export default class Auth { + auth0 = new auth0.WebAuth({ + // the following three lines MUST be updated + domain: 'bkrebs.auth0.com', + audience: 'https://bkrebs.auth0.com/userinfo', + clientID: '3co4Cdt3h3x8En7Cj0s7Zg5FxhKOjeeK', + redirectUri: 'http://localhost:3000/callback', + responseType: 'token', + scope: 'openid' + }); + + constructor() { + this.login = this.login.bind(this); + this.logout = this.logout.bind(this); + this.handleAuthentication = this.handleAuthentication.bind(this); + this.isAuthenticated = this.isAuthenticated.bind(this); + } + + handleAuthentication() { + this.auth0.parseHash((err, authResult) => { + if (authResult && authResult.accessToken) { + this.setSession(authResult); + history.replace('/home'); + } else if (err) { + history.replace('/home'); + console.log(err); + } + }); + } + + setSession(authResult) { + // Set the time that the access token will expire at + let expiresAt = JSON.stringify((authResult.expiresIn * 1000) + new Date().getTime()); + localStorage.setItem('access_token', authResult.accessToken); + localStorage.setItem('expires_at', expiresAt); + // navigate to the home route + history.replace('/home'); + } + + login() { + this.auth0.authorize(); + } + + logout() { + // Clear access token and expiration from local storage + localStorage.removeItem('access_token'); + localStorage.removeItem('expires_at'); + // navigate to the home route + history.replace('/home'); + } + + isAuthenticated() { + // Check whether the current time is past the + // access token's expiry time + let expiresAt = JSON.parse(localStorage.getItem('expires_at')); + return new Date().getTime() < expiresAt; + } +} +``` + +The `Auth` service just created contains functions to deal with various steps of the sign in/sign up process. The following list briefly summarizes these functions and their descriptions: + +- `handleAuthentication`: looks for the result of the authentication in the URL hash. Then, process the result with the `parseHash` method from `auth0-js`; +- `setSession`: sets the user's access token and the access token's expiry time; +- `login`: initiates the login process, redirecting users to the login page; +- `logout`: removes the user's tokens and expiry time from browser storage; +- `isAuthenticated`: checks whether the expiry time for the user's access token has passed; + +Besides these functions, the class contains a field called `auth0` that is initialized with values extracted from the Auth0 application. Let's keep in mind that we need to update them accordingly before proceeding. + +Attentive readers probably noticed that the `Auth` service also imports a module called `history` that we haven't talked about. We can define this module in only two lines, but let's define it in a file to provide reusability. Let's call this file `./src/history/history.js` and add the following code: + +```js +import createHistory from 'history/createBrowserHistory' + +export default createHistory() +``` + +After creating both elements, we can refactor our `App` component to make use of the `Auth` service. + +```jsx +import React, { Component } from 'react'; +import { Navbar, Button } from 'react-bootstrap'; +import './App.css'; + +class App extends Component { + goTo(route) { + this.props.history.replace(`/${route}`) + } + + login() { + this.props.auth.login(); + } + + logout() { + this.props.auth.logout(); + } + + render() { + const { isAuthenticated } = this.props.auth; + + // ... render the view + } +} + +export default App; +``` + +Note that we are passing this service through `props`. Therefore, when including the `App` component, we need to inject `Auth` into it: ``. + +Considering that we are using the Auth0 login page, our users are taken away from the application. However, after they authenticate, users automatically return to the callback URL that we set up previously (`http://localhost:3000/callback`). This means that we need to create a component responsible for this URL: + +```jsx +import React, { Component } from 'react'; +import loading from './loading.svg'; + +class Callback extends Component { + render() { + const style = //... + + return ( +
    + loading +
    + ); + } +} + +export default Callback; +``` + +This component can just contain a loading indicator that keeps spinning while the application sets up a client-side session for the users. After the session is set up, we can redirect users to another route. + +Please refer to [the official Quick Start Guide to see, step by step, how to properly secure a React application](https://auth0.com/docs/quickstart/spa/react/01-login). Besides the steps shown in this section, the guide also shows: + +- [How to manage profile information of authenticated users](https://auth0.com/docs/quickstart/spa/react/02-user-profile) +- [How to properly call an API](https://auth0.com/docs/quickstart/spa/react/03-calling-an-api) +- [How to control which routes users can see/interact with](https://auth0.com/docs/quickstart/spa/react/04-authorization) +- [How to deal with expiry time of users' access token](https://auth0.com/docs/quickstart/spa/react/05-token-renewal) diff --git a/_includes/asides/ruby.markdown b/_includes/asides/ruby.markdown new file mode 100644 index 0000000000..ebf63c2a95 --- /dev/null +++ b/_includes/asides/ruby.markdown @@ -0,0 +1,146 @@ +## Aside: Securing Ruby APIs with Auth0 + +Securing Ruby APIs with Auth0 is not hard and brings a lot of great features to the table. With Auth0, we only have to write a few lines of code to get: + +- A solid [identity management solution](https://auth0.com/user-management), including [single sign-on](https://auth0.com/docs/sso/single-sign-on) +- [User management](https://auth0.com/docs/user-profile) +- Support for [social identity providers (like Facebook, GitHub, Twitter, etc.)](https://auth0.com/docs/identityproviders) +- [Enterprise identity providers (Active Directory, LDAP, SAML, etc.)](https://auth0.com/enterprise) +- Our [own database of users](https://auth0.com/docs/connections/database/mysql) + +### Sign Up for Auth0 + +You'll need an [Auth0](https://auth0.com) account to manage authentication. You can sign up for a free account here. Next, set up an Auth0 API. + +### Set Up an API + +1. Go to [**APIs**](https://manage.auth0.com/#/apis) in your Auth0 dashboard and click on the "Create API" button. Enter a name for the API. Set the **Identifier** to a URL(_existent or non-existent URL_). The **Signing Algorithm** should be `RS256`. + +![Create API on Auth0 dashboard](https://cdn2.auth0.com/docs/media/articles/api-auth/create-api.png) +_Create API on Auth0 dashboard_ + +We're now ready to implement Auth0 authentication on our Ruby backend API. + +### Dependencies and Setup + +Install the following packages via bundler like so: + +```bash +gem `jwt` +bundle install +``` + +### JWT Verification Service + +```ruby + +require 'net/http' +require 'uri' + +class JsonWebToken + def self.verify(token) + JWT.decode(token, nil, + true, # Verify the signature of this token + algorithm: 'RS256', + iss: 'AUTH0_DOMAIN', + verify_iss: true, + # AUTHO_API_AUDIENCE is the identifier for the API set up in the Auth0 dashboard + aud: AUTHO_API_AUDIENCE, + verify_aud: true) do |header| + jwks_hash[header['kid']] + end + end + + def self.jwks_hash + jwks_raw = Net::HTTP.get URI("https://{AUTH0_DOMAIN}/.well-known/jwks.json") + jwks_keys = Array(JSON.parse(jwks_raw)['keys']) + Hash[ + jwks_keys + .map do |k| + [ + k['kid'], + OpenSSL::X509::Certificate.new( + Base64.decode64(k['x5c'].first) + ).public_key + ] + end + ] + end +end +``` + +Change the `AUTH0_DOMAIN` variable to your Auth0 domain and set the `AUTH0_API_AUDIENCE` to the **Identifier** you chose while creating the API from the Auth0 dashboard. + +> **Note:** To learn more about RS256 and JSON Web Key Set, read [Navigating RS256 and JWKS](https://auth0.com/blog/navigating-rs256-and-jwks/). + +### Configure Authenticated Routes + +Then use it to secure your API endpoints like so: + +```ruby +def authenticate! + # Extract from the 'Bearer ' value of the Authorization header + supplied_token = String(request.env['HTTP_AUTHORIZATION']).slice(7..-1) + + @auth_payload, @auth_header = JsonWebToken.verify(supplied_token) + +rescue JWT::DecodeError => e + halt 401, json(error: e.class, message: e.message) +end + +before do + authenticate! +end + +get '/restricted_resource' do + json( message: 'Access Granted', allowed_scopes: String(@auth_payload['scope']).split(' ') ) +end +``` + +Once a user hits the endpoint `/restricted_resource`, a valid JWT `access_token` will be required before the resource can be released. With this in place, private routes can be defined. + +### Configure Scopes + +To look for a particular `scope` in an `access_token`, provide an array of required scopes and check if they are present in the payload of the token. + +In this example the `SCOPES` array for the given key `/restricted_resource` is intersected with the `scopes` contained in the payload of the `access_token` to determine if it contains one or more items from the array. + +```ruby +SCOPES = { + '/restricted_resource' => ['read:messages'], + '/another_resource' => ['some:scope', 'some:other_scope'] +} + +def authenticate! + # Extract from the 'Bearer ' value of the Authorization header + supplied_token = String(request.env['HTTP_AUTHORIZATION']).slice(7..-1) + + @auth_payload, @auth_header = JsonWebToken.verify(supplied_token) + + halt 403, json(error: 'Forbidden', message: 'Insufficient scope') unless scope_included + +rescue JWT::DecodeError => e + halt 401, json(error: e.class, message: e.message) +end + +def scope_included + # The intersection of the scopes included in the given JWT and the ones in the SCOPES hash needed to access + # the PATH_INFO, should contain at least one element + (String(@auth_payload['scope']).split(' ') & (SCOPES[request.env['PATH_INFO']])).any? +end +``` + +With this configuration in place, only `access_tokens` which have a scope of `read:messages` will be allowed to access this endpoint. + +### More Resources + +That's it! We have an authenticated Ruby API with protected routes. To learn more, check out the following resources: + +* [Why You Should Always Use Access Tokens to Secure an API](https://auth0.com/blog/why-should-use-accesstokens-to-secure-an-api/) +* [Navigating RS256 and JWKS](https://auth0.com/blog/navigating-rs256-and-jwks/) +* [Access Token](https://auth0.com/docs/tokens/access-token) +* [Verify Access Tokens](https://auth0.com/docs/api-auth/tutorials/verify-access-token) +* [Call APIs from Client-side Web Apps](https://auth0.com/docs/api-auth/grant/implicit) +* [How to implement the Implicit Grant](https://auth0.com/docs/api-auth/tutorials/implicit-grant) +* [Auth0.js Documentation](https://auth0.com/docs/libraries/auth0js) +* [OpenID Standard Claims](https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims) \ No newline at end of file diff --git a/_includes/asides/spring-boot.markdown b/_includes/asides/spring-boot.markdown new file mode 100644 index 0000000000..286869dd4b --- /dev/null +++ b/_includes/asides/spring-boot.markdown @@ -0,0 +1,141 @@ +## Aside: Securing Spring APIs with Auth0 + +Securing applications with Auth0 is very easy and brings a lot of great features to the table. With Auth0, we only have to write a few lines of code to get solid [identity management solution](https://auth0.com/user-management), +[single sign-on](https://auth0.com/docs/sso/single-sign-on), support for [social identity providers (like Facebook, GitHub, Twitter, etc.)](https://auth0.com/docs/identityproviders), and support for [enterprise identity providers (Active Directory, LDAP, SAML, custom, etc.)](https://auth0.com/enterprise). + +In the following sections, we are going to learn how to use Auth0 to secure Spring APIs. As we will see, the process is simple and fast. + +### Creating the API + +First, we need to create an API on our free Auth0 account. To do that, we have to go to [the APIs section of the management dashboard](https://manage.auth0.com/#/apis) and click on "Create API". On the dialog that appears, we can name our API as "Contacts API" (the name isn't really important) and identify it as `https://contacts.mycompany.com` (we will use this value later). + +After creating it, we have to go to the "Scopes" tab of the API and define the desired scopes. For this sample, we will define two scopes: `read:contacts` and `add:contacts`. They will represent two different operations (read and add) over the same entity (contacts). + +![Defining OAuth scopes in the new Auth0 API](https://cdn.auth0.com/blog/spring-boot-aside/defining-oauth-scopes.png) + +### Registering the Auth0 Dependency + +The second step is to import a dependency called [`auth0-spring-security-api`](https://mvnrepository.com/artifact/com.auth0/auth0-spring-security-api). This can be done on a Maven project by including the following configuration to `pom.xml` ([it's not harder to do this on Gradle, Ivy, and so on](https://mvnrepository.com/artifact/com.auth0/auth0-spring-security-api)): + +```xml + + + + + + com.auth0 + auth0-spring-security-api + 1.0.0-rc.3 + + + +``` + +### Integrating Auth0 with Spring Security + +The third step consists of extending the [WebSecurityConfigurerAdapter](https://docs.spring.io/spring-security/site/docs/current/apidocs/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.html) class. In this extension, we use `JwtWebSecurityConfigurer` to integrate Auth0 and Spring Security: + +```java +package com.auth0.samples.secure; + +import com.auth0.spring.security.api.JwtWebSecurityConfigurer; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +@Configuration +@EnableWebSecurity +@EnableGlobalMethodSecurity(prePostEnabled = true) +public class SecurityConfig extends WebSecurityConfigurerAdapter { + @Value(value = "${auth0.apiAudience}") + private String apiAudience; + @Value(value = "${auth0.issuer}") + private String issuer; + + @Override + protected void configure(HttpSecurity http) throws Exception { + JwtWebSecurityConfigurer + .forRS256(apiAudience, issuer) + .configure(http) + .cors().and().csrf().disable().authorizeRequests() + .anyRequest().permitAll(); + } +} +``` + +As we don't want to hard code credentials in the code, we make `SecurityConfig` depend on two environment properties: + +- `auth0.apiAudience`: This is the value that we set as the identifier of the API that we created at Auth0 (`https://contacts.mycompany.com`). +- `auth0.issuer`: This is our domain at Auth0, including the HTTP protocol. For example: `https://bk-samples.auth0.com/`. + +Let's set them in a properties file on our Spring application (e.g. `application.properties`): + +```bash +auth0.issuer:https://bk-samples.auth0.com/ +auth0.apiAudience:https://contacts.mycompany.com/ +``` + +### Securing Endpoints with Auth0 + +After integrating Auth0 and Spring Security, we can easily secure our endpoints with Spring Security annotations: + +```java +package com.auth0.samples.secure; + +import com.google.common.collect.Lists; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequestMapping(value = "/contacts/") +public class ContactController { + private static final List contacts = Lists.newArrayList( + Contact.builder().name("Bruno Krebs").phone("+5551987654321").build(), + Contact.builder().name("John Doe").phone("+5551888884444").build() + ); + + @GetMapping + @PreAuthorize("hasAuthority('read:contacts')") + public List getContacts() { + return contacts; + } + + @PostMapping + @PreAuthorize("hasAuthority('add:contacts')") + public void addContact(@RequestBody Contact contact) { + contacts.add(contact); + } +} +``` + +Note that the integration allows us to use [the `hasAuthority` Spring EL Expression](https://docs.spring.io/spring-security/site/docs/current/reference/html/el-access.html) to restrict access to endpoints based on the `scope` of the `access_token`. Let's see how to get this token now. + +### Creating an Auth0 Application + +As the focus of this section is to secure Spring APIs with Auth0, [we are going to use a live Angular app that has a configurable Auth0 application](http://auth0.digituz.com.br/?clientID=ssII6Fu1qfFI4emuNeXeadMv8iTQn1hJ&domain=bk-samples.auth0.com&audience=https:%2F%2Fcontacts.mycompany.com%2F&scope=read:contacts). To use this app we need to create an Auth0 application that represents it. Let's head to the [_Applications_ section of the management dashboard](https://manage.auth0.com/#/applications) and click on the "Create Application" button to create this application. + +On the popup shown, let's set the name of this new application as "Contacts Application" and choose "Single Page Web App" as the application type. After hitting the "Create" button, we have to go to the "Settings" tab of this application and change two properties. First, we have to set `http://auth0.digituz.com.br/` in the "Allowed Web Origins" property. Second, we have to set `http://auth0.digituz.com.br/callback` in the "Allowed Callback URLs" property. + +That's it, we can save the application and head to [the sample Angular app secured with Auth0](http://auth0.digituz.com.br/?clientID=ssII6Fu1qfFI4emuNeXeadMv8iTQn1hJ&domain=bk-samples.auth0.com&audience=https:%2F%2Fcontacts.mycompany.com%2F&scope=read:contacts). On it, we just need to set the correct values to the four properties: + +- `clientID`: We have to copy this value from the "Client ID" field of the "Settings" tab of "Contacts Application". +- `domain`: We can also copy this value from the "Settings" tab of "Contacts Application". +- `audience`: We have to set this property to meet the identifier of the "Contacts API" that we created earlier. +- `scope`: This property will define the `authority` that the `access_token` will get access to in the backend API. For example: `read:contacts` or both `read:contacts add:contacts`. + +Then we can hit the "Sign In with Auth0" button. + +![Using the Angular app with the configurable Auth0 application](https://cdn.auth0.com/blog/angular-generic-client/signing-in.png) + +After signing in, we can use the application to submit requests to our secured Spring API. For example, if we issue a GET request to `http://localhost:8080/contacts/`, the Angular app will include the `access_token` in the `Authorization` header and our API will respond with a list of contacts. + +![Getting a response from a secure Spring API](https://cdn.auth0.com/blog/angular-generic-client/issuing-secured-requests.png) diff --git a/_includes/asides/vue.markdown b/_includes/asides/vue.markdown new file mode 100644 index 0000000000..f660241cf9 --- /dev/null +++ b/_includes/asides/vue.markdown @@ -0,0 +1,266 @@ +## Aside: Authenticate a Vue App and Node API with Auth0 + +We can protect our applications and APIs so that only authenticated users can access them. Let's explore how to do this with a Vue application and a Node API using [Auth0](https://auth0.com). You can clone this sample app and API from the [vue-auth0-aside repo on GitHub](https://github.com/auth0-blog/vue-auth0-aside). + +![Auth0 login screen](https://cdn.auth0.com/blog/resources/auth0-centralized-login.jpg) + +### Features + +The [sample Vue application and API](https://github.com/auth0-blog/vue-auth0-aside) has the following features: + +* Vue application generated with [Vue CLI](https://github.com/vue/vue-cli) and served at [http://localhost:4200](http://localhost:4200) +* Authentication with [auth0.js](https://auth0.com/docs/libraries/auth0js/) using the Auth0 login page +* Node server protected API route `http://localhost:3001/api/meetups/private` returns JSON data for authenticated `GET` requests +* Vue app fetches data from API once user is authenticated with Auth0 +* Authentication service uses a subject to propagate authentication status events to the entire app. +* Access token and token expiration are stored in local storage and removed upon logout. + +### Sign Up for Auth0 + +You'll need an [Auth0](https://auth0.com) account to manage authentication. You can sign up for a free account here. Next, set up an Auth0 application and API so Auth0 can interface with a Vue.js app and Node API. + +### Set Up an Auth0 Application + +1. Go to your [**Auth0 Dashboard**](https://manage.auth0.com/#/) and click the "[create a new application](https://manage.auth0.com/#/applications/create)" button. +2. Name your new app and select "Single Page Web Applications". +3. In the **Settings** for your new Auth0 application, add `http://localhost:8080/callback` to the **Allowed Callback URLs**. +4. Scroll down to the bottom of the **Settings** section and click "Show Advanced Settings". Choose the **OAuth** tab and verify that the **JsonWebToken Signature Algorithm** is set to `RS256`. +5. If you'd like, you can [set up some social connections](https://manage.auth0.com/#/connections/social). You can then enable them for your app in the **Application** options under the **Connections** tab. The example shown in the screenshot above utilizes username/password database, Facebook, Google, and Twitter. For production, make sure you set up your own social keys and do not leave social connections set to use Auth0 dev keys. + +### Set Up an API + +1. Go to [**APIs**](https://manage.auth0.com/#/apis) in your Auth0 dashboard and click on the "Create API" button. Enter a name for the API. Set the **Identifier** to a URL. In this example, this is `http://meetupapi.com/`. The **Signing Algorithm** should be `RS256`. +2. You can consult the Node.js example under the **Quick Start** tab in your new API's settings. We'll implement our Node API in this fashion, using [Express](https://expressjs.com/), [express-jwt](https://github.com/auth0/express-jwt), and [jwks-rsa](https://github.com/auth0/node-jwks-rsa). + +We're now ready to implement Auth0 authentication on both our Vue client and Node backend API. + +### Dependencies and Setup + +The Vue app utilizes the [Vue.js CLI](https://github.com/vue/vue-cli). Make sure you have the CLI installed globally: + +```bash +$ npm install -g vue-cli +``` + +Once you've cloned [the project](https://github.com/auth0-blog/vue-auth0-aside), install the Node dependencies for both the Vue app and the Node server by running the following commands in the root of your project folder: + +```bash +$ npm install +$ cd server +$ npm install +``` + +The Node API is located in the [`/server` folder](https://github.com/auth0-blog/vue-auth0-aside/tree/master/server) at the root of our sample application. + +Find the [`config.js.example` file](https://github.com/auth0-blog/vue-auth0-aside/blob/master/server/config.js.example) and **remove** the `.example` extension from the filename. Then open the file: + +```js +// server/config.js +// (formerly config.js.example) +module.exports = { + CLIENT_DOMAIN: '[CLIENT_DOMAIN]', // e.g. 'you.auth0.com' + AUTH0_AUDIENCE: 'http://meetupapi.com' +}; +``` + +Change the `CLIENT_DOMAIN` variable to your Auth0 application domain and set the `AUTH0_AUDIENCE` to your audience (in this example, this is `http://meetupapi.com`). The `/api/examples/private` route will be protected with [express-jwt](https://github.com/auth0/express-jwt) and [jwks-rsa](https://github.com/auth0/node-jwks-rsa). + +> **Note:** To learn more about RS256 and JSON Web Key Set, read [Navigating RS256 and JWKS](https://auth0.com/blog/navigating-rs256-and-jwks/). + +Our app and API are now set up. They can be served by running `npm run dev` from the root folder and `node server.js` from the `/server` folder. + +With the Node API and the Vue.js app running, let's take a look at how authentication is implemented. + +### Authentication Service + +Authentication logic on the front end is handled with an `Auth` authentication service: [`src/auth/Auth.js` file](https://github.com/auth0-blog/vue-auth0-aside/blob/master/src/auth/Auth.js). + +```js +// src/auth/Auth.js +/* eslint-disable */ +import auth0 from 'auth0-js'; +import router from '../router'; + +export default class Auth { + + auth0 = new auth0.WebAuth({ + domain: AUTH0_DOMAIN, // e.g., you.auth0.com + clientID: AUTH0_CLIENT_ID, // e.g., i473732832832cfgajHYEUqiqwq + redirectUri: CALLBACK_URL, // e.g., http://localhost:8080/callback + audience: AUTH0_API_AUDIENCE, // e.g., https://meetupapi.com + responseType: 'token', + scope: 'openid' + }); + + constructor() { + this.login = this.login.bind(this); + this.handleAuthentication = this.handleAuthentication.bind(this); + this.logout = this.logout.bind(this); + } + + handleAuthentication() { + this.auth0.parseHash((err, authResult) => { + if (authResult && authResult.accessToken) { + this.setSession(authResult); + router.replace('/'); + } else if (err) { + router.replace('/'); + } + }) + } + + setSession(authResult) { + // Set the time that the access token will expire at + const expiresAt = JSON.stringify(authResult.expiresIn * 1000 + new Date().getTime()); + localStorage.setItem('access_token', authResult.accessToken); + localStorage.setItem('expires_at', expiresAt); + } + + requireAuth(to, from, next) { + if (! (new Auth).isAuthenticated()) { + next({ + path: '/', + query: { redirect: to.fullPath } + }); + } else { + next(); + } + } + + + login() { + this.auth0.authorize(); + } + + logout() { + // Clear access token and expiration from local storage + localStorage.removeItem('access_token'); + localStorage.removeItem('expires_at'); + // navigate to the landing page route + router.go('/'); + } + + isAuthenticated() { + // Check whether the current time is past the + // access token's expiry time + const expiresAt = JSON.parse(localStorage.getItem('expires_at')); + return new Date().getTime() < expiresAt; + } +} +``` + +Replace the constants, `AUTH0_DOMAIN`, `AUTH0_CLIENT_ID`, `AUTH0_API_AUDIENCE` with values from your Auth0 dashboard. Replace `CALLBACK_URL` with `http://localhost:8080/callback`. + +The `login()` method authorizes the authentication request with Auth0. An Auth0 login page will be shown to the user and they can then log in. + +> **Note:** If it's the user's first visit to our app _and_ our callback is on `localhost`, they'll also be presented with a consent screen where they can grant access to our API. A first party application on a non-localhost domain would be highly trusted, so the consent dialog would not be presented in this case. You can modify this by editing your [Auth0 Dashboard API](https://manage.auth0.com/#/apis) **Settings**. Look for the "Allow Skipping User Consent" toggle. + +We'll receive `accessToken` and `expiresIn` in the hash from Auth0 when returning to our app. The `handleAuthentication()` method uses Auth0's `parseHash()` method callback to set the session (`setSession()`) by saving the tokens, and token expiration to local storage. The `isAuthenticated` method informs the components in the app about the user's authentication status via checking the access token's expiry time. + +Finally, we have a `logout()` method that clears data from local storage. + +### Callback Component + +The [callback component](https://github.com/auth0-blog/vue-auth0-aside/tree/master/src/components/Callback.vue) is where the app is redirected after authentication. This component simply shows a loading message until the login process is completed. It executes the `handleAuthentication()` method to parse the hash and extract authentication information. + +```js +// src/components/Callback.vue + + +``` + +### Making Authenticated API Requests + +In order to make authenticated HTTP requests, we need to add an `Authorization` header with the access token in our [`meetup-api.js` file](https://github.com/auth0-blog/vue-auth0-aside/blob/master/utils/meetup-api.js). + +```js +// utils/meetup-api.js +/* eslint-disable */ +import axios from 'axios'; +import Auth from '../src/auth/Auth.js'; + +const auth = new Auth(); +const BASE_URL = 'http://localhost:3333'; + +export function getPublicMeetups() { + const url = `${BASE_URL}/api/meetups/public`; + return axios.get(url).then(response => response.data).catch(err => err || 'Unable to retrieve data'); +} + +export function getPrivateMeetups() { + const url = `${BASE_URL}/api/meetups/private`; + return axios.get(url, { headers: { Authorization: `Bearer ${localStorage.getItem('access_token')}` }}).then(response => response.data).catch(err => err || 'Unable to retrieve data'); +} +``` + +### Final Touches: Route Guard and Private Meetups Page + +A [Private meetup page component](https://github.com/auth0-blog/angular-auth0-aside/tree/master/src/app/profile) can show information about private meetups. However, we only want this component to be accessible if the user is logged in. + +The route guard is implemented on specific routes of our choosing in the [`router/index.js` file](https://github.com/auth0-blog/vue-auth0-aside/blob/master/src/router/index.js) like so: + +```js +// src/router/index.js +import Vue from 'vue'; +import Router from 'vue-router'; +import PublicMeetups from '@/components/PublicMeetups'; +import PrivateMeetups from '@/components/PrivateMeetups'; +import Callback from '@/components/Callback'; +import Auth from '../auth/Auth'; + +const auth = new Auth(); + +Vue.use(Router); + +export default new Router({ + mode: 'history', + routes: [ + { + path: '/', + name: 'PublicMeetups', + component: PublicMeetups, + }, + { + path: '/private-meetups', + name: 'PrivateMeetups', + beforeEnter: auth.requireAuth, + component: PrivateMeetups, + }, + { + path: '/callback', + component: Callback, + }, + ], +}); +``` + +### More Resources + +That's it! We have an authenticated Node API and Vue.js application with login, logout, and protected routes. To learn more, check out the following resources: + +* [Why You Should Always Use Access Tokens to Secure an API](https://auth0.com/blog/why-should-use-accesstokens-to-secure-an-api/) +* [Navigating RS256 and JWKS](https://auth0.com/blog/navigating-rs256-and-jwks/) +* [Access Token](https://auth0.com/docs/tokens/access-token) +* [Verify Access Tokens](https://auth0.com/docs/api-auth/tutorials/verify-access-token) +* [Call APIs from Client-side Web Apps](https://auth0.com/docs/api-auth/grant/implicit) +* [How to implement the Implicit Grant](https://auth0.com/docs/api-auth/tutorials/implicit-grant) +* [Auth0.js Documentation](https://auth0.com/docs/libraries/auth0js) +* [OpenID Standard Claims](https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims) diff --git a/_includes/banner_blog.html b/_includes/banner_blog.html new file mode 100644 index 0000000000..29958abe6b --- /dev/null +++ b/_includes/banner_blog.html @@ -0,0 +1,24 @@ +{% assign current = page %} +{% assign is_banner_post = true %} + +{% if page.is_post_index %} + {% assign current = post %} +{% endif %} + +
    +
    +
    +

    Blog

    + +

    + Company Updates & Technology Articles +

    + + + + +
    +
    +
    + {% include navigation.html %} +
    diff --git a/_includes/banner_post.html b/_includes/banner_post.html new file mode 100644 index 0000000000..468a1bc3a4 --- /dev/null +++ b/_includes/banner_post.html @@ -0,0 +1,39 @@ +{% assign current = page %} +{% assign is_banner_post = true %} + +{% if page.is_post_index %} + {% assign current = post %} +{% endif %} + + diff --git a/_includes/custom-try-banner-extend.html b/_includes/custom-try-banner-extend.html new file mode 100644 index 0000000000..5068d58055 --- /dev/null +++ b/_includes/custom-try-banner-extend.html @@ -0,0 +1,25 @@ +{% assign current = page %} + +
    + +
    diff --git a/_includes/custom-try-banner.html b/_includes/custom-try-banner.html new file mode 100644 index 0000000000..3186142097 --- /dev/null +++ b/_includes/custom-try-banner.html @@ -0,0 +1,25 @@ +{% assign current = page %} + +
    + +
    diff --git a/_includes/entry_image.html b/_includes/entry_image.html new file mode 100644 index 0000000000..73a5ae2cf9 --- /dev/null +++ b/_includes/entry_image.html @@ -0,0 +1,54 @@ +{% capture opacity %} + {% if {{current.design.image_opacity}} %} + opacity: {{current.design.image_opacity}}; + {% endif %} +{% endcapture %} + +{% capture size %} + {% if current.design.image %} + {% if {{current.design.image_size}} %} + max-width: {{current.design.image_size}}; + {% endif %} + {% else %} + max-width: 50%; + top: 53%; + {% endif %} +{% endcapture %} + +{% capture top %} + {% if {{current.design.image_top}} %} + top: {{current.design.image_top}}; + {% endif %} +{% endcapture %} + +{% capture left %} + {% if {{current.design.image_left}} %} + left: {{current.design.image_left}}; + {% endif %} +{% endcapture %} + +{% capture bg_merge %} +{% if current.design.bg_merge and is_banner_post %} + border-radius: 0; +{% endif %} +{% endcapture %} + +{% capture image_bg_color %} +{% if current.design.image_bg_color %} + background: {{current.design.image_bg_color}}; +{% endif %} +{% endcapture %} + + +
    + {{ current.title }} + + + +
    +
    diff --git a/_includes/entry_image_search.html b/_includes/entry_image_search.html new file mode 100644 index 0000000000..6827eadd9a --- /dev/null +++ b/_includes/entry_image_search.html @@ -0,0 +1,11 @@ + +
    + {title} + + + +
    +
    diff --git a/_includes/entry_metadata.html b/_includes/entry_metadata.html deleted file mode 100644 index b16d3b210d..0000000000 --- a/_includes/entry_metadata.html +++ /dev/null @@ -1,11 +0,0 @@ - diff --git a/_includes/entry_metadata_home.html b/_includes/entry_metadata_home.html deleted file mode 100644 index dbf7ce82e8..0000000000 --- a/_includes/entry_metadata_home.html +++ /dev/null @@ -1,11 +0,0 @@ - diff --git a/_includes/experiment_disclaimer.html b/_includes/experiment_disclaimer.html new file mode 100644 index 0000000000..cf214c371c --- /dev/null +++ b/_includes/experiment_disclaimer.html @@ -0,0 +1,6 @@ +{% if page.experimental == true %} +
    + Attention
    +

    This post contains experimental code that has not undergone a thorough security review and should not be used in production without further vetting. Please use discretion when implementing in your code.

    +
    +{% endif %} \ No newline at end of file diff --git a/_includes/footer.html b/_includes/footer.html index 34ac34692d..394366b18e 100644 --- a/_includes/footer.html +++ b/_includes/footer.html @@ -1,7 +1,82 @@ -
    - Powered by Jekyll and - Source code on github. -
    + + diff --git a/_includes/footer_extend.html b/_includes/footer_extend.html new file mode 100644 index 0000000000..75841d28c8 --- /dev/null +++ b/_includes/footer_extend.html @@ -0,0 +1,21 @@ + \ No newline at end of file diff --git a/_includes/head.html b/_includes/head.html new file mode 100644 index 0000000000..f780cd50a2 --- /dev/null +++ b/_includes/head.html @@ -0,0 +1,160 @@ + + + + + + {% include sharing_metas.html %} + {% include locales.html %} + + {% if page.is_tag_index or page.switch_index %} + {{ page.tag | capitalize }} - Auth0 Blog + {% else %} + {{ page.title }} + + + + + + {% endif %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + {% if page.meta-robots %} + + {% endif %} + + {% if paginator.page > 1 %} + + {% endif %} + + {% if page.is_tag_index %} + + {% else %} + {% if page.url %} + + {% endif %} + {% endif %} + + {% if page.is_tag_index %} + + {% else %} + {% if page.url %} + + {% endif %} + {% endif %} + + + {% if page.is_tag_index %} + {% if paginator.previous_page %} + + {% endif %} + {% if paginator.next_page %} + + {% endif %} + {% else %} + {% if paginator.previous_page %} + + {% endif %} + {% if paginator.next_page %} + + {% endif %} + {% endif %} + + + + + + + + + + + + + diff --git a/_includes/head_extend.html b/_includes/head_extend.html new file mode 100644 index 0000000000..57463659f1 --- /dev/null +++ b/_includes/head_extend.html @@ -0,0 +1,166 @@ + + + + + {% include sharing_metas.html %} + + {% if page.is_tag_index or page.switch_index %} + {{ page.tag | capitalize }} - Auth0 Blog + {% else %} + {{ page.title }} + + + + + + {% endif %} + + {% if page.canonical_url %} + + {% endif %} + + + + + + + + + + + + + + + + + + + + + + + + + + + {% if page.meta-robots %} + + {% endif %} + + {% if paginator.page > 1 %} + + {% endif %} + + {% if page.is_tag_index %} + + {% else %} + {% if page.url %} + + {% endif %} + {% endif %} + + + + + {% if page.is_tag_index %} + {% if paginator.previous_page %} + + {% endif %} + {% if paginator.next_page %} + + {% endif %} + {% else %} + {% if paginator.previous_page %} + + {% endif %} + {% if paginator.next_page %} + + {% endif %} + {% endif %} + + + + + + + + + + + + + + + + diff --git a/_includes/locales.html b/_includes/locales.html new file mode 100644 index 0000000000..4ca4f74838 --- /dev/null +++ b/_includes/locales.html @@ -0,0 +1,12 @@ +{% if page.alternate_locale_en %} + +{% endif %} +{% if page.alternate_locale_es %} + +{% endif %} +{% if page.alternate_locale_de %} + +{% endif %} +{% if page.alternate_locale_ja %} + +{% endif %} diff --git a/_includes/navigation.html b/_includes/navigation.html new file mode 100644 index 0000000000..527a324acb --- /dev/null +++ b/_includes/navigation.html @@ -0,0 +1,29 @@ + diff --git a/_includes/newsletter.html b/_includes/newsletter.html new file mode 100644 index 0000000000..b9068dcbcd --- /dev/null +++ b/_includes/newsletter.html @@ -0,0 +1,15 @@ +
    + +
    diff --git a/_includes/newsletter_extend.html b/_includes/newsletter_extend.html new file mode 100644 index 0000000000..96a6bd67d1 --- /dev/null +++ b/_includes/newsletter_extend.html @@ -0,0 +1,15 @@ +
    + +
    diff --git a/_includes/paging.html b/_includes/paging.html new file mode 100644 index 0000000000..6b32fc8a7e --- /dev/null +++ b/_includes/paging.html @@ -0,0 +1,106 @@ + + +{% if page.is_tag_index %} + + + +{% else %} + + {% if page.switch_index %} + + + {% else %} + + {% if page.is_list_amp_index %} + + + {% else %} + + + {% endif%} + {% endif%} +{% endif %} diff --git a/_includes/pdf_bonus.html b/_includes/pdf_bonus.html new file mode 100644 index 0000000000..d0b99c541d --- /dev/null +++ b/_includes/pdf_bonus.html @@ -0,0 +1,135 @@ +{% comment %} +This is the template used for bonus PDF content. + +To use it just include it in a post (Don't use the outer quotes): + +'{% include pdf_bonus.html text="description text" pdf="pdf_id" modal-title="Get the PDF" %}' + +The PDF id uniquely identifies a PDF in our system. + +You can also save the text in a variable + +'{% assign text = "Some more text" %}' + +'{% include pdf_bonus.html text=text %}' + +{% endcomment %} + +{% assign current = page %} + + + + diff --git a/_includes/popular_posts.html b/_includes/popular_posts.html new file mode 100644 index 0000000000..ffd4bf7f0b --- /dev/null +++ b/_includes/popular_posts.html @@ -0,0 +1,30 @@ + + + diff --git a/_includes/popup_notification.html b/_includes/popup_notification.html new file mode 100644 index 0000000000..787503a0aa --- /dev/null +++ b/_includes/popup_notification.html @@ -0,0 +1,12 @@ +
    + +
    diff --git a/_includes/reply_buttons.html b/_includes/reply_buttons.html new file mode 100644 index 0000000000..01d1439cae --- /dev/null +++ b/_includes/reply_buttons.html @@ -0,0 +1,17 @@ +{% if current.reply %} +
    +{% if current.reply.hn %} + + HN + Discuss on Hacker News + +{% endif %} + +{% if current.reply.twitter %} + + + Reply on Twitter + +{% endif %} +
    +{% endif %} \ No newline at end of file diff --git a/_includes/results_post.html b/_includes/results_post.html new file mode 100644 index 0000000000..b91e66962d --- /dev/null +++ b/_includes/results_post.html @@ -0,0 +1,36 @@ + + diff --git a/_includes/search_box.html b/_includes/search_box.html deleted file mode 100644 index a509666fda..0000000000 --- a/_includes/search_box.html +++ /dev/null @@ -1,7 +0,0 @@ -
    -
    - -
    -
    \ No newline at end of file diff --git a/_includes/sharing.html b/_includes/sharing.html deleted file mode 100644 index e3f958b537..0000000000 --- a/_includes/sharing.html +++ /dev/null @@ -1,30 +0,0 @@ - - -
    - - - - - - - diff --git a/_includes/sharing_buttons.html b/_includes/sharing_buttons.html deleted file mode 100644 index 72bcc0e266..0000000000 --- a/_includes/sharing_buttons.html +++ /dev/null @@ -1,32 +0,0 @@ - diff --git a/_includes/sharing_metas.html b/_includes/sharing_metas.html index b5e572c921..305cd20e41 100644 --- a/_includes/sharing_metas.html +++ b/_includes/sharing_metas.html @@ -1,15 +1,26 @@ - +{% if page.is_tag_index or page.switch_index %} + +{% else %} + +{% endif %} - - - + +{% if page.design.image_fb %} + +{% else %} + {%if page.design.image %} + + {% else %} + + {% endif %} +{% endif%} -{% if page.excerpt %} - +{% if page.description %} + {% else %} - + {% endif %} {% if page.path contains '_posts' %} @@ -17,3 +28,45 @@ {% else %} {% endif %} + + + + +{% if page.title %} + +{% else %} + {% if page.is_tag_index or page.switch_index %} + + {% else %} + + {% endif %} +{% endif %} + +{% if page.url %} + +{% endif %} +{% if page.description %} + +{% else %} + +{% endif %} + + +{% if page.is_post_index %} + + +{% else %} +{% if page.design.image_tw %} + + +{% else if page.design.image %} + + +{% endif %} +{% endif %} + + + + + + diff --git a/_includes/single_page.html b/_includes/single_page.html deleted file mode 100644 index 1ab0275dc8..0000000000 --- a/_includes/single_page.html +++ /dev/null @@ -1,27 +0,0 @@ - -
    \ No newline at end of file diff --git a/_includes/social_buttons.html b/_includes/social_buttons.html new file mode 100644 index 0000000000..08a1e243f6 --- /dev/null +++ b/_includes/social_buttons.html @@ -0,0 +1,59 @@ +{% if page.is_post_index %} + {% assign current = post %} +{% else %} + {% assign current = page %} +{% endif %} + +{% if current.alias %} + {% assign url = current.alias %} +{% else %} + {% assign url = current.url %} +{% endif %} + + diff --git a/_includes/social_buttons_search.html b/_includes/social_buttons_search.html new file mode 100644 index 0000000000..66afea7c7b --- /dev/null +++ b/_includes/social_buttons_search.html @@ -0,0 +1,58 @@ + + diff --git a/_includes/tweet_quote.html b/_includes/tweet_quote.html new file mode 100644 index 0000000000..d3a2f1c816 --- /dev/null +++ b/_includes/tweet_quote.html @@ -0,0 +1,31 @@ +{% comment %} +This is the template used for tweeteable quotes. + +To use it just include it in a post (Don't use the outer quotes): + +'{% include tweet_quote.html quote_text="the quote text" %}' + +You can also save the text in a variable + +'{% assign quote_text = "Some more text" %}' + +'{% include tweet_quote.html quote_text=quote_text %}' + +{% endcomment %} + +{% assign current = page %} +{% assign amp_page = current.url | scan_filter: '^\/amp\/+' %} + +
    + +

    "{{ include.quote_text }}"

    + +
    +
    \ No newline at end of file diff --git a/_includes/utility_bar.html b/_includes/utility_bar.html new file mode 100644 index 0000000000..d1c9dd4722 --- /dev/null +++ b/_includes/utility_bar.html @@ -0,0 +1,12 @@ +
    +
    + + + +
    +
    diff --git a/_includes/utility_bar_extend.html b/_includes/utility_bar_extend.html new file mode 100644 index 0000000000..57f3040c1a --- /dev/null +++ b/_includes/utility_bar_extend.html @@ -0,0 +1,12 @@ +
    +
    + + + +
    +
    diff --git a/_includes/widget_guest_author.html b/_includes/widget_guest_author.html new file mode 100644 index 0000000000..1b69672df0 --- /dev/null +++ b/_includes/widget_guest_author.html @@ -0,0 +1,47 @@ +
    + + +
    diff --git a/_layouts/amp.html b/_layouts/amp.html new file mode 100644 index 0000000000..0f41dcad13 --- /dev/null +++ b/_layouts/amp.html @@ -0,0 +1,116 @@ + + + + + + + + + + + +{% if page.title%} +{{ page.title }} +{% else %} +Auth0 Blog +{%endif%} + +{% if page.is_list_amp_index %} + +{%else %} + +{%endif%} + + + + {% if page.meta-robots %} + + {% endif %} + + {% if paginator.page > 1 %} + + {% endif %} + + + +{% if page.is_post_index %} + + +{% endif %} + + + + + + + {% include /amp/amp_sidebar.html %} + +
    + {% include /amp/nav-bar.html %} + {{content}} +
    + + + + + diff --git a/_layouts/amp_index.html b/_layouts/amp_index.html new file mode 100644 index 0000000000..d2dd6d3834 --- /dev/null +++ b/_layouts/amp_index.html @@ -0,0 +1,34 @@ +--- +layout: amp +meta-robots: "noodp, noydir, noindex, noarchive, follow" +is_post_index: true +sitemap: + exclude: 'yes' +--- + +{% assign post = page.post %} + +
    + {% include amp/banner_post.html %} +
    +
    +
    +
    +
    +
    + {{ post.content | markdownify_includes | markdownify | amp_images }} +
    +
    +
    +
    +
    +
    +
    +
    + + + +
    + +
    +
    diff --git a/_layouts/amp_list_post.html b/_layouts/amp_list_post.html new file mode 100644 index 0000000000..055a8e7627 --- /dev/null +++ b/_layouts/amp_list_post.html @@ -0,0 +1,58 @@ +--- +layout: amp +meta-robots: "noodp, noydir, noarchive, follow" +is_list_amp_index: true +--- + +
    +
      + {% for post in paginator.posts %} + {% if forloop.first %} +
    • +
      +
      + +
      +
      +
      + + +
      +
    • + {% else %} +
    • +
      +
      + +
      +
      + +

      {{post.title}}

      +
      + {{post.author.name | capitalize }} +
      +
      +
    • + {%endif%} + {% endfor %} +
    +
    +{% include paging.html %} diff --git a/_layouts/amp_tag.html b/_layouts/amp_tag.html new file mode 100644 index 0000000000..3067926a18 --- /dev/null +++ b/_layouts/amp_tag.html @@ -0,0 +1,59 @@ +--- +layout: amp +meta-robots: "noodp, noydir, noarchive, follow" +is_list_amp_index: true +is_tag_index: true +--- + +
    +
      + {% for post in paginator.posts limit: 6 %} + {% if forloop.first %} +
    • +
      +
      + +
      +
      +
      + + +
      +
    • + {% else %} +
    • +
      +
      + +
      +
      + +

      {{post.title}}

      +
      + {{post.author.name | capitalize }} +
      +
      +
    • + {%endif%} + {% endfor %} +
    +
    +{% include paging.html %} diff --git a/_layouts/amp_tag_index.html b/_layouts/amp_tag_index.html new file mode 100644 index 0000000000..e01966033e --- /dev/null +++ b/_layouts/amp_tag_index.html @@ -0,0 +1,59 @@ +--- +layout: amp +meta-robots: "noodp, noydir, noarchive, follow" +is_list_amp_index: true +is_tag_index: true +--- + +
    +
      + {% for post in site.tags[page.tag] limit: 5 %} + {% if forloop.first %} +
    • +
      +
      + +
      +
      +
      + + +
      +
    • + {% else %} +
    • +
      +
      + +
      +
      + +

      {{post.title}}

      +
      + {{post.author.name | capitalize }} +
      +
      +
    • + {%endif%} + {% endfor %} +
    +
    +{% include paging.html %} diff --git a/_layouts/archive_index.html b/_layouts/archive_index.html index 671031a7f0..09330ef9bb 100644 --- a/_layouts/archive_index.html +++ b/_layouts/archive_index.html @@ -1,13 +1,17 @@ --- layout: default meta-robots: "noodp, noydir, noindex, noarchive, follow" +sitemap: + exclude: 'yes' ---

    Posts archive: {{ page.period["month"] }} - {{page.period["year"]}}


      {% for post in page.period_posts %} - {% unless post.draft %} - {% include archive_post.html %} + {% unless post.hide_from_index %} + {% unless post.press_release %} + {% include archive_post.html %} + {% endunless %} {% endunless %} {% endfor %} -
    \ No newline at end of file + diff --git a/_layouts/default.html b/_layouts/default.html index fdc521b70e..0f718d86c1 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -1,236 +1,631 @@ - - - - + - {% include sharing_metas.html %} + {% include head.html %} - {{ page.title }} + - - - - {% if page.meta-robots %} - - {% endif %} + + + {% include popup_notification.html %} + + + +
    + + +
    + + + + + + + + {{ content }} + {% include footer.html %}
    + + + {% if page.is_post_index %} + + + + + + + {% endif %} + + + + - - + + + + + + + + + + + + + + + + + + + - + + + + + diff --git a/_layouts/default_extend.html b/_layouts/default_extend.html new file mode 100644 index 0000000000..37f3415c38 --- /dev/null +++ b/_layouts/default_extend.html @@ -0,0 +1,608 @@ + + + + {% include head_extend.html %} + + + + + + {% include popup_notification.html %} + +
    + + +
    + + + + + + + + {{ content }} + {% include footer_extend.html %} +
    + + + + {% if page.is_post_index %} + + + + + + + {% endif %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/_layouts/post.html b/_layouts/post.html index b558d49d8d..53fe18140e 100644 --- a/_layouts/post.html +++ b/_layouts/post.html @@ -1,74 +1,95 @@ --- layout: default +is_post: true --- - -
    -
    -

    - - {{ page.title }} -

    - - {% include entry_metadata.html %} - -
    - {{ content }} -
    + + + - + ); + } +} + +// We’re using the mixin `LinkStateMixin` to have two-way databinding between our component and the HTML. +reactMixin(Login.prototype, React.addons.LinkedStateMixin); +``` + +#### The AuthService & the LoginAction +![AuthService role in ReactJS Authentication](https://docs.google.com/drawings/d/1FFBWhfS7hqtq4qcXXsNEtOW2T8f5RkEiq50D4MTlDyw/pub?w=959&h=638) + +Our AuthService is in charge of calling our [login API](https://github.com/auth0/nodejs-jwt-authentication-sample/blob/master/user-routes.js#L37-L54). The server will validate the username and password and return a token [(JWT)](http://jwt.io/) back to our app. Once we get it, we’ll create a [LoginAction](https://facebook.github.io/flux/docs/todo-list.html#creating-semantic-actions) and send it to all the [Stores](https://facebook.github.io/flux/docs/todo-list.html#creating-stores) using the [Dispatcher](https://facebook.github.io/flux/docs/todo-list.html#creating-a-dispatcher) from Flux. + +```jsx +// AuthService.js +// ... imports +class AuthService { + + login(username, password) { + // We call the server to log the user in. + return when(request({ + url: ‘http://localhost:3001/sessions/create', + method: ‘POST’, + crossOrigin: true, + type: ‘json’, + data: { + username, password + } + })) + .then(function(response) { + // We get a JWT back. + let jwt = response.id_token; + // We trigger the LoginAction with that JWT. + LoginActions.loginUser(jwt); + return true; + }); + } +} + +export default new AuthService() +``` + +```js +// LoginAction.js +// ... imports +export default { + loginUser: (jwt) => { + // Go to the Home page once the user is logged in + RouterContainer.get().transitionTo(‘/‘); + // We save the JWT in localStorage to keep the user authenticated. We’ll learn more about this later. + localStorage.setItem(‘jwt’, jwt); + // Send the action to all stores through the Dispatcher + AppDispatcher.dispatch({ + actionType: LOGIN_USER, + jwt: jwt + }); + } +} +``` + +You can take a look at the router configuration [on Github](https://github.com/auth0/react-flux-jwt-authentication-sample/blob/gh-pages/src/app.jsx#L11-L29), but it’s important to note that once the `LoginAction` is triggered, the user is successfully authenticated. Therefore, we need to redirect him or her from the Login page to the Home. That’s why we’re adding the URL transition in here. + +#### The LoginStore +![Dispatcher and LoginStore](https://docs.google.com/drawings/d/1_IAM5yjabjPK6EGq7dfliV_rRISTrsT7BlKl9MSX1D0/pub?w=959&h=638) + +The LoginStore, like any other store, has 2 functions: + +* It holds the data it gets from the actions. In our case, that data will be used by all components that need to display the user information. +* It inherits from `EventEmmiter`. It’ll emit a change event every time its data changes so that Components can be rendered again. + +```jsx +// ... imports +class LoginStore extends BaseStore { + + constructor() { + // First we register to the Dispatcher to listen for actions. + this.dispatchToken = AppDispatcher.register(this._registerToActions.bind(this)); + this._user = null; + this._jwt = null; + } + + _registerToActions(action) { + switch(action.actionType) { + case USER_LOGGED_IN: + // We get the JWT from the action and save it locally. + this._jwt = action.jwt; + // Then we decode it to get the user information. + this._user = jwt_decode(this._jwt); + // And we emit a change to all components that are listening. + // This method is implemented in the `BaseStore`. + this.emitChange(); + break; + default: + break; + }; + } + + // Just getters for the properties it got from the action. + get user() { + return this._user; + } + + get jwt() { + return this._jwt; + } + + isLoggedIn() { + return !!this._user; + } +} +export default new LoginStore(); +``` + +> You can take a look at the `BaseStore` [in Github](https://github.com/auth0/react-flux-jwt-authentication-sample/blob/gh-pages/src/stores/BaseStore.js). It includes some utility methods that all stores will have. + +### Displaying the user information +#### Creating an Authenticated component +![AuthenticatedComponent](https://docs.google.com/drawings/d/1LlRJ_EC6M11wLzGgicTv5DJGOOxMGl4P-yX5LfvGlgs/pub?w=959&h=638) + +Now, we can start creating components that require authentication. For that, we’ll create a wrapper (or decorator) component called `AuthenticatedComponent`. It’ll make sure the user is authenticated before displaying its content. If the user isn’t authenticated, it’ll redirect him or her to the Login page. Otherwise, it’ll send the user information to the component it’s wrapping: + +```jsx +// ... imports +export default (ComposedComponent) => { + return class AuthenticatedComponent extends React.Component { + + static willTransitionTo(transition) { + // This method is called before transitioning to this component. If the user is not logged in, we’ll send him or her to the Login page. + if (!LoginStore.isLoggedIn()) { + transition.redirect(‘/login’); + } + } + + constructor() { + this.state = this._getLoginState(); + } + + _getLoginState() { + return { + userLoggedIn: LoginStore.isLoggedIn(), + user: LoginStore.user, + jwt: LoginStore.jwt + }; + } + + // Here, we’re subscribing to changes in the LoginStore we created before. Remember that the LoginStore is an EventEmmiter. + componentDidMount() { + LoginStore.addChangeListener(this._onChange.bind(this)); + } + + // After any change, we update the component’s state so that it’s rendered again. + _onChange() { + this.setState(this._getLoginState()); + } + + componentWillUnmount() { + LoginStore.removeChangeListener(this._onChange.bind(this)); + } + + render() { + return ( + + ); + } + } +}; +``` + +An interesting pattern is used here. +First, take a look at what we’re exporting. We’re exporting a function that receives a Component as a parameter and then returns a new Component that wraps the one that was sent as an argument. +Next, take a look at the `render` method. There, we’re rendering the Component we received as a parameter. Besides the `props` it should receive, we’re also sending it all the user information so it can use those properties. +Now, let’s create the Home component which will be wrapped by the `AuthenticatedComponent` we’ve just created. + +#### Home Page +![Home](https://docs.google.com/drawings/d/1kZBxoxkMMQe2-VZb1kJPjoQQ3EZ_kJ1lw1Bg2zQ9JG4/pub?w=959&h=638) + +The `Home` will display user information. As it’s wrapped by the `AuthenticatedComponent`, we can be sure of 2 things: + +* Once the `render` method is called on the `Home` component, we know the user is authenticated. Otherwise, the app would have redirected him to the `Login` page. +* We know we’ll have the user information under `props` because we’ve received them from the `AuthenticatedComponent` + +```jsx +// ... imports +// We’re wrapping the home with the AuthenticatedComponent +export default AuthenticatedComponent(class Home extends React.Component { + render() { + // Here, we display the user information + return (

    Hello {this.props.user.username}

    ); + } +}); +``` + +### Let’s call an API! + +Now, you should be able to call an API. In order to call an API that requires authentication, you must send the JWT we received on Login in the `Authorization` header. Any `AuthenticatedComponent` has access to this JWT so you can do something as follows: + +```jsx +// Home.jsx +// It must be on an AuthenticatedComponent +callApi() { + fetch(‘http://example.com/my-cool-url', { + method: ‘GET’, + headers: { + Authorization: ‘Bearer ‘ + this.props.jwt + } + } +``` + +### Keeping the user authenticated +Now that the user is authenticated, we want to keep him or her authenticated instead of showing the login page every time he refreshes the website. +Due to the fact we’re saving the JWT on `localStorage` after a successful authentication, we can manually trigger the `LoginAction` and everything will work. That’s the beauty of using Flux. + +```js +// app.jsx ==> Bootstrap file +let jwt = localStorage.getItem(‘jwt’); +if (jwt) { + LoginActions.loginUser(jwt); +} +``` + +{% include asides/react.markdown %} + +## Closing remarks + +We’ve finished implementing the Login for a React Flux app. If you want to know how to implement a signup or if you want to see the full example at work, you can [grab the code from Github](https://github.com/auth0/react-flux-jwt-authentication-sample). + +Happy Hacking! :). diff --git a/_posts/2015-04-15-update-of-the-user-details-section.markdown b/_posts/2015-04-15-update-of-the-user-details-section.markdown new file mode 100644 index 0000000000..4134b9c0d5 --- /dev/null +++ b/_posts/2015-04-15-update-of-the-user-details-section.markdown @@ -0,0 +1,76 @@ +--- +layout: post +title: "Update of the user's details section" +description: "Good news! We have redesigned the user's profile page in order to make use of the new API v2." +date: 2015-04-15 15:57 +alias: /2015/04/15/update-of-the-user-details-section/ +category: Product +author: + name: Pablo Terradillos + url: http://twitter.com/tehsis + mail: tehsis@auth0.com + avatar: https://s.gravatar.com/avatar/647b1eea820b3fc8a5aee0383930b888?s=60 +design: + image: https://cldup.com/MTjz9l-URX.png + image_size: "200%" + image_top: "90%" + image_left: "90%" +tags: +- dashboard +- product +related: +- 2016-04-18-progressive-profiling +- 2016-04-14-safely-use-best-customer-retention-tactics +- 2015-09-11-7-ways-to-2x-your-revenue-growth-by-putting-your-user-data-to-work +--- + +**TL;DR** + + * We have redesigned the user's profile page to use the new [API v2](https://auth0.com/docs/apiv2). + * API v2 brings an improved way of [handling metadata](https://auth0.com/docs/apiv2Changes#8). In short, metadata will be stored in a separate section (__app\_metadata__ and __user\_metadata__) on the user structure and not merged with root attributes, which had caused confusion before. + * The data you've modified in our previous dashboard version or using API v1 is now under __app\_metadata__. + * This is a change on the __Dashboard__, not on the runtime. There are __no breaking changes__ to your apps. + + + +Good news! We have redesigned the user's profile page in order to make use of the new [API v2](https://auth0.com/docs/apiv2). + +User's data is now separated in three sections, each one with different meanings according to what it represents. + +### User Identity Section + +On the main section you will find essential data such as the user's email and login access information. + +![](https://cdn.auth0.com/blog/new-profile-1.png) + +### Metadata Section + +The __Metadata__ section is the part of the user's data that you can modify. + +In the previous user's profile, the _metadata_ was merged with the user's root-level attributes which created some confusion. To make everythig more explicit and easier to find, we've decided to keep it separate. + +![](https://cdn.auth0.com/blog/new-profile-2.png) + +If you modified user attributes in the past through the dashboard, you will find the additional attributes under __app\_metadata__ after this change (since the dashboard was using API v1). + +> [Learn more](https://auth0.com/docs/apiv2Changes#8) about when to use __app\_metadata__ vs __user\_metadata__. + +### Identity Provider Attributes Section + +Last but not least, there's the __Identity Provider Attributes__ section. Here you will find all the information retrieved from the authentication provider (e.g. Facebook, Twitter, Google, SAML, your own provider, etc.). + +![](https://cdn.auth0.com/blog/new-profile-3.png) + +This data is read-only. Let's say you'd like to modify the picture that is coming from the Facebook profile. You won't be able to change the attribute in the __Identity Provider Attributes__ section. Instead you need to set the `picture` attribute in the `user_metadata` property and then in your application you would do: + +``` + +``` + +The previous code snippet tries to use the `picture` property from `user_metadata` and if it doesn't exist it uses the default (`user.picture`). + +### Raw JSON + +Finally, you can easily take a look at the raw JSON format that will be served by our API at the new __Raw JSON__ tab. + +![](https://cdn.auth0.com/blog/new-profile-4.png) diff --git a/_posts/2015-04-27-auth0-europe-launches.markdown b/_posts/2015-04-27-auth0-europe-launches.markdown new file mode 100644 index 0000000000..fb55f7f683 --- /dev/null +++ b/_posts/2015-04-27-auth0-europe-launches.markdown @@ -0,0 +1,54 @@ +--- +layout: post +title: "Auth0 Europe Launches" +description: "With Auth0 Europe subscribers everywhere that have the compliance requirement of EU data residence can check that box." +date: 2015-04-27 13:26 +alias: /2015/04/27/auth0-europe-launches/ +category: Announcement +author: + name: José F. Romaniello + url: http://twitter.com/jfroma + mail: jose@auth0.com + avatar: https://www.gravatar.com/avatar/4d44bd6c8bbd97dfeb5e9c299aaa68c5 +design: + bg_color: "#000C72" + bg_merge: true + image: https://cldup.com/C76gL6oGIE.png + image_size: "155%" +reply: + hn: https://news.ycombinator.com/item?id=9447536 + twitter: https://twitter.com/auth0/status/592765437634555904 +tags: +- announcements +related: +- 2015-09-21-auth0-australia-launches +- 2016-04-21-facebook-account-kit-passwordless-authentication +- 2016-02-03-getting-started-with-auth0-lock-series-implementing-lock +--- + +Today we're happy to announce the launch of the **Auth0 Europe** region. Auth0 makes identity simple for tens of thousands of developers in more than 150 countries around the world, and now those topographically closer to Frankfurt can enjoy even lower latency logins. Further, subscribers everywhere that have the compliance requirement of EU data residence can check that box. + +{% include tweet_quote.html quote_text="Today we're happy to announce the launch of the Auth0 Europe region." %} + + + +![](https://cdn.auth0.com/blog/eu-launch.png) + +## One Auth0, Two Continents +New subscribers now have the ability to specify their preferred region - Europe or the US - by simply selecting a location when creating their free developer account. Existing subscribers who are currently running from the US region who want to switch can just let us know and we'll handle migration. A number of Auth0 subscribers have already migrated to the European region and have reported a quick and easy transition. + +![](https://cdn.auth0.com/blog/multiregion.gif) + +## Faster than ever +Choosing a service region closer to where your customers are operating can mean some pretty awesome improvements in login latency. Subscribers in Europe who are already running from the European region have seen latency improvements of nearly 2x when compared to running from the US region, meaning even faster logins. + +## Migration from US +If you’d like to move from the US region to the EU region, simply let us know and we'll migrate your configuration/data. Once done, it’s just a simple change in your application so that it points to `https://.eu.auth0.com`. + +## Safe Harbor +Auth0 has always and will continue to comply with the U.S.-EU Safe Harbor Framework and the U.S.-Swiss Safe Harbor Framework as set forth by the US Department of Commerce regarding the collection, use and retention of personal information from European Union member countries and Switzerland. Auth0 has certified that it adheres to the Safe Harbor Privacy Principles of notice, choice, onward transfer, security, data integrity, access, and enforcement. To learn more about the Safe Harbor program, and to view our certification page, please visit http://www.export.gov/safeharbor/. + +## We're here to help. +As always, we'd be happy to answer any questions or provide assistance related to the new European region, or anything else. Open a support ticket, chat with us at https://chat.auth0.com or ask questions at ask.auth0.com. + +We look forward to hearing from you! diff --git a/_posts/2015-04-28-announcing-auth0-sso-dashboard.markdown b/_posts/2015-04-28-announcing-auth0-sso-dashboard.markdown new file mode 100644 index 0000000000..cfe08c7216 --- /dev/null +++ b/_posts/2015-04-28-announcing-auth0-sso-dashboard.markdown @@ -0,0 +1,61 @@ +--- +layout: post +title: "Announcing the Auth0 Open Source Single-Sign-On Dashboard" +description: "Remembering usernames and passwords and login urls for all of these apps is a pain. With this app we take another step in simplifying the authentication experience." +date: 2015-04-28 10:00 +alias: /2015/04/28/announcing-auth0-sso-dashboard/ +category: Product +author: + name: Nathan Totten + url: http://twitter.com/ntotten + mail: ntotten@auth0.com + avatar: https://www.gravatar.com/avatar/d48b998c2dce49ca309710eba498c562.png?s=60 +design: + bg_color: "#4a5259" + image: https://cloudup.com/cfENOYpKxfL+ + image_size: "200%" + image_opacity: "0.8" +tags: +- product +related: +- 2015-09-23-what-is-and-how-does-single-sign-on-work +- 2014-08-22-sso-for-legacy-apps-with-auth0-openid-connect-and-apache +- 2015-09-28-5-steps-to-add-modern-authentication-to-legacy-apps-using-jwts +--- + +
    + + Update: The Single-Sign-On Dashboard referred in this article is now deprecated and has been replaced with the extension that can be found here: Auth0 Extension: Single Sign-On (SSO) Dashboard. +
    + +**tl;dr**: Control which apps your users can access with an SSO dashboard. Download the [code](https://github.com/auth0/auth0-sso-dashboard) or [deploy to Heroku](https://dashboard.heroku.com/new?template=https%3A%2F%2Fgithub.com%2Fauth0%2Fauth0-sso-dashboard) in 5 minutes. + +Today, we are excited to announce the release of the Auth0 [Open Source Single-Sign-On Dashboard](https://github.com/auth0/auth0-sso-dashboard). This SSO dashboard is designed to solve a problem familiar to many people. Organizations of all sizes maintain a variety of different applications to handle various business functions like accounting, HR, development, support, etc. Remembering usernames and passwords and login urls for all of these apps is a pain. With this app we take another step in simplifying the authentication experience. + +{% include tweet_quote.html quote_text="Today, we are excited to announce the release of the Auth0 Open Source Single-Sign-On Dashboard." %} + + + +![SSO Dashboard](https://cloudup.com/cfENOYpKxfL+) + +You can find the entire source of this project on [Github](https://github.com/auth0/auth0-sso-dashboard) along with instructions on how to deploy and configure a dashboard for your own organization. Additionally, you can try out a demo version (with non-functional apps) at [https://ssodashboard.herokuapp.com](https://ssodashboard.herokuapp.com). Username: `publicdemo` Password: `TestUser123`. This demo only shows the user functionality; if you would like to see the full admin capabilities, please deploy your own instance. + +A few key features of the SSO Dashboard: + +* Landing page showing all apps that a user is allowed to access. +* Self-service user profile updates. +* Administrator interface for configuring roles, apps, and users. +* Completely customizable UI. + +## Completely Open Source +Our approach to this project, like many projects at Auth0, was to build this as an open source app. This approach gives our users the most flexibility while still being very easy to maintain and receive updates. + +* Node.js Backend (Express) +* React, Flux, and React-Router +* Bootstrap ([Paper Theme](https://bootswatch.com/paper/)) and [Material UI](http://callemall.github.io/material-ui/#/) +* [Babel](https://babeljs.io/) to enable ES6 +* [Webpack](https://webpack.github.io/) for asset bundling + +Feel free to fork and clone this project. We will accept pull requests and [issues](https://github.com/auth0/auth0-sso-dashboard/issues) if you want to contribute back. + +If this is something you have been looking for at your company or organization do give it a try. Let us know what you think, we will be adding features based on feedback. diff --git a/_posts/2015-05-14-creating-your-first-real-world-angular-2-app-from-authentication-to-calling-an-api-and-everything-in-between.markdown b/_posts/2015-05-14-creating-your-first-real-world-angular-2-app-from-authentication-to-calling-an-api-and-everything-in-between.markdown new file mode 100644 index 0000000000..acb0eccb06 --- /dev/null +++ b/_posts/2015-05-14-creating-your-first-real-world-angular-2-app-from-authentication-to-calling-an-api-and-everything-in-between.markdown @@ -0,0 +1,561 @@ +--- +layout: post +title: "Build your Angular 2 App: From Auth to calling an API" +description: "Learn how to create a real world angular 2 app using Pipes, Directives, Components, DI, ES6 and much more! We'll implement from Authentication to calling an API and everything in between" +reply: + twitter: https://twitter.com/auth0/status/598909226111631360 +date: 2015-05-14 09:54AM +updated: 2016-10-26 12.30 +category: Technical Guide, Angular, Angular 2 +alias: /2015/05/14/creating-your-first-real-world-angular-2-app-from-authentication-to-calling-an-api-and-everything-in-between/ +banner: + text: "The Definitive Guide to Single Sign-On" + action: "https://resources.auth0.com/definitive-guide-to-single-sign-on/?utm_source=blog" + cta: "Download eBook" +author: + name: Martin Gontovnikas + url: http://twitter.com/mgonto + mail: gonto@auth0.com + avatar: https://www.gravatar.com/avatar/df6c864847fba9687d962cb80b482764??s=60design +design: + image: https://cdn.auth0.com/blog/angular/logo3.png + bg_color: "#012C6C" +tags: +- authentication +- angular2 +- pipes +- component +- directive +- angular-2 +- di +- bind +- real-world +- example +- talk +related: +- 2016-09-29-angular-2-authentication +- 2017-02-21-reactjs-authentication-tutorial +- 2017-04-18-vuejs2-authentication-tutorial +--- + +
    + + This post is out of date. The Angular framework has undergone many changes since the publication of this article, and the method of authentication utilized in this post is not up-to-date with current best practices. Check out the Real-World Angular Series for a full tutorial on building, authenticating, and deploying a real-world Angular app! +
    + +---- + +**TL;DR:** Get the sample Angular 2 app from [this Github repository](https://github.com/auth0/angular2-authentication-sample). Also, check out [this talk](https://www.youtube.com/watch?v=pgFtp2LgwoE) I did where I explain and live-code this same example. + +---- + +
    + + This article was updated on Oct 26, 2016 to reflect the latest version of Angular 2. +
    + +Last week, the Angular team [announced](https://twitter.com/angularjs/status/593797019258359809) that **Angular 2 was moving from Alpha to Developer Preview**. Therefore, we figured **it was time to give it a try**. + +After looking around the internet, I learned that **all of the existing examples were only one single page** with just 1 or 2 components. Those examples, while nice, weren't really useful for creating a real world app. Therefore, in order to learn and help the community, we decided to **create a fully working, real life small application that would have multiple pages and would handle authentication as well as calling an API**. In order to do all this, we'd use most of the new Angular 2 features like the router, components, directives, pipes and DI, as well as [Fetch](https://fetch.spec.whatwg.org/) for calling an API. In this article, we'll explain how we did it. + + + +## Before we start + +### Warning! + +When we originally wrote this article, Angular 2 was constantly changing. Angular 2 is in a much better place now, so we are updating the content and code samples to reflect the latest version of the framework. + +### Seed project + +In order to start working with Angular 2, I strongly recommend checking [Pawel](https://twitter.com/pkozlowski_os)'s [ng2-play](https://github.com/pkozlowski-opensource/ng2-play). It makes it really easy to install and spin up a new project with Angular 2. The Angular 2 team has also released an official [Angular 2 CLI](https://cli.angular.io/) which can also make project setup a breeze. + +### Read the comments! + +Throughout this example, please **read the comments on the code**, which will give you insights about what each of the lines does. + +### Install angular2-jwt + +We can use [**angular2-jwt**](https://github.com/auth0/angular2-jwt) to make authenticated HTTP requests easily. + +```bash +npm install angular2-jwt +``` + +## Let's code! + +### Our Root Module + +Angular 2 requires us to set up a root module for our application. This is done using the [@NgModule](https://angular.io/docs/ts/latest/guide/ngmodule.html) decorator which allows us to logically group our application into overarching modules. NgModules aims to simplify the way we manage dependencies in our Angular 2 apps. For our application, we'll group our entire application into a single module. + +```js +// Import dependencies +import { BrowserModule } from '@angular/platform-browser'; +import { RouterModule } from '@angular/router'; +import { HttpModule } from '@angular/http'; +import { FormsModule } from '@angular/forms'; +import { NgModule } from '@angular/core'; + +import { AUTH_PROVIDERS } from 'angular2-jwt'; + +import { AuthGuard } from './common/auth.guard'; +import { Home } from './home'; +import { Login } from './login'; +import { Signup } from './signup'; +import { App } from './app'; + +import { routes } from './app.routes'; + +// Declare the NgModule decorator +@NgModule({ + // Define the root component + bootstrap: [App], + // Define other components in our module + declarations: [ + Home, Login, Signup, App + ], + // Define the services imported by our app + imports: [ + HttpModule, BrowserModule, FormsModule, + RouterModule.forRoot(routes, { + useHash: true + }) + ], + providers: [ + AuthGuard, ...AUTH_PROVIDERS + ] +}) +export class AppModule {} +``` + +You can learn much more about Angular 2 modules and how to better manage dependencies with them in this [post](https://auth0.com/blog/angular-2-ngmodules/). + +### Setting Up the Router + +Next we will set up the router. For each URL, our job is to setup which component should be loaded and where. Let's create a `app.routes.ts` file and we'll define our routes there. + +```js +// Import our dependencies +import { Routes } from '@angular/router'; +import { Home } from './home'; +import { Login } from './login'; +import { Signup } from './signup'; +import { AuthGuard } from './common/auth.guard'; + +// Define which component should be loaded based on the current URL +export const routes: Routes = [ + { path: '', component: Login }, + { path: 'login', component: Login }, + { path: 'signup', component: Signup }, + { path: 'home', component: Home, canActivate: [AuthGuard] }, + { path: '**', component: Login }, +]; +``` + +### Building the Root Component + +Our app needs an initial entry point. Let's create an `app.component.ts` file and define the specifics of our root component there. + +``` +import { Component } from '@angular/core'; +import { Router } from '@angular/router'; + +const template = require('./app.html'); + +@Component({ + selector: 'auth-app', + template: template +}) + +export class App { + constructor(public router: Router) {} +} +``` + +You may have noticed that we are also importing an `app.html` file which will be our template. Let's create this file as well and add the following code to it: + +```html +
    + + +
    +``` + +### Bootstrapping our Application + +Finally, we'll need to bootstrap our application to get it running. + +Now we can `bootstrap` the application to get it running. + +```js +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app.module'; + +// The bootstrap process in the final release of Angular 2 requires a module instead of a component +platformBrowserDynamic().bootstrapModule(AppModule); +``` + +We are now ready to start implementing our app specific logic. + +### Restricting Access to Pages + +We don't want anonymous users to be able to access the `Home` route, so we should redirect them if they aren't authenticated. For that, we can create our own `Guard`, which will only let authenticated users access the home route. + +If we look at our routes code above we see that on the `Home` route we pass some additional code `canActivate: [AuthGuard]`. In this section we will implement the code for the `AuthGuard`. Once our app is running, each time the home route is hit, the AuthGuard function will be executed and decide whether or not a user can actually access the route. + +``` +import { Injectable } from '@angular/core'; +import { Router, CanActivate } from '@angular/router'; +import { tokenNotExpired } from 'angular2-jwt'; + + +@Injectable() +export class AuthGuard implements CanActivate { + constructor(private router: Router) {} + + canActivate() { + // Check to see if a user has a valid JWT + if (tokenNotExpired()) { + // If they do, return true and allow the user to load the home component + return true; + } + + // If not, they redirect them to the login page + this.router.navigate(['/login']); + return false; + } +``` + +### Creating the Login page + +Now it's time to create our [Login](https://github.com/auth0/angular2-authentication-sample/blob/master/src/login/login.ts) component. Its main function will be displaying the login form and calling the login API using `Http`. Once the server successfully authenticates the user, we'll save the [JWT](http://jwt.io/) we get back in `localStorage` and then redirect the user to the home page. + +```js +import { Component } from '@angular/core'; +import { Router } from '@angular/router'; +import { Http } from '@angular/http'; +import { contentHeaders } from '../common/headers'; + +const styles = require('./login.css'); +const template = require('./login.html'); + +@Component({ + selector: 'login', + template: template, + styles: [ styles ] +}) +export class Login { + constructor(public router: Router, public http: Http) { + } + + login(event, username, password) { + event.preventDefault(); + let body = JSON.stringify({ username, password }); + this.http.post('http://localhost:3001/sessions/create', body, { headers: contentHeaders }) + .subscribe( + response => { + localStorage.setItem('id_token', response.json().id_token); + this.router.navigate(['home']); + }, + error => { + alert(error.text()); + console.log(error.text()); + } + ); + } + + signup(event) { + event.preventDefault(); + this.router.navigate(['signup']); + } +} +``` + +```html + +``` + +The sign up page is implemented in much the same way. For brevity, we will omit the implementation here, but you can view it on [GitHub](https://github.com/auth0-blog/angular2-authentication-sample/tree/master/src/signup). + +### Creating the Home component + +The user is logged in. It's time to create the [Home](https://github.com/auth0/angular2-authentication-sample/blob/master/src/home/home.ts) component, to which the user will arrive upon successful login. It will let the user call an authenticated API as well as display the JWT information. + +```js +import { Component } from '@angular/core'; +import { Http } from '@angular/http'; +import { Router } from '@angular/router'; +import { AuthHttp } from 'angular2-jwt'; + +const styles = require('./home.css'); +const template = require('./home.html'); + +@Component({ + selector: 'home', + template: template, + styles: [ styles ] +}) +export class Home { + jwt: string; + decodedJwt: string; + response: string; + api: string; + + constructor(public router: Router, public http: Http, public authHttp: AuthHttp) { + this.jwt = localStorage.getItem('id_token'); + this.decodedJwt = this.jwt && window.jwt_decode(this.jwt); + } + + logout() { + localStorage.removeItem('id_token'); + this.router.navigate(['login']); + } + + callAnonymousApi() { + this._callApi('Anonymous', 'http://localhost:3001/api/random-quote'); + } + + callSecuredApi() { + this._callApi('Secured', 'http://localhost:3001/api/protected/random-quote'); + } + + _callApi(type, url) { + this.response = null; + if (type === 'Anonymous') { + // For non-protected routes, just use Http + this.http.get(url) + .subscribe( + response => this.response = response.text(), + error => this.response = error.text() + ); + } + if (type === 'Secured') { + // For protected routes, use AuthHttp + this.authHttp.get(url) + .subscribe( + response => this.response = response.text(), + error => this.response = error.text() + ); + } + } +} +``` + +```html +
    +
    +

    Welcome to the angular2 authentication sample!

    +

    Your JWT is:

    +
    {{ jwt }}
    +
    {{ decodedJwt | json }}
    +

    Click any of the buttons to call an API and get a response

    +

    Call Anonymous API

    +

    Call Secure API

    +

    Logout

    +

    The response of calling the {{ api }} API is:

    +

    {{ response }}

    +
    +
    +``` + +That's it. We should be able to run and test our application now. If you are using the ng-play starter, simply run `npm start` and the app will compile and build. You can access the app on `localhost:3000`. + +In the sample [GitHub repo](https://github.com/auth0-blog/angular2-authentication-sample/tree/master/backend) we also included a simple backend server that accompanies this tutorial. You can run this server by executing the `npm run server` command. + +With both of these running, navigate to your app and try logging in with the credentials `gonto` for both the username and password. If all worked as expected, you will be redirected to the home page and can make calls to the API. + +## Aside: Using Angular 2 with Auth0 + +
    + + This article uses an outdated Auth0 authentication approach. For improved security, it is strongly advised that you refer to the following updated tutorial instead: Angular 2 Authentication Tutorial. +
    + +Auth0 issues **JSON Web Tokens** on every login for your users. That means that you can have a solid identity infrastructure, including [single sign-on](https://resources.auth0.com/definitive-guide-to-single-sign-on/?utm_source=blog), user management, support for social (Facebook, Github, Twitter, etc.), enterprise (Active Directory, LDAP, SAML, etc.) and your own database of users with just a few lines of code. + +We can add Auth0 to the app we just created really easily. There are just a few simple steps: + +### Step 0: Sign Up for Auth0 + +If you don't already have any Auth0 account, sign up for one now to follow along with the other steps. + +### Step 1: Add Auth0Lock to Your App + +[Lock](https://auth0.com/lock) is the beautiful (and totally customizable) login box widget that comes with Auth0. The script for it can be brought in from a CDN link or with npm. + +> Note: If you use npm to get Auth0Lock, you will need to include it in your build step. + +```html + + ... + + + + + + + + ... +``` + +### Step 2: Add an Authentication Service + +It's best to set up an injectable service for authentication that can be used across the application. + +With Auth0, we get access to the user's profile and JWT in the `lock.on('authenticated')` callback and these items can be saved in local storage for use later. Let's create a new file called `auth.service.ts` in our common directory. The implementation will be as follows: + +```js +import { Injectable } from '@angular/core'; +import { tokenNotExpired } from 'angular2-jwt'; +import { Router } from '@angular/router'; + +declare var Auth0Lock: any; + +@Injectable() +export class AuthService { + // Set our Auth0 credentials + lock = new Auth0Lock('YOUR-AUTH0-CLIENT-ID', 'YOUR-AUTH0-DOMAIN.auth0.com'); + + constructor(private router: Router) { + // Capture the user credentials when the user has succesfully logged in + this.lock.on('authenticated', (authResult: any) => { + localStorage.setItem('id_token', authResult.idToken); + + this.lock.getProfile(authResult.idToken, (error: any, profile: any) => { + if (error) { + console.log(error); + } + + localStorage.setItem('profile', JSON.stringify(profile)); + this.router.navigateByUrl('/home'); + }); + + this.lock.hide(); + }); + } + + // Display the lock login box + login() { + this.lock.show(); + } + + // Logout the user + logout() { + // To log out, just remove the token and profile + // from local storage + localStorage.removeItem('profile'); + localStorage.removeItem('id_token'); + + // Send the user back to the dashboard after logout + this.router.navigateByUrl('/login'); + } + + // Check whether the user is logged in or not + loggedIn() { + return tokenNotExpired(); + } +} +``` + +In order to use this service, we'll have to declare it in our NgModule in the `app.module.ts` file. + +```js +// ... existing dependencies +import { AuthService } from './common/auth.service'; + +@NgModule({ + // ... existing declarations + providers: [ + AuthGuard, ...AUTH_PROVIDERS, AuthService + ] +}) +``` + +### Step 3: Add a Click Handler to Login + +We can use the methods from our authentication service in any of our components which means we can easily add a click handler to a "Login" and "Logout" button. Let's edit our login component to make use of Lock for authentication. + +First we'll need to include the `AuthService` in our `Login` component. + +```js +//... existing dependencies +import { AuthService } from '../common/auth.service'; +// ... +export class Login { + // Add the AuthService to our constructor + constructor(public router: Router, public http: Http, private auth: AuthService) { + } + // ... +} +``` + +Next, we'll make the updates to our template. + +```html + +``` + +Now, when a user clicks the Log In link, they will be presented with the Lock UI. Upon a succesful login, they will be redirected to the home page. + +### Step 4: Make Authenticated HTTP Requests + +We can again use `AuthHttp` from [**anuglar2-jwt**](https://github.com/auth0/angular2-jwt) to automatically have our JWTs sent in HTTP requests. You can read more over about [Angular 2 Http examples](https://auth0.com/blog/angular-2-series-part-3-using-http/). + +```js +// src/home/home.ts + +... + + _callApi(type, url) { + this.response = null; + if (type === 'Anonymous') { + // For non-protected routes, just use Http + this.http.get(url) + .subscribe( + response => this.response = response.text(), + error => this.response = error.text() + ); + } + if (type === 'Secured') { + // For protected routes, use AuthHttp + this.authHttp.get(url) + .subscribe( + response => this.response = response.text(), + error => this.response = error.text() + ); + } + } + +... +``` + +### Step 5: Done! + +That's all there is to it to add authentication to your Angular 2 app with Auth0! If you would like to see the completed demo with Auth0 Lock, you can view it [here](https://github.com/auth0-blog/angular2-authentication-sample/tree/auth0-lock). + +## Conclusions + +In this article, we've learned how to create a multiple page Angular 2 app that uses the router, templates, directives and components to implement both authentication and calling an API. You can see the complete example on [Github](https://github.com/auth0/angular2-authentication-sample), as well as a [talk that I did](https://www.youtube.com/watch?v=pgFtp2LgwoE), where this example is live-coded. + +Before ending, I want to thank [David East](https://twitter.com/_davideast) for his support with some questions, [PatrickJS](https://twitter.com/gdi2290) for his help on coding parts of the example and to [Jesus Rodriguez](https://twitter.com/Foxandxss) for cleaning up some of the unused code. + +Happy hacking :). diff --git a/_posts/2015-05-15-design-at-auth0.markdown b/_posts/2015-05-15-design-at-auth0.markdown new file mode 100644 index 0000000000..0434a96fe3 --- /dev/null +++ b/_posts/2015-05-15-design-at-auth0.markdown @@ -0,0 +1,150 @@ +--- +layout: post +title: "Engineering the Design Process at Auth0" +description: "Our lessons in defining a cohesive design language
    across all our products." +date: 2015-06-09 21:24 +draft: false +category: Design +author: + name: Ricardo Rauch + url: https://twitter.com/rickyrauch + mail: ricky@auth0.com + avatar: https://www.gravatar.com/avatar/27396b3fa24389198ef5d3e7e410e9c4?size=60 + +design: + bg_color: "#131313" + image: https://cldup.com/P7V08dAY6M.png + image_bg_color: "transparent" + image_size: "101%" + +alias: /2015/06/08/design-at-auth0/ + +tags: +- business +related: +- 2016-03-22-how-we-hire-engineers +- 2015-06-23-another-big-milestone +- 2016-02-03-getting-started-with-auth0-lock-series-implementing-lock +--- + + + + + +For the past year, Auth0 has been growing at a fast pace and, naturally, so has the number of fronts that the Design Team has had to tackle. + +In this post, we’ll explain what we've learned as we’ve implemented practices that help integrate design into our products, guarantee consistency, and optimize design choices. + + + +## 1. Blueprints + +The first practice we introduced was design from blueprints. A blueprint is just a monochrome draft of the final product, with as little focus on styling as possible. + + + +We go through a lot of feedback from different company stakeholders, including engineers, marketers, analytics, and our own clients. By taking subjective factors like color and embellishment out of the process, blueprints help us get better feedback and achieve simpler solutions in a shorter length of time. + +## 2. Frontend + +### Asset production + +We constantly review previous work when producing new assets to check for consistency and understand how they have evolved over time so we don’t repeat mistakes. + +Our website is mapped out on a 1:1 scale with design files and is available to the whole team on Dropbox. + + + +Using Sketch has dramatically improved the way we create and export assets. We think it’s the best tool for the job because it was designed with the Web in mind. + +### Mobile first + +Most of the HTML and CSS at Auth0 it handled or refactored by a designer at some point. We’ve set [clear guidelines](https://github.com/auth0/code-conventions/blob/master/frontend/README.md) for front-end code, and we’ve chosen [Stylus](https://learnboost.github.io/stylus/) and [Jade](http://jade-lang.com/) to optimize our productivity. As a result, we write concise, clear code much faster. + +Over the past year, we have started to migrate all of our pages as part of a mobile-first strategy. + +The rationale behind this strategy starts with the design perspective: we set out to present only information that we can make available in a way that works on any device. This simplifies our approach to information architecture by getting rid of unnecessary elements and solving problems in the simplest way, which also reduces the potential for errors during development. + + + +To maintain stability, we are making these changes progressively. Each page that gets a redesign gets a mobile-first refactor to go with it. + +A key factor in preventing Responsive Design from getting messy was abstracting all of our media queries to a [Stylus mixin](https://github.com/auth0/styleguide/blob/master/lib/mixins/index.styl#L11) and keeping them as close as possible to their relevant selectors, making them harder to overlook and easier to maintain. + +```css +.hero-cta + color: blue; + border-radius: 3px; + padding: 10px 20px; + max-width: 100%; + + +breakpoint("tablet") + max-width: 620px; + font-size: 18px; + + +breakpoint("desktop") + max-width: 740px; + font-size: 20px; +``` +### Styleguide for consistency + +Styleguide is one of our most important projects. Its aim is to help maintain the same look and feel across all of our products and to make our front-end code reusable, no matter the specifics of any project's codebase. + + + +Styleguide holds values, patterns, and specific components that repeat across pages, enabling designers and engineers quickly to reuse them on any product without worrying about markup or CSS. + + + +- Elements like our [header](https://github.com/auth0/web-header), footer, and other components are easily maintained on different projects by sourcing them directly from [Styleguide](https://styleguide.auth0.com). + +- By reducing complex html structures and patterns to Jade mixins available through [Styleguide](https://styleguide.auth0.com) and passing only content as parameters, we optimize development time. + + + +- Company [colors](https://styleguide.auth0.com/#colors), [typography](https://styleguide.auth0.com/#typography), and [icons](https://styleguide.auth0.com/#icons) are kept consistent by using the same set of variables and files for every project. + +- We use [semantic versioning](http://semver.org/) to enforce certain versions of Styleguide across projects. This helps maintain stability in our sites when we're doing heavy updates. + +Today, we can include Styleguide using [Bower](http://bower.io/) or [Component](http://component.github.io/) or by linking [directly from the CDN](https://cdn.auth0.com/styleguide/latest/index.css). + +If you are interested, [check the live version](https://styleguide.auth0.com) or [view the code on Github](https://github.com/auth0/styleguide). + +## 3. Publish + +### Deep testing + +Once we reach the final step in producing a deliverable, we start a rough path of tests by using Heroku or Github pages as staging deployments. The whole point of this task is to have a last ride before publishing to the open when there is no take-back of the first impression statement. + +Some of the things we review here: + +Mobile rendering and response times on different real devices, browsers, and networks’ SEO optimizations (title on each page, description, sitemap…) and social sharing media elements like OpenGraph and Twitter Cards. + +This method has always preceded the finest look and feel, in our experience. + + + +And we’ll keep improving our test process to deliver no less than the best. + +## Design meetings + +Though we’re still a small group, there are already different kinds of designers on the team, some more adept in usability or visual design and some more focused on prototyping and code. + +

    Design Meeting @auth0

    A photo posted by Ricardo Rauch (@rickyrauch) on

    + + +We have established weekly design meetings as a simple “show and tell” exercise to feed off each other's work and be in the loop of what’s been going on during the week with other projects. This adds a big-picture perspective to all the work that's been done and complements daily feedback by showing the whole team the impact of design on our products. + + + +We’re always looking for ways to improve and optimize our process. We hope that some of our practices can prove useful for anyone interested in collaborating on product design, and if you have suggestions or thoughts, we’d be glad to hear them. + + + +Finally, processes are nothing without a great team. I am humbled to work every day with these amazing people: [Victor Fernandez](http://twitter.com/vctrfrnndz), [Benjamin Flores](https://twitter.com/beneliflo_), and [Nicolás Garro](https://twitter.com/evilrabbit_) + + + +If this sparked some interest, [we are hiring](https://auth0.com/jobs). diff --git a/_posts/2015-06-23-another-big-milestone.markdown b/_posts/2015-06-23-another-big-milestone.markdown new file mode 100644 index 0000000000..90a5ea241e --- /dev/null +++ b/_posts/2015-06-23-another-big-milestone.markdown @@ -0,0 +1,48 @@ +--- +layout: post +title: "Another big milestone: $9.3M in funding and 24,000 users" +description: "Auth0 has raised an additional $6.875 million in Series A funding, led once again by Bessemer Venture Partners and K9 Ventures bringing our funding total to $9.3 million" +date: 2015-06-24 06:00 +alias: /2015/06/23/another-big-milestone/ +category: Announcement +author: + name: "Jon Gelsey" + mail: jon@auth0.com + url: http://www.twitter.com/jgelsey + avatar: https://auth0.com/pages/about/img/jon.png + twitter: jgelsey +design: + image: https://cdn.auth0.com/blog/series-a/blog-logo-4.png + image_bg_color: "transparent" + bg_color: "#111118" + +tags: +- series-A +- milestone +- announcement +- capital +- round +related: +- 2014-09-17-big-milestone +- 2014-04-28-announcing-our-advisors +- 2016-03-22-how-we-hire-engineers +--- +Only nine short months after our [previous funding announcement](https://auth0.com/blog/2014/09/17/big-milestone/), we are proud to announce that Auth0 has raised an additional $6.875 million in Series A funding, led once again by [Bessemer Venture Partners](http://www.bvp.com/) and [K9 Ventures](http://www.k9ventures.com/) bringing our funding total to $9.3 million. + +We’re also proud to announce that our subscriber base has grown to over 24,000 developers at over 20,000 enterprises across more than 150 countries around the world. We’re humbled by the rapid growth that we’ve experienced -- the Series A funding will even further enhance our ability to provide developers and IT staff with strong identity security across their enterprises. + + +{% include tweet_quote.html quote_text="We are proud to announce that our subscriber base has grown to over 24,000 developers." %} + + + +Auth0 was founded to simplify identity implementation and management for developers everywhere. Historically, implementing identity for anything more than the most trivial scenario had always been a massive pain. The friction of traditional identity implementation has often meant that developers are forced to trade strong identity security for on-time product launches. With Auth0, developers and IT staff can now quickly secure modern mobile and web applications, APIs and IoT devices by adding just a few lines of code, regardless of the complexity of the identity environment. Our goal is to make strong identity security easy to implement with no compromises. + +
    +

    “It’s now painfully obvious that insecure or incomplete authentication implementations are creating vulnerabilities, jeopardizing customer trust, and hindering enterprise adoption of cloud and mobile services. So Bessemer is thrilled to partner with the best identity team in the world to deliver secure login as a service — as easy to use as Twilio, SendGrid and Stripe.”

    + David Cowan, VeriSign co-founder and partner at BVP. +
    + + + +We look forward to continuing to build and support simple-to-implement, strong identity security for our subscribers. You can read the [full funding announcement press release](http://www.businesswire.com/news/home/20150624005699/en/Auth0-Raises-Series-Led-Bessemer-Pain-Identity#.VYqrSFxVikp) and stay up to date on our progress anytime by following us [@auth0](https://twitter.com/auth0). diff --git a/_posts/2015-06-24-auth0-raises-series-a-led-by-bessemer.markdown b/_posts/2015-06-24-auth0-raises-series-a-led-by-bessemer.markdown new file mode 100644 index 0000000000..1459170525 --- /dev/null +++ b/_posts/2015-06-24-auth0-raises-series-a-led-by-bessemer.markdown @@ -0,0 +1,49 @@ +--- +layout: post +title: "Auth0 Raises Series A Led By Bessemer to Take the Pain Out of Identity Security" +description: The Identity-as-a-Service platform raises $9.3M to simplify secure identity implementation and management for developers everywhere. +date: 2015-06-24 19:12 +category: Announcements, Press +author: + name: Martin Gontovnikas + url: http://twitter.com/mgonto + mail: gonto@auth0.com + avatar: https://www.gravatar.com/avatar/df6c864847fba9687d962cb80b482764??s=60 +design: + bg_color: "#2A6DBA" + image: https://cdn.auth0.com/blog/seattle-top10-2016/logo.png +press_release: true +tags: +- auth0 +- founding +- series-A +related: +- 2015-09-30-auth0-introduces-passwordless-authentication +- 2016-08-24-auth0-raises-15M-to-enhance-its-identity-platform +- 2015-08-25-auth0-adds-saas-luminaries-to-its-board-of-directors +--- + +**BELLEVUE, Wash.**--(BUSINESS WIRE)--[**Auth0**](https://auth0.com), the leading universal identity platform, today announced it has raised a total of $9.3M in funding led by Bessemer Venture Partners, with participation by K9 Ventures and others. The company will use the funds to support the high subscriber growth of its identity-as-a-service platform for developers, specifically focusing on expanding its marketing and customer success staff and developing additional product features to make strong identity security even easier for developers to implement and manage. + +Auth0 is on a mission to simplify enterprise, social and custom identity management for developers everywhere. Vastly different from traditional authorization and authentication solutions, Auth0’s cloud platform makes strong authentication and authorization simple to implement for developers and IT administrators by augmenting and extending existing or new identity environments. With Auth0, developers no longer need deep vertical experience across authorization, authentication, identity management and security. Instead they can quickly leverage the world-class security and identity expertise of the Auth0 cloud service to secure modern mobile and web applications and APIs by simply adding a few lines of code, regardless of the complexity of the identity environment. + +*“Implementing identity for anything more than the most trivial scenario has historically been a massive pain,”* said CEO Jon Gelsey. *“That pain translates both into weaker security and delays in shipping new applications, APIs or IoT devices. The friction of traditional identity implementation has often meant that developers are forced to trade strong identity security for on-time product launches. Auth0 makes strong identity security easy to implement without making any sacrifices.”* + +While developers love Auth0 because it’s the fastest and simplest way to implement identity even in the most complex identity environments, their enterprises love Auth0 because it ensures their users always benefit from the latest state-of-the-art security features. This includes a sophisticated, configurable, and contextual [multifactor authentication](https://auth0.com/learn/multifactor-authentication/) capability as well as numerous additional security policies that developers can implement with a click. + +*“It’s now painfully obvious that insecure or incomplete authentication implementations are creating vulnerabilities, jeopardizing customer trust, and hindering enterprise adoption of cloud and mobile services,”* observed VeriSign co-founder David Cowan, a partner at BVP. *“So Bessemer is thrilled to partner with the best identity team in the world to deliver secure login as a service — as easy to use as Twilio, SendGrid and Stripe.”* As part of the Series A round, Sunil Nagaraj of Bessemer Venture Partners will join the board of Auth0. + +Auth0’s Series A funding of $6.875M, added to the $2.4M raised in a convertible seed round last September, brings its total raised to over $9M. The company plans to invest in marketing, customer success, and new product features to further extend and support its rapidly growing customer base. Auth0’s 20,000+ subscribers include world-class enterprises like *Schneider Electric*, *JetPrivilege*, and *Mindjet*. Auth0 subscribers are located in more than 150 countries worldwide. The Series A funding will enhance Auth0’s capability to deliver a platform that meets developers’ evolving needs. + +## About Auth0 +Auth0 provides frictionless authentication and authorization for developers. The company makes it easy for developers to implement even the most complex identity solutions for their web, mobile, and internal applications. Ultimately, **Auth0** allows developers to control how a person’s identity is used with the goal of making the internet safer. As of August, 2016, Auth0 has raised over $24m from *Trinity Ventures*, *Bessemer Venture Partners*, *K9 Ventures*, *Silicon Valley Bank*, *Founders Co-Op*, *Portland Seed Fund* and *NXTP Labs*, and the company is further financially backed with a credit line from *Silicon Valley Bank*. + +For more information visit [https://auth0.com](https://auth0.com) or follow [@auth0](https://twitter.com/auth0) on Twitter. + +## About Bessemer Venture Partners + +**Bessemer Venture Partners (BVP)** invests in early-stage and hyper-growth startups, partnering closely with entrepreneurs to build durable businesses. BVP is a global firm with offices in Menlo Park, San Francisco, Cambridge, New York, Bangalore and Herzliya. With $4 billion under management, BVP invests anywhere from $100,000 to $75 million in innovative companies like *Pinterest*, *Twilio*, *Box*, *LinkedIn*, *Shopify*, *Yelp*, *Skype* and *CornerstoneOnDemand**. Over 100 BVP companies have gone public – in the last three years alone, BVP has had 46 exits (IPO and M&A) and made new investments in more than 85 companies. Learn more at [www.bvp.com](http://www.bvp.com) and follow us on Twitter [@BessemerVP](https://twitter.com/BessemerVP). + +## About K9 Ventures + +**K9 Ventures** is a technology focused micro-VC fund led by Dr. Manu Kumar. K9 is based in Palo Alto, California and invests in teams of technical founders in Silicon Valley, who are creating new technology or opening new markets, with a direct revenue model. K9 primarily invests at the pre-seed or seed stage and has invested in several companies including *Lyft*, *Twilio*, *CardMunch*, *Occipital*, *NimbleVR*, *eShares*, *Coin*, *Osmo*, and more. K9 Ventures can be found on the web at [k9ventures.com](http://k9ventures.com) or on Twitter at [@K9Ventures](https://twitter.com/K9Ventures). \ No newline at end of file diff --git a/_posts/2015-06-26-auth0-ember-simple-auth.markdown b/_posts/2015-06-26-auth0-ember-simple-auth.markdown new file mode 100644 index 0000000000..f0e34fafcf --- /dev/null +++ b/_posts/2015-06-26-auth0-ember-simple-auth.markdown @@ -0,0 +1,95 @@ +--- +layout: post +title: "Ember + Auth0" +description: "Introducing an official 'ember simple auth' add-on" +date: 2015-07-23 06:00 +alias: /2015/06/26/auth0-ember-simple-auth/ +category: Technical Guide, Frontend, Ember +author: + name: "Ben Schwarz" + mail: ben@auth0.com + url: https://twitter.com/benschwarz + avatar: https://www.gravatar.com/avatar/42e2ec6a72627f8c15115e279a5f7d8e.png?size=80 + twitter: benschwarz +design: + image: https://cdn.auth0.com/blog/ember-simple-auth/ember-logo-small.png + image_bg_color: "transparent" + bg_color: "#412b13" + +tags: +- ember +- authentication +- plugin +- library +- open-source + +related: +- 2015-08-11-create-your-first-ember-2-dot-0-app-from-authentication-to-calling-an-api +- 2016-04-15-angularjs-authentication-screencast-series-part-1 +- 2016-04-21-facebook-account-kit-passwordless-authentication +--- +__TLDR;__ Install the add-on using ember-cli `ember install auth0-ember-simple-auth`, or checkout the generated [example application](https://github.com/auth0/auth0-ember-simple-auth/tree/master/examples/simple) to get started. + +------- + +At Auth0, we’re huge fans of [Ember](http://emberjs.com), the Javascript framework made famous for writing ambitious single page applications (also known as SPA). + +Unfortunately, up until now our support and resources for Ember have not equalled our enthusiasm for it. Thankfully, starting today, we have some great news — + +![Auth0 + Ember logos](https://cdn.auth0.com/blog/ember-simple-auth/auth0-ember.png) + +# Introducing an official 'ember simple auth' add-on + +For those familiar with Ember, it might go without saying that “[ember-simple-auth](http://ember-simple-auth.com)” is the most popular authentication helper library around. It implements a strategy based plugin system that allows users to write their own custom authentication strategies. + +That is exactly what one of our customers, [Aram Zadikian](https://github.com/brancusi) did, and thanks to his generosity, we’re able to share his fantastic work with you today! + +We'll be maintaining `auth0-ember-simple-auth` on an ongoing basis, so you can rely on it to build your ember applications. + +## What does it do? + +`auth0-ember-simple-auth` is an Ember-cli add-on that uses [Lock](https://auth0.com/lock), and, after some simple configuration will allow you to sign up/in and out — protecting whichever pages you've specified in your router. + +``` +// app/routes/application.js + +import Ember from 'ember'; +import ApplicationRouteMixin from 'simple-auth/mixins/application-route-mixin'; + +export default Ember.Route.extend(ApplicationRouteMixin, { + actions: { + sessionRequiresAuthentication: function(){ + // Check out the docs for all the options: + // https://auth0.com/docs/libraries/lock/customization + + // These options will request a refresh token and launch lock.js in popup mode + var lockOptions = {authParams:{scope: 'openid'}}; + + // This tells simple-lock to use our `auth0-ember-simple-auth` add-on + this.get('session').authenticate('simple-auth-authenticator:lock', lockOptions); + } + } +}); + +``` + +Now that you have enabled your simple-auth strategy, its just a matter of importing the `AuthenticatedRouteMixin` from `ember-simple-auth` to lock down routes that need to be protected. + +``` +// app/routes/my-protected-route.js + +import Ember from 'ember'; +import AuthenticatedRouteMixin from 'simple-auth/mixins/authenticated-route-mixin'; + +export default Ember.Route.extend(AuthenticatedRouteMixin); +``` + +Now you've already got a secure single page application. Time to focus on writing your app! + +![A sample application in use — Running through an authentication flow](https://cdn.auth0.com/blog/ember-simple-auth/ember-simple-auth.gif) + +You can install & run the `auth0-ember-simple-lock` add-on, or, to fast forward to a fully working ember application (shown above), you can download the seed project from [your application dashboard](https://manage.auth0.com/#/applications). + +We can't wait to see what you do with ember, make sure you [reach out on Twitter](https://twitter.com/auth0) with what you create. + +✌️ Happy hacking! diff --git a/_posts/2015-06-29-using-smartlock-on-android-in-3-simple-steps.markdown b/_posts/2015-06-29-using-smartlock-on-android-in-3-simple-steps.markdown new file mode 100644 index 0000000000..c862b572a8 --- /dev/null +++ b/_posts/2015-06-29-using-smartlock-on-android-in-3-simple-steps.markdown @@ -0,0 +1,198 @@ +--- +layout: post +title: "Using Smart Lock on Android in 3 simple steps" +description: In this tutorial, learn how to use Google Smart Lock, a service that makes handling user's credentials on Android a pain-free process. +date: 2015-06-29 22:16 +alias: /2015/06/29/using-smartlock-on-android-in-3-simple-steps/ +category: Technical Guide, Mobile, Android +author: + name: Hernan Zalazar + url: "https://twitter.com/hzalaz" + mail: "hernan@auth0.com" + avatar: "https://www.gravatar.com/avatar/b93af62499ed0f76f280acb37913f15d.png?size=200" +design: + bg_color: + image: https://cdn.auth0.com/blog/smart-lock/smart-lock.png +tags: +- android +- smart-lock +- password-manager +- 1password +- lastpass +- auth0 +- lock +- github +- sample +- tutorial +related: +- 2016-02-08-how-to-authenticate-on-android-using-social-logins +- 2016-04-21-facebook-account-kit-passwordless-authentication +- 2016-02-18-ionic-2-authentication-how-to-secure-your-mobile-app-with-jwt +--- + +---- + +**TL;DR:** Get a sample Android app with Smart Lock configured and ready to use from [this Github repository](https://github.com/auth0/Lock-SmartLock/tree/master/app). + +---- + + +Members of our team participated in the last Google I/O to learn about the latest trends. There were a lot of cool new things announced, but these two particularly caught our eyes: **Android Fingerprint Support** & **Smart Lock for Passwords**. In this blog post, we’ll focus on the latter. + +**Smart Lock for Passwords** is a service that makes handling your user’s credentials a pain-free process. It also allow friction-free log-in and sign-up for your web & Android apps. In other words, the idea is that you never have to remember your passwords again :). Have you heard about [1Password](https://agilebits.com/onepassword) or [LastPass](https://lastpass.com/)? Well, imagine that, but from Google. + +If you’re writing a WebApp, password management can be handled by Chrome itself. However, for Android apps. you need to do some extra work to add it. After playing around with the samples, we decided that it would be great to integrate it with our **Lock for Android** to make it even easier to authenticate your users and keep their credentials safe. With that in mind, we created a new library [Lock-SmartLock](https://github.com/auth0/Lock-SmartLock). In this blog post, we’ll learn how to use it! + + + +## Before we start + +First, you need to make sure you have updated *Google Play services* in your Android SDK installation. (You can update it using Android SDK Manager) + +Then, follow [this guide](https://developers.google.com/identity/smartlock-passwords/android/get-started#configure_your_console_name_project) to configure your [Google Developers Console](https://console.developers.google.com/) project and associate it with your Android application so it can use SmartLock API. + +> In order to use **Lock-SmartLock**, you’ll need to create an Auth0 application. You can create one from [the Auth0 dashboard](https://manage.auth0.com/#/applications) + +## Let’s add Lock & SmartLock + +### Configuring Lock-Smartlock + +First, add **Lock-SmartLock** to your `build.gradle` file. + +```gradle +compile ‘com.auth0.android:appcompat-v7:22.2.0’ +``` + +Then, in your `res/values` folder, create a resource file named `auth0.xml` that will include your Auth0 credentials: + +```xml + + + {YOUR_CLIENT_ID} + {YOUR_DOMAIN} + a0{YOUR_CLIENT_ID_LOWERCASE} + +``` + +In order to authenticate the user, **Lock** needs access to the Internet. Let’s request that permission in your `AndroidManifest.xml`: + +```xml + +``` + +### Implementing Lock-Smartlock +Now it’s time to register **Lock for Android** and **Google Play services** for your application in your `AndroidManifest.xml`: + +```xml + + + + + + + + + + + + + + +``` + +Then, we need to create a custom class that extends from `android.app.Application` and implements `LockProvider`. In this class, we’ll create an instance of `SmartLock` and store it so that we can use it from any `Activity`. + +```java +public class MyApplication extends Application implements LockProvider { + + private Lock lock; + + @Override + public void onCreate() { + super.onCreate(); + lock = new SmartLock.Builder(this) + .loadFromApplication(this) + .build(); + } + + @Override + public Lock getLock() { + return lock; + } + +} +``` + +> Remember to register this class as your application class in the `AndroidManfifest.xml`, too. + +Then, in the Activity that will trigger authentication, override the following lifecycle callbacks that will allow **Lock** to interact with **Smart Lock**: + +```java +@Override +protected void onStart() { + super.onStart(); + SmartLock.getSmartLock(this).onStart(); +} + +@Override +protected void onStop() { + super.onStop(); + SmartLock.getSmartLock(this).onStop(); +} + +@Override +protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + SmartLock.getSmartLock(this).onActivityResult(this, requestCode, resultCode, data); +} +``` + +> The layout of the Activity is up to you; we only need a way to trigger the authentication (either a button or automatically from code). + +Then, we need to create a `BroadcastReceiver` to handle the events broadcasted by **Lock** when the user authenticates: + +```java +@Override +protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + //... Setup your Activity + broadcastManager = LocalBroadcastManager.getInstance(this); + broadcastManager.registerReceiver(authenticationReceiver, new IntentFilter(Lock.AUTHENTICATION_ACTION)); +} + +@Override +protected void onDestroy() { + super.onDestroy(); + //... Clean up your Activity + broadcastManager.unregisterReceiver(authenticationReceiver); +} + +private BroadcastReceiver authenticationReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + UserProfile profile = intent.getParcelableExtra(Lock.AUTHENTICATION_ACTION_PROFILE_PARAMETER); + Token token = intent.getParcelableExtra(Lock.AUTHENTICATION_ACTION_TOKEN_PARAMETER); + } +}; +``` + +Finally, in order to authenticate the user, you just call the following method method inside your Activity + +```java +SmartLock.getSmartLock(this).loginFromActivity(this); +``` + +Connect your device, and run the app. You’ll see something like this: + +![smartlock-android](https://cdn.auth0.com//blog/smartlock-android.gif) + +That’s it all, you have successfully integrated **Auth0** and **SmartLock**! + +Congrats ;) + + diff --git a/_posts/2015-07-21-jwt-json-webtoken-logo.markdown b/_posts/2015-07-21-jwt-json-webtoken-logo.markdown new file mode 100644 index 0000000000..ea6ffd0007 --- /dev/null +++ b/_posts/2015-07-21-jwt-json-webtoken-logo.markdown @@ -0,0 +1,118 @@ +--- +layout: post +title: "JWT: 2 years later" +description: Json Web Tokens (JWT) gets a logo, new website and more +date: 2015-07-21 13:00 +alias: /2015/07/21/jwt-json-webtoken-logo/ +category: Announcement +author: + name: Matias Woloski + url: "https://twitter.com/woloski" + mail: "matias@auth0.com" + avatar: "https://secure.gravatar.com/avatar/0cd73f2f2f39709bd03646e9225cc3d3?s=200" +design: + image: //cdn.auth0.com/blog/jwt/logo-400.png + image_bg_color: "#000" + bg_color: "#000" + image_size: "170%" + image_fb: http://jwt.io/img/facebook-card.png + image_tw: http://jwt.io/img/twitter-card.png +tags: +- jwt +- openid-connect +- logo +related: +- 2015-09-28-5-steps-to-add-modern-authentication-to-legacy-apps-using-jwts +- 2014-02-26-openid-connect-final-spec-10 +- 2014-08-22-sso-for-legacy-apps-with-auth0-openid-connect-and-apache +--- + +In 2013 when we started building Auth0 we had to decide which standard we wanted our platform to be built on. Back then there was [WS-Fed](https://en.wikipedia.org/wiki/WS-Federation) and [SAML2](http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf). I was very familiar with both, having worked on many projects with Microsoft technologies. My main issue with these standards was that they were too complex: too many knobs and levers, interop issues and above all, a lack of libraries. + + + +On the other extreme we had OAuth2 which was rapidly being adopted because of its simplicity. It was also being used by Facebook, Google and many others. There were already libraries written in many languages, making it even more appealing. + +However, [OAuth2](https://tools.ietf.org/html/rfc6749) was/is an __authorization__ protocol, not one specific for __authentication__ [[1](http://www.thread-safe.com/2012/01/problem-with-oauth-for-authentication.html)] [[2](http://homakov.blogspot.com.ar/2012/08/oauth2-one-accesstoken-to-rule-them-all.html)]. + +[OpenID Connect](http://openid.net/specs/openid-connect-core-1_0.html) was being drafted as a very thin layer on top of OAuth2 to overcome exactly that issue. With the introduction of [JSON Web Tokens](https://tools.ietf.org/html/rfc7519) there was now a simple way of verifying user identity and audience (the consumer of these tokens). + +Back then it was on [draft-06](https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-06), and I decided to join the Working Group. + +## Why is JWT so popular? + +![](https://cdn.auth0.com/blog/jwtc/jwt-google-trend.png) + +I think there are many reasons why JWT is being widely adopted: + +* It embraces JSON which is already heavily adopted across many stacks. +* It is simple to use and simple to implement (hence more libraries and fewer interop issues). +* It supports symmetric and asymmetric crypto which solves the majority of use cases. + +## Numbers speak for themselves + +About 2 years since the first draft this simple, yet useful standard expanded: + +![](https://cdn.auth0.com/blog/jwtc/banner-jwt.png) + +* [972 GitHub repos](https://github.com/search?q=jwt) related to JWT. +* [2600+ StackOverflow](http://stackoverflow.com/search?q=jwt) threads. +* 400K page views on [jwt.io](http://jwt.io). +* [50K Google](https://www.google.com.ar/search?q="json+web+token") results. + +If you use Android, AWS, Microsoft Azure, Salesforce, or Google then chances are that you are already using JWT. + +{% include tweet_quote.html quote_text="If you use AWS, Microsoft Azure, Salesforce, or Google then chances are that you are already using JWT." %} + +We very much believe in this standard so we wanted to keep making contributions to foster its adoption. We are happy to share the **new logo**, the **new website**, **badges**, and other things. :) + + +## New Logo + +The central component of the branding is a logo symbol representing an individual JSON Web Token. The circular icon was designed to suggest a coin while avoiding being mistaken for a digital currency symbol. + +Here are some initial sketches and colors: + +![](https://cdn.auth0.com/blog/jwtc/jwt_02.jpg) +![](https://cdn.auth0.com/blog/jwtc/jwt_04.jpg) + +We decided to use the starburst shape representing the crypto protection of a JSON Web Token. Multicolored spokes radiate from the centralized hub representing the various claims within a payload. +Here is the final symbol: + +![](https://cdn.auth0.com/blog/jwtc/jwt_01.jpg) + +JSON Web Token has been abbreviated to the initials JWT and custom lettering was developed. + +![](https://cdn.auth0.com/blog/jwtc/jwt_03.jpg) +![](https://cdn.auth0.com/blog/jwtc/jwt_05.jpg) + +## New Site + +We redesigned [jwt.io](http://jwt.io), incorporating the new branding. The debugger is still the central piece. We added support for RS256 in addition to HS256. + + + +In the libraries section, we improved the readability by color coding each library and using the proper logos for each. In addition to that, we added the number of stars from the GitHub repository. + + + +## Badges and Others + +If your API supports JSON Web Tokens, feel free to add this badge + +![](https://cdn.auth0.com/badges/jwt-compatible.svg) + +If there is some functionality on your site that uses and exposes JSON Web Tokens, you can use the following button to open the JWT on jwt.io. + +[![](https://cdn.auth0.com/badges/jwt-view.svg)](http://jwt.io/#id_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ) + +The token is sent through the hash like this: `http://jwt.io/#id_token=eyJhbGciOi....` + +Finally, we designed some cool JWT t-shirts that you can order from [swag.auth0.com](http://swag.auth0.com). + + +## Acknowledgements + +Special thanks to [Ty Wilkins](http://tywilkins.com/) for crafting the logo and lettering, [Ricky Rauch](http://twitter.com/rickyrauch) and [team](http://dribbble.com/auth0) for the awesome looking website, [Alberto Pose](http://twitter.com/thepose) who created the initial jwt.io version and curates the community contributions, [Guillermo Rauch](http://twitter.com/rauchg) for his constant advice, [Mike Jones](http://self-issued.info) for introducing me into the working group and all of you who [contributed to jwt.io](https://github.com/jsonwebtoken/jsonwebtoken.github.io/graphs/contributors) through GitHub. + +**JWT all the things!** diff --git a/_posts/2015-07-28-if-this-then-node-dot-js-extending-ifttt-with-webtask-dot-io.markdown b/_posts/2015-07-28-if-this-then-node-dot-js-extending-ifttt-with-webtask-dot-io.markdown new file mode 100644 index 0000000000..a0cd7a0d4b --- /dev/null +++ b/_posts/2015-07-28-if-this-then-node-dot-js-extending-ifttt-with-webtask-dot-io.markdown @@ -0,0 +1,189 @@ +--- +layout: post +title: "If This Then Node.js" +description: In this article we'll build a simple IFTTT channel to log words used in the headers and bylines of articles we save with Pocket. +date: 2015-07-28 15:39 +alias: /2015/07/28/if-this-then-node-dot-js-extending-ifttt-with-webtask-dot-io/ +category: Technical Guide, Serverless, Webtask +author: + name: Milo Mordaunt + url: https://twitter.com/bananaoomarang + mail: milo@auth0.com + avatar: https://en.gravatar.com/userimage/24343392/119d3a13b6da0d9eabbcde831f163c4b.jpeg +design: + bg_color: "#000" + image: https://cdn.auth0.com/blog/ifttt-tutorial/ifttt-logo.png + image_fb: https://cdn.auth0.com/blog/ifttt-tutorial/facebook-card.png + image_tw: https://cdn.auth0.com/blog/ifttt-tutorial/ifttt-webtask-card-1.0.png +tags: +- webtask +- ifttt +- pocket +- tutorial +related: +- 2105-10-27-extensibility-through-code-using-webtasks +- 2015-12-11-get-your-twitter-share-count-back-with-a-webtask +- 2016-02-03-getting-started-with-auth0-lock-series-implementing-lock +--- + + + +If you've ever used [IFTTT](https://ifttt.com) as a developer and thought something like: "Gee, I wish I could write my own scripts as channels", you may just be in luck. When combined with their new ['Maker' Channel](https://ifttt.com/channels/maker), enabling users to call external REST APIs, [webtasks](https://webtask.io) can be used to run arbitrary code, on request. + +In this article we'll build a simple IFTTT channel to log words used in the headers and bylines of articles we save with [Pocket](https://getpocket.com/), a 'read it later service' which bookmarks interesting articles for later, so they are sortable by frequency. + +![IFTTT](https://docs.google.com/drawings/d/17bLUFY_iGN6T7V_szV4w6qv286PGHyT4wIwISQGRuw8/pub?w=818&h=210) + +## What's a webtask? + +```js +module.exports = function (done) { + done(null, 'Hello, webtasks!'); +} +``` + +A webtask is a snippet of code that can be called on a simple HTTP request, either directly in a browser or indeed anywhere else. The above is a very simple example but, as we will see in a moment, they can be extended as much as you wish. Their major benefits include: + ++ Ease of use. No complicated setup, just code. ++ Vastly simplifies/eliminates the need for backend code, boiling it down into reusable, functional pieces. ++ Tamper proof (uses JSON webtokens behind the scenes), and encrypted where they need to be. + +You can play with the service and read more about it [here](https://webtask.io/), but what it amounts to is a safe and frictionless way to run custom microservices. Perfect for extending IFTTT. + +## Setup + +Firstly we need to install the command line application, to make task management easier. To set it up all we need is: + +``` +$ npm i -g wt-cli +$ wt init +``` + +To test if it's working after the setup, write to the file `hello-webtasks.js` with either the following, or something to that effect: + +```js +module.exports = function (done) { + done(null, 'Hello, webtasks!'); +} +``` + +The only requirement is that you supply an entry function to be run on webtask.io's servers, here we just send back a simple message. Just run: + +{% include tweet_quote.html quote_text="The only requirement is that you supply an entry function to be run on webtask.io's servers." %} + +`$ wt create hello-webtasks.js` + +And you should be given a URL. Visit it in you're browser or console of choice and you can see the message is returned. It's pretty neat, right? + +![Cool beans.](https://cdn.auth0.com/blog/ifttt-tutorial/hello-webtasks-directors-cut-1.0.gif "Hello There!") + +Even neater is the ability to add some context to the request through a query string or request body, and access it like so: + +```js +module.exports = function (ctx, done) { + done(null, 'Hello, ' + ctx.data.name); +} +``` + +Generate a new URL with `wt create`, but this time when you visit it add `&name=` to the end of the address, and you're webtask will greet you! + +``` +$ curl https://webtask.it.auth0.com/api/run//hello-world?webtask_no_cache=1&name=milo +Hello milo! +``` + +>Note that you can also add ES6 support to your webtasks easily, just put `"use latest";` at the top, and you're all set! + +## Storing your data + +We're going to take the titles and article excerpts that Pocket gives us and log their frequency. Since webtasks provide no guarantees around data survival, we'll need somewhere more persistant for the values to live. Here we'll use Mongo, because it's easy and free to get access to a database ([Mongolab](https://mongolab.com/plans/pricing/)), and pretty terse to code with under Node. + +Our webtask might look something like: + +```js +var parallel = require('async').parallel; +var MongoClient = require('mongodb').MongoClient; + +module.exports = function (ctx, done) { + var words = ctx.data.title + .split(' ') + .concat( + ctx.data.excerpt.split(' ') + ); + + MongoClient.connect(ctx.data.MONGO_URL, function (err, db) { + if(err) return done(err); + + var job_list = words.map(function (word) { + + return function (cb) { + save_word(word, db, function (err) { + if(err) return cb(err); + + cb(null); + }); + }; + + }); + + parallel(job_list, function (err) { + if(err) return done(err); + + done(null, 'Success.'); + }); + + }); +}; +``` + +We connect to the remote database, put all the words Pocket gives us in an array and loop over it, saving each one, then we confirm to IFTTT that we're done by responding. + +>Note that we can use `require` just as in regular Node. There is a list of available modules [here](https://tehsis.github.io/webtaskio-canirequire/), with many of them installed in multiple versions for you to start ``require`-ing (`require(module@0.13.2)`). + +## Top secrets + +Now we need to supply our webtask with access to a database, but a querystring is hardly the safest place for passwords! Instead we'll embed it, encrypted, in our webtask. Sounds like it might need some setup, but webtasks supports the passing of encrypted variables out of the box, by embedding them in a [token](https://webtask.io/docs/token). To pass your secrets safely to your task, just run: + +``` +$ wt create --secret SECRET= +``` + +And `SECRET` will by passed on `ctx.data`, just like the variables attached on the querystring. If you haven't already set one up, sign up for a sandbox account at [Mongolab](http://mongolab.com) and pass in your database's address as a the secret `MONGO_URL=mongodb://`. + +``` +$ wt create --secret MONGO_URL=mongodb://... pocket-ifttt.js +https://webtask.it.auth0.com/api/run//pocket-ifttt +``` + +## If This Then Webtask + +Connecting your webtask to IFTTT is relatively painless, just setup a recipe to be triggered every time you save something to Pocket. + +![](https://cdn.auth0.com/blog/ifttt-tutorial/ifttt-step1-1.0.png) + +And configure the 'That' component to be a 'Maker Channel', where we can hand over control to our script. + +![](https://cdn.auth0.com/blog/ifttt-tutorial/ifttt-step2-1.0.png) + +Copy and paste the URL given by `wt create` into the box, but add `&title={{Title}}&excerpt={{Excerpt}}` to the very end. This dumps the data given by the Pocket channel, making it consumable in the webtask's context. + +![Edited URL](https://cdn.auth0.com/blog/ifttt-tutorial/ifttt-maker-1.0.png "Edited URL") + + +You can test to see if everything's working by saving something in Pocket and watching your webtask's logs with: + +`$ wt logs` + +Sometimes it takes a little while for IFTTT to send the request (within a couple of minutes), but you should see a bunch of 'Successfully saved' messages in your console. + +![Success.](https://cdn.auth0.com/blog/ifttt-tutorial/logging-1.0.0.jpg "Nice logging skills.") + +We can soup it up by ignoring common words and punctuation, saving extra data etc, but the use of webtasks would remain the same. + +## To the backend and beyond + +![Ta da!](https://cdn.auth0.com/blog/ifttt-tutorial/mongo-read.jpg "Ta da!") + +The finished recipe can be found [here](https://ifttt.com/recipes/304471-record-most-read-words-to-mongodb), and the source is on [github](https://github.com/bananaoomarang/webtask-ifttt-tutorial), for your viewing pleasure. + +If you'd like to find out more about how webtasks work, as well as their more advanced features, you should check out the docs at [webtask.io](https://webtask.io), but hopefully you can see that their simplicity and versatility is already pretty exciting! diff --git a/_posts/2015-08-05-creating-your-first-aurelia-app-from-authentication-to-calling-an-api.markdown b/_posts/2015-08-05-creating-your-first-aurelia-app-from-authentication-to-calling-an-api.markdown new file mode 100644 index 0000000000..0635a99850 --- /dev/null +++ b/_posts/2015-08-05-creating-your-first-aurelia-app-from-authentication-to-calling-an-api.markdown @@ -0,0 +1,711 @@ +--- +layout: post +title: "Creating your first Aurelia app: From authentication to calling an API" +description: "Learn how to create a real world Aurelia app using ES6, aurelia-auth and much more! We'll implement from Authentication to calling an API and everything in between" +date: 2015-08-05 18:28 +alias: /2015/08/05/creating-your-first-aurelia-app-from-authentication-to-calling-an-api/ +category: Technical Guide, Frontend, Aurelia +author: + name: Ryan Chenkie + url: https://twitter.com/ryanchenkie?lang=en + mail: ryanchenkie@gmail.com + avatar: https://www.gravatar.com/avatar/7f4ec37467f2f7db6fffc7b4d2cc8dc2?size=200 +design: + bg_color: "#646F71" + image: https://cdn.auth0.com/blog/aurelia-logo.png + image_size: "70%" +tags: +- aurelia +- authentication +- authorization +- jwt +- spa +- api +- auth +related: +- 2015-08-11-create-your-first-ember-2-dot-0-app-from-authentication-to-calling-an-api +- 2015-12-15-create-a-desktop-app-with-angular-2-and-electron +- 2015-12-17-json-web-token-signing-algorithms-overview +--- + +
    + + This post is out of date. The Aurelia framework and its dependencies have undergone many changes since the publication of this article, and the method of authentication utilized in this post is not up-to-date with current best practices. Thank you for your patience while we work on bringing you an updated tutorial! +
    + +----- + +**TL;DR**: Aurelia is a great client-side JavaScript framework and adding JWT authentication to Aurelia apps is easy with the [aurelia-auth package](https://github.com/paulvanbladel/aurelia-auth). Check out the [GitHub repo](https://github.com/chenkie/aurelia-jwt-auth) for this article to find out how to add authentication to your Aurelia app. + +----- + +Aurelia is a client-side JavaScript framework that has been gaining a lot of popularity lately. One of the nice aspects of Aurelia is that it anticipates common application needs and provides simple conventions for accomplishing them. In some ways, Aurelia is similar to Angular 2, so parts of it may look familiar if you've [checked out Angular 2](https://auth0.com/blog/2015/05/14/creating-your-first-real-world-angular-2-app-from-authentication-to-calling-an-api-and-everything-in-between/) already. + +{% include tweet_quote.html quote_text="Aurelia anticipates common application needs and provides simple conventions for accomplishing them." %} + +## Getting Started + +Getting started with Aurelia is a piece of cake. The framework's [getting started guide](http://aurelia.io/hub.html#/doc/article/aurelia/framework/latest/quick-start/1) offers an in-depth set of instructions and a [seed project](https://github.com/aurelia/skeleton-navigation/releases) that make it very simple to get up and running quickly. + +Aurela has a cli tool. You can run `npm install -g aurelia-cli` to get the new cli tool. Then just run `au new` and the CLI will setup your project and you'll have an app ready for deploy within a few minutes. You can read more about it in the [CLI documentation](http://aurelia.io/hub.html#/doc/article/aurelia/framework/latest/the-aurelia-cli). + +This tutorial will expand upon the seed project and show how to add JWT authentication to a random quote application. We'll be using the [NodeJS JWT Authentication Sample](https://github.com/auth0/nodejs-jwt-authentication-sample) as our backend to show how we can retrieve a JWT upon login, save it in local storage, and send it along with every subsequent request. Our app will let all visitors retrieve a random quote, but logged-in users will be able to get a super-secret quote. + +![](https://cdn.auth0.com/blog/aurelia/aurelia-welcome.png?dl=1) + +### Two Ways to Get Going + +You can go through the [getting started](http://aurelia.io/get-started.html) guide that Aurelia provides and follow along, changing files in their seed project as we go. Alternatively, you can clone the [the project for this tutorial](https://github.com/chenkie/aurelia-jwt-auth) and follow along from there. + +If you are starting from Aurelia's seed project, be sure to split it out into two subdirectories, `client` and `server`. The `server` directory is where the [NodeJS JWT authentication sample](https://github.com/auth0/nodejs-jwt-authentication-sample) will go. + +To get the server setup, you can follow the instructions in the readme for the [NodeJS JWT Authentication Sample](https://github.com/auth0/nodejs-jwt-authentication-sample). + +### A Little Help from Aurelia-Auth + +To help us with our token dealings on the front-end, we'll use the awesome [aurelia-auth plugin](https://github.com/paulvanbladel/aurelia-auth) provided by Paul van Bladel, along with some pointers from his [sample app repo](https://github.com/paulvanbladel/aurelia-auth-sample). + +## Setting up the Client Config Files + +There's a bit of configuration setup to do, but once it's in place, things will be a breeze. + +After doing `npm install` and `jspm install` to pull in all the dependencies, we'll need to also install the `aurelia-auth` plugin. We can do this with `jspm` within the client directory: + + jspm install github:paulvanbladel/aurelia-auth + +### Application Bootstrap Config + +Let's give our app the name `quotes-app` and reflect this in the `body` tag of `index.html` where the app loads. + +```html + + + ... + + + + + + + + + ... + +``` + +Aurelia looks for a JavaScript file with the same name in the `src` directory for the main app config details. Let's create that now: + +```js + +// client/src/quotes-app.js + +import config from './auth-config'; + +export function configure(aurelia) { + + // Here we provide configuration for our application and can + // bring in the configuration settings we put within auth-config.js + // that will tell the aureliauth plugin the specific settings + // for our application's authentication context. + aurelia.use + .standardConfiguration() + .developmentLogging() + .plugin('paulvanbladel/aureliauth', (baseConfig) => { + baseConfig.configure(config); + }); + + aurelia.start().then(a => a.setRoot()); +} +``` + +You can see here that we're importing a file called `auth-config.js` and that it's the export from this file that is passed to the `baseConfig` for the plugin. The `auth-config` file will let us override the aurelia-auth plugin's defaults with our own specifics. Let's create it now: + +```js +// client/src/auth-config.js + +// Specific settings for our application's +// authentication context. These will override +// the default settings provided by aureliauth + +var config = { + + // Our Node API is being served from localhost:3001 + baseUrl: 'http://localhost:3001', + // The API specifies that new users register at the POST /users enpoint. + signupUrl: 'users', + // Logins happen at the POST /sessions/create endpoint. + loginUrl: 'sessions/create', + // The API serves its tokens with a key of id_token which differs from + // aureliauth's standard. + tokenName: 'id_token', + // Once logged in, we want to redirect the user to the welcome view. + loginRedirect: '#/welcome', + +} + +export default config; +``` + +The API is accessible at `localhost:3001`, so we set this as our `baseUrl`. Next, we will set up the proper endpoints needed for signing users up and logging them in. We also need to override the `tokenName` with what our API serves, which in this case is `id_token`. Our API serves both `id_token` and `access_token` but we'll see how to handle the `access_token` later in the tutorial. Finally, we say that we want to redirect the users to the `welcome` view once they log in. + +### Application Routing Config + +We'll now need to set up the application's routing configuration. Let's first set up the HTML that will require and load our nav bar and other views: + +```html + + +``` + +Here, we are requiring the `nav-bar` and binding it to the router. We will serve our views from the `` within our containing `
    `. + +```js +// client/src/app.js + +import 'bootstrap'; +import 'bootstrap/css/bootstrap.css!'; + +import {inject} from 'aurelia-framework'; +import {Router} from 'aurelia-router'; +import HttpClientConfig from 'paulvanbladel/aureliauth/app.httpClient.config'; +import AppRouterConfig from 'router-config'; + +// Using Aurelia's dependency injection, we inject Aurelia's router, +// the aurelia-auth http client config, and our own router config +// with the @inject decorator. +@inject(Router, HttpClientConfig, AppRouterConfig) + +export class App { + + constructor(router, httpClientConfig, appRouterConfig) { + + this.router = router; + + // Client configuration provided by the aureliauth plugin + this.httpClientConfig = httpClientConfig; + + // The application's configuration, including the + // route definitions that we've declared in router-config.js + this.appRouterConfig = appRouterConfig; + }; + + activate() { + + // Here, we run the configuration when the app loads. + this.httpClientConfig.configure(); + this.appRouterConfig.configure(); + + }; +} +``` + +The HTTP configuration that `aurelia-auth` provides is what handles adding the JWT as a header if the user is authenticated. The `httpClientConfig` file has logic that checks for the existence of a token in `localstorage` and then adds an `Authorization` header with a value of `Bearer ` if one exists. The token will be sent for all HTTP requests to the API but will obviously only be needed for protected resources. + +![](https://cdn.auth0.com/blog/aurelia/aurelia-auth-bearer.png?dl=1) + +We can keep our routing logic within the main `app.js` file, as is done in many Aurelia projects, but in our case, we'll put this configuration in a separate file called `router-config.js` that we are injecting. Let's set up this routing configuration: + +```js +// client/src/router-config.js + +import {AuthorizeStep} from 'paulvanbladel/aureliauth'; +import {inject} from 'aurelia-framework'; +import {Router} from 'aurelia-router'; + +// Using Aurelia's dependency injection, we inject Router +// with the @inject decorator +@inject(Router) + +export default class { + + constructor(router) { + this.router = router; + }; + + configure() { + + var appRouterConfig = function(config) { + + config.title = 'Random Quotes App'; + + // Here, we hook into the authorize extensibility point + // to add a route filter so that we can require authentication + // on certain routes + config.addPipelineStep('authorize', AuthorizeStep); + + // Here, we describe the routes we want along with information about them + // such as which they are accessible at, which module they use, and whether + // they should be placed in the navigation bar + config.map([ + { route: ['','welcome'], name: 'welcome', moduleId: './welcome', nav: true, title:'Welcome' }, + { route: 'random-quote', name: 'random-quote', moduleId: './random-quote', nav: true, title:'Random Quote' }, + // The secret-quote route is the only one that the user needs to be logged in to see, so we set auth: true + { route: 'secret-quote', name: 'secret-quote', moduleId: './secret-quote', nav: true, title:'Super Secret Quote', auth: true }, + { route: 'signup', name: 'signup', moduleId: './signup', nav: false, title:'Signup', authRoute: true }, + { route: 'login', name: 'login', moduleId: './login', nav: false, title:'Login', authRoute: true }, + { route: 'logout', name: 'logout', moduleId: './logout', nav: false, title:'Logout', authRoute: true } + ]); + }; + + // The router is configured with what we specify in the appRouterConfig + this.router.configure(appRouterConfig); + + }; +} +``` + +Aurelia gives us the ability to customize the navigation pipeline with some extensibility points, including an `authorize` route filter. Using this filter means we can specify which routes we would like authentication to be required for. Since our `super-secret-quotes` route needs to remain top secret until the user is logged in, we put `auth: true` in it. We hook into this filter by calling `addPipelineStep`, passing in the `AuthorizeStep` that is provided by the `aurelia-auth` plugin. + +With the configuration out of the way, let's get to coding the actual routes and their views! We'll need to have files that take care of each route in place before the app will work so you can comment out the routes in `router-config.js` that aren't ready yet. + +## Setting up Routes and Views + +Two files are required for each route in Aurelia--a JavaScript file for the view model logic and an HTML file for the view itself. Views are enclosed within `