Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/Auth : Playground Sample : automatic refresh token #658

Merged
merged 71 commits into from
Aug 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
cef7a1d
Added password grant_type option
alain-charles Jun 29, 2018
9f0dbab
Added password grant_type option implementation
alain-charles Jun 29, 2018
44d7d2e
Playground sample for oAuth2 password grant-type
alain-charles Jun 29, 2018
f57b737
Added route to oAuth2 password grant-type sample
alain-charles Jun 29, 2018
ed4628e
Addes angular-jwt for decoding jwt token (used in playground only)
alain-charles Jun 29, 2018
d7f01df
Added /api/auth/token endpoint (oAuth2 password grant-type playground)
alain-charles Jun 29, 2018
af52e88
Patched code for passing ci
alain-charles Jun 29, 2018
554f7dc
code optimization for passing ci
alain-charles Jun 29, 2018
fa0548f
Merge branch 'master' into master
nnixaa Jun 29, 2018
b72adae
Changes request by Dmitry (first review)
alain-charles Jun 29, 2018
733efc0
Generalized code in oauth2-strategy
alain-charles Jun 29, 2018
57fab69
Merge remote-tracking branch 'origin/master'
alain-charles Jun 29, 2018
c50df3a
Generalized code in oauth2-strategy
alain-charles Jun 29, 2018
dda5436
Added unit tests for oAuth-strategy password authentication
alain-charles Jul 2, 2018
ef0be25
Merge branch 'master' into master
alain-charles Jul 3, 2018
e180dc3
Merge branch 'master' into master
nnixaa Jul 4, 2018
6aa1fdb
Cleaned code according to nnixaa second review
alain-charles Jul 4, 2018
96c7344
Merge branch 'master' of https://github.com/alain-charles/nebular
alain-charles Jul 4, 2018
cf82c42
Removed package-lock.json from git repo
alain-charles Jul 4, 2018
dcff930
package-lock.json to revert
alain-charles Jul 4, 2018
315662f
reverted package-lock.json
alain-charles Jul 4, 2018
57a0230
Added new grant_type 'PASSWORD' in the block comment for it to be ins…
alain-charles Jul 4, 2018
d69d633
Merge remote-tracking branch 'upstream/master'
alain-charles Jul 5, 2018
1be8d91
Add the refreshtoken request management in NbJwtInterceptor and NbAut…
alain-charles Jul 13, 2018
4b67825
Merge branch 'master' of https://github.com/alain-charles/nebular
alain-charles Jul 19, 2018
deea3e2
Merge branch 'master' into temp
alain-charles Jul 19, 2018
acd0241
The token now contains ownerStrategyName, with is a back link to the …
alain-charles Jul 19, 2018
25250f0
feature/auth:
alain-charles Jul 19, 2018
c0fdd31
feature/auth:
alain-charles Jul 19, 2018
bd564b3
feature/auth:
alain-charles Jul 20, 2018
44ed61a
Merge branch 'master' into master
nnixaa Jul 20, 2018
9c16387
feature/auth
alain-charles Jul 24, 2018
2dd26c4
Merge remote-tracking branch 'origin/master'
alain-charles Jul 24, 2018
b284377
Merge branch 'master' into master
alain-charles Jul 24, 2018
e462c7d
feature/auth
alain-charles Jul 24, 2018
a3ee10b
Merge branch 'master' into master
nnixaa Jul 24, 2018
b38ae55
Merge branch 'master' into master
nnixaa Jul 25, 2018
62a2aff
feature/auth
alain-charles Jul 25, 2018
66ca493
Merge remote-tracking branch 'origin/master'
alain-charles Jul 25, 2018
3bd5a50
feature/auth
alain-charles Jul 25, 2018
398c058
feature/auth
alain-charles Jul 25, 2018
ee706d3
Merge branch 'master' into master
alain-charles Jul 26, 2018
5eb5821
feature/auth
alain-charles Jul 26, 2018
1a08225
Merge remote-tracking branch 'origin/master'
alain-charles Jul 26, 2018
1d697bc
feature/auth
alain-charles Jul 27, 2018
1c25956
Merge remote-tracking branch 'upstream/master'
alain-charles Jul 27, 2018
ff0e8d3
Merge remote-tracking branch 'upstream/master'
alain-charles Jul 27, 2018
e7af99e
Optimized getAccessTokenPayload() calls
alain-charles Jul 28, 2018
95632b1
Optimized getAccessTokenPayload() calls
alain-charles Jul 28, 2018
bc4001f
Merge branch 'master' into master
nnixaa Jul 30, 2018
7e21d20
Merge branch 'master' of https://github.com/akveo/nebular into feat/a…
alain-charles Aug 14, 2018
c76bfeb
auth/feat:
alain-charles Aug 15, 2018
c144b37
corrected import path for ci:docs
alain-charles Aug 15, 2018
f511fce
Merge branch 'master' into feat/auth-automaticRefreshToken
nnixaa Aug 16, 2018
b25e052
First version after 2 days of exchanges with nnixaa about the archite…
alain-charles Aug 17, 2018
b720265
Merge remote-tracking branch 'origin/feat/auth-automaticRefreshToken'…
alain-charles Aug 17, 2018
0cd5103
Commented out some things relative to other implementation
alain-charles Aug 17, 2018
71eb911
Added export to filter function ?!
alain-charles Aug 17, 2018
c0bf6a3
Corrected import paths
alain-charles Aug 17, 2018
d7e70de
Cleaned code (comments)
alain-charles Aug 18, 2018
1e0eeff
Merge from upstream
alain-charles Aug 22, 2018
24d9122
Reverted 2 files (unrelated to the PR)
alain-charles Aug 22, 2018
9673ee2
Modified backend to that the automatic refreshToken request can be te…
alain-charles Aug 22, 2018
6bdf0ba
Merge branch 'master' into feat/auth-automaticRefreshToken
nnixaa Aug 23, 2018
0451d28
Merge branch 'master' into feat/auth-automaticRefreshToken
nnixaa Aug 23, 2018
0187ead
refactor(auth): refactor api-calls example
nnixaa Aug 23, 2018
0c6ed0e
Merge from upstream
alain-charles Aug 23, 2018
e6fd503
Merge remote-tracking branch 'origin/feat/auth-automaticRefreshToken'…
alain-charles Aug 23, 2018
5b4f975
Corrected imports for ci:docs to pass
alain-charles Aug 23, 2018
9a47298
Merge branch 'feat/auth-automaticRefreshToken' into alain-charles-fea…
alain-charles Aug 23, 2018
802fdf5
Merge pull request #3 from nnixaa/alain-charles-feat/auth-automaticRe…
alain-charles Aug 23, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 51 additions & 27 deletions src/backend/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@ const express = require('express');
const bodyParser = require('body-parser');
const jwt = require('jwt-simple');
const auth = require('./auth.js')();
const auth_helpers = require('./auth_helpers.js');
const users = require('./users.js');
const tokens = require('./token_helpers.js');
const wines = require('./wines.js');
const cfg = require('./config.js');
const app = express();
const moment = require('moment');


app.use(bodyParser.json());
app.use(auth.initialize());
Expand All @@ -34,6 +39,10 @@ app.get('/api/user', auth.authenticate(), function (req, res) {
});
});

app.get('/api/wines', auth.authenticate(), function (req,res) {
res.json(wines);
})

app.post('/api/auth/login', function (req, res) {

if (req.body.email && req.body.password) {
Expand All @@ -43,16 +52,10 @@ app.post('/api/auth/login', function (req, res) {
return u.email === email && u.password === password;
});
if (user) {
var payload = {
id: user.id,
email: user.email,
role: 'user',
};
var token = jwt.encode(payload, cfg.jwtSecret);
return res.json({
data: {
message: 'Successfully logged in!',
token: token
token: tokens.createAccessToken(user),
}
});
}
Expand All @@ -73,17 +76,11 @@ app.post('/api/auth/token', function (req, res) {
return u.email === email && u.password === password;
});
if (user) {
var payload = {
id: user.id,
email: user.email,
role: 'user',
};
var token = jwt.encode(payload, cfg.jwtSecret);
return res.json({
token_type: 'Bearer',
access_token: token,
expires_in: 3600,
refresh_token: 'eb4e1584-0117-437c-bfd7-343f257c4aae',
access_token: tokens.createAccessToken(user),
expires_in: cfg.accessTokenExpiresIn,
refresh_token: tokens.createRefreshToken(user),
});
}
}
Expand Down Expand Up @@ -155,18 +152,45 @@ app.delete('/api/auth/logout', function (req, res) {
});

app.post('/api/auth/refresh-token', function (req, res) {
var payload = {
id: users[0].id,
email: users[0].email,
role: 'user',
};
var token = jwt.encode(payload, cfg.jwtSecret);

// token issued by oauth2 strategy
if (req.body.refresh_token) {
var token = req.body.refresh_token;
var parts = token.split('.');
if (parts.length !== 3) {
return res.status(401).json({
error: 'invalid_token',
error_description: 'Invalid refresh token'
});
}
var payload = JSON.parse(auth_helpers.urlBase64Decode(parts[1]));
var exp = payload.exp;
var userId = payload.sub;
var now = moment().unix();
if (now > exp) {
return res.status(401).json({
error: 'unauthorized',
error_description: 'Refresh Token expired.'
})
} else {
return res.json({
token_type: 'Bearer',
access_token: tokens.createAccessToken(users[userId - 1]),
expires_in: cfg.accessTokenExpiresIn,
});
}
}

// token issued via email strategy
if (req.body.token) {
return res.json({
data: {
message: 'Successfully refreshed token.',
token: token
}
});
data: {
message: 'Successfully refreshed token!',
token: tokens.createAccessToken(users[0]),
}
});
};

});

app.listen(4400, function () {
Expand Down
4 changes: 2 additions & 2 deletions src/backend/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ var ExtractJwt = passportJWT.ExtractJwt;
var Strategy = passportJWT.Strategy;
var params = {
secretOrKey: cfg.jwtSecret,
jwtFromRequest: ExtractJwt.fromAuthHeader()
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken()
};

module.exports = function () {
var strategy = new Strategy(params, function (payload, done) {
var user = users[payload.id] || null;
var user = users[payload.sub -1 ] || null;
if (user) {
return done(null, {
id: user.id
Expand Down
46 changes: 46 additions & 0 deletions src/backend/auth_helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
function b64decode(str) {
var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
var output = '';

str = String(str).replace(/=+$/, '');

if (str.length % 4 === 1) {
console.error("'atob' failed: The string to be decoded is not correctly encoded.");
}

for (
// initialize result and counters
var bc=0, bs, buffer, idx= 0;
// get next character
buffer = str.charAt(idx++);
// character found in table? initialize bit storage and add its ascii value;
~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer,
// and if not first of each 4 characters,
// convert the first 8 bits to one ascii character
bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0
) {
// try to find character in table (0-63, not found => -1)
buffer = chars.indexOf(buffer);
}
return output;
}

function b64DecodeUnicode(str) {
return decodeURIComponent(Array.prototype.map.call(b64decode(str), function(c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
}

module.exports.urlBase64Decode = function (str) {
var output = str.replace(/-/g, '+').replace(/_/g, '/');
switch (output.length % 4) {
case 0: { break; }
case 2: { output += '=='; break; }
case 3: { output += '='; break; }
default: {
throw new Error('Illegal base64url string!');
}
}
return b64DecodeUnicode(output);
}

4 changes: 3 additions & 1 deletion src/backend/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@ module.exports = {
jwtSecret: 'MyS3cr3tK3Y',
jwtSession: {
session: false
}
},
accessTokenExpiresIn : 60,
refreshTokenExpiresIn: 120
};
3 changes: 2 additions & 1 deletion src/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"body-parser": "^1.17.1",
"express": "^4.15.2",
"jwt-simple": "^0.5.1",
"moment": "^2.22.2",
"passport": "^0.3.2",
"passport-jwt": "^2.2.1"
"passport-jwt": "^4.0.0"
}
}
33 changes: 33 additions & 0 deletions src/backend/token_helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const moment = require('moment');
const jwt = require('jwt-simple');
const cfg = require('./config.js');

module.exports.permanentRefreshToken = 'eb4e15840117437cbfd7343f257c4aae';

module.exports.createAccessToken = function(user) {
var payload = {
sub: user.id,
exp: moment().add(cfg.accessTokenExpiresIn, 'seconds').unix(),
iat: moment().unix(),
id: user.id,
email: user.email,
role: 'user',
};
var token = jwt.encode(payload, cfg.jwtSecret);
return token;
}

module.exports.createRefreshToken = function(user) {
var refreshPayload = {
sub: user.id,
exp: moment().add(cfg.refreshTokenExpiresIn, 'seconds').unix(),
iat: moment().unix(),
id: user.id,
email: user.email,
role: 'REFRESH_TOKEN',
};
var refreshToken = jwt.encode(refreshPayload, cfg.jwtSecret);
return refreshToken;
}

module.exports
22 changes: 22 additions & 0 deletions src/backend/wines.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const wines = [
{
id: 1,
name: 'Pommard 1er cru',
region: 'Bourgogne',
year: 2012,
},
{
id: 2,
name: 'Aloxe Corton Grand cru',
region: 'Bourgogne',
year: 2008,
},
{
id: 3,
name: 'Meursault 1er cru',
region: 'Bourgogne',
year: 1997,
},
];

module.exports = wines;
93 changes: 93 additions & 0 deletions src/playground/auth/api-calls/api-calls.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/**
* @license
* Copyright Akveo. All Rights Reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*/

import { Component, Inject } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Router } from '@angular/router';
import { Observable, of as observableOf } from 'rxjs';
import { catchError, delay } from 'rxjs/operators';
import { NbAuthResult, NbAuthService, NbAuthToken } from '../../../framework/auth/services';
import { NB_AUTH_OPTIONS } from '../../../framework/auth/auth.options';
import { getDeepFromObject } from '../../../framework/auth/helpers';
import { Wine } from './wine';

@Component({
selector: 'nb-playground-api-calls',
template: `
<router-outlet></router-outlet>
<nb-layout>
<nb-layout-column>
<nb-card>
<nb-card-body>
<h2>You are authenticated</h2>
<p>You can call the secured API</p>
<button nbButton status="primary" (click)="loadWines()">Call API</button>
<button nbButton status="primary" (click)="logout()">Sign out</button>
</nb-card-body>
</nb-card>
<nb-card *ngIf="(wines$ | async)?.length">
<nb-card-header>
Alain'wines
</nb-card-header>
<nb-list>
<nb-list-item *ngFor="let wine of wines$ | async">
{{ wine.region }}, {{ wine.name }} ({{ wine.year }})
</nb-list-item>
</nb-list>
</nb-card>
</nb-layout-column>
</nb-layout>
`,
})

export class NbPlaygroundApiCallsComponent {

token: NbAuthToken;
wines$: Observable<Wine[]>;
redirectDelay: number = 0;
strategy: string = '';

constructor(private authService: NbAuthService,
private http: HttpClient,
private router: Router,
@Inject(NB_AUTH_OPTIONS) protected options = {}) {

this.redirectDelay = this.getConfigValue('forms.logout.redirectDelay');
this.strategy = this.getConfigValue('forms.logout.strategy');

this.authService.onTokenChange()
.subscribe((token: NbAuthToken) => {
this.token = null;
if (token && token.isValid()) {
this.token = token;
}
});
}

logout() {
this.authService.logout(this.strategy)
.pipe(
delay(this.redirectDelay),
)
.subscribe((result: NbAuthResult) => this.router.navigate(['/auth/login']));
}

loadWines() {
this.wines$ = this.http.get<Wine[]>('http://localhost:4400/api/wines')
.pipe(
catchError(err => {
if (err instanceof HttpErrorResponse && err.status === 401) {
this.router.navigate(['/auth/login']);
}
return observableOf([]);
}),
);
}

getConfigValue(key: string): any {
return getDeepFromObject(this.options, key, null);
}
}
6 changes: 6 additions & 0 deletions src/playground/auth/api-calls/wine.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface Wine {
id: number;
name: string;
region: string;
year: number
}
6 changes: 6 additions & 0 deletions src/playground/auth/auth-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
} from '@nebular/auth';
import { NbAclTestComponent } from './acl/acl-test.component';
import { NbAuthGuard } from './auth-guard.service';
import { NbPlaygroundApiCallsComponent } from './api-calls/api-calls.component';


export const routes: Routes = [
Expand Down Expand Up @@ -66,6 +67,11 @@ export const routes: Routes = [
canActivate: [NbAuthGuard],
component: NbAuthPlaygroundComponent,
},
{
path: 'auth/api-calls.component',
canActivate: [NbAuthGuard],
component: NbPlaygroundApiCallsComponent,
},
];

@NgModule({
Expand Down
Loading