Multi Tenancy support for IdentityServer4 with javascript client - identityserver4

Hi I am using IdentityServer4 with aspnet core application.
I am using MVC client from their sample and also using a javascript client. The javascript client can be opened as tenantone.domain.com or tenanttwo.domain.com and so on according to tenancy name.
I am not able to get authorization for dynamic sub-domains.
Please help! if any one has done such task in asp.net core
To Register the javascript client I am using below code
string tenancyName = "tenantone"
var discoveryClient = new DiscoveryClient("http://login.domain.io:5000");
discoveryClient.Policy.RequireHttps = false;
var doc = await discoveryClient.GetAsync();
var request = new IdentityModel.Client.AuthorizeRequest(doc.AuthorizeEndpoint);
var url = new IdentityModel.Client.AuthorizeRequest(doc.AuthorizeEndpoint).CreateAuthorizeUrl(
clientId: tenancyName,
responseType: ResponseTypes.IdTokenToken,
scope: "openid profile api1",
redirectUri: "http://" + tenancyName + ".domain.io:5003/callback.html",
state: "random_state",
nonce: "random_nonce",
responseMode: "form_post",
extra: new
{
AllowedCorsOrigins = "http://" + tenancyName + ".domain.io:5003"
});
var response = new IdentityModel.Client.AuthorizeResponse(url);
var accessToken = response.AccessToken;
var idToken = response.IdentityToken;
var state = response.State;
Then after to be authorized from client I am using below code:
var config = {
authority: "http://login.domain.io:5000",
client_id: "tenantone",
redirect_uri: "http://tenantone.domain.io:5003/callback.html",
response_type: "id_token token",
scope: "openid profile api1",
post_logout_redirect_uri: "http://tenantone.domain.io:5003/",
};
var mgr = new Oidc.UserManager(config);
debugger;
mgr.getUser().then(function (user) {
if (user) {
//log("User logged in", user.profile);
$("#bodyPage").removeAttr("style");
}
else {
// log("User not logged in");
mgr.signinRedirect();
}
});

Related

renewal token is not working in adal.js v1.0.14

I am using adal.js 1.0.14, below is my login
var authenticationContext = new AuthenticationContext(config);
if (!config.popUp) {
if (authenticationContext.isCallback(window.location.hash)) {
authenticationContext.handleWindowCallback();
}
}
var user = authenticationContext.getCachedUser();
sessionStorage.setItem("adal:Tenant","#CommonMethods.GetAppConfigValue("ida:TenantId")")
if (!user) {
authenticationContext.login();
}
below is my renewal code
this.authenticationContext.getCachedUser();
this.authenticationContext.acquireToken(config.clientId, function (errorDesc, token) {
if (errorDesc) {
console.log("ErrorDesc", errorDesc);
}
if (token) {
console.log("calling the Web API with the access token", token);
}
});
but i got "User login is required". Can't understand what happen with this.
I got the problem, the problem is, I use below config settings for authentication
var config = {
tenant: window.sessionStorage.getItem('adal:Tenant'),
clientId: window.sessionStorage.getItem('adal.token.keys'),
redirectUri: baseURL,
popUp: false,
postLogoutRedirectUri: baseURL,
expireOffsetSeconds: 3540
}
in above code, the ClientId contain '|', because of which can't get token and user information. I update my config settings,
var config = {
tenant: window.sessionStorage.getItem('adal:Tenant'),
clientId: window.sessionStorage.getItem('adal.token.keys').replace('|', ''),
redirectUri: baseURL,
popUp: false,
postLogoutRedirectUri: baseURL,
expireOffsetSeconds: 3540
}
And it works.

No user in signinSilentCallback using identityserver and oidc client of javascript

I am getting user undefined in following code.
I have already authenticated user from MVC.
But when I use signinSilentCallback to get detail of that user, it is getting undefined using oidc-client in js.
It doesn't give any error as well.
var mgr = new UserManager({
authority: "http://localhost:5000",
client_id: "js",
redirect_uri: "http://localhost:50144/signin-oidc",
silent_redirect_uri: "http://localhost:50144/signin-oidc",
response_type: "id_token token",
post_logout_redirect_uri: "http://localhost:50144/signout-callback-oidc",
});
mgr.signinSilentCallback().then(function (user) {
//**Here user is undefined.**
axios.defaults.headers.common['Authorization'] = "Bearer " + user.access_token;
});
In Identityserver 4, client is defined as following.
new Client
{
ClientId = "js",
ClientName = "js",
ClientUri = "http://localhost:50144",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RequireClientSecret = false,
AccessTokenType = AccessTokenType.Jwt,
RedirectUris =
{
"http://localhost:50144/signin-oidc",
},
PostLogoutRedirectUris = { "http://localhost:50144/signout-callback-oidc" },
AllowedCorsOrigins = { "http://localhost:50144" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email
}
}
signinSilentCallback: Returns promise to notify the parent window of response from the authorization endpoint.
https://github.com/IdentityModel/oidc-client-js/wiki
signinSilentCallback - This is not something will return you the user object.
If you really need to get the user object on silent renew i would suggest to use this approach with folloowing code snippet. This works for me in salesforce apps as well.
this.userManager.events.addAccessTokenExpiring(() =>
{
this.userManager.signinSilent({scope: oidcSettings.scope, response_type: oidcSettings.response_type})
.then((user: CoreApi.Authentication.Interfaces.OidcClientUser) =>
{
this.handleUser(user); // This function just set the current user
})
.catch((error: Error) =>
{
this.userManager.getUser()
.then((user: CoreApi.Authentication.Interfaces.OidcClientUser) =>
{
this.handleUser(user);
});
});
});
We need to handle the getUser in catch as well due to one of bug reported for iFrame in oidc-client js
From above code focus on the way the silent renew is performed when the token expires.
you can set automaticSilentRenew to true in your config
var mgr = new UserManager({
authority: "http://localhost:5000",
client_id: "js",
redirect_uri: "http://localhost:50144/signin-oidc",
silent_redirect_uri: "http://localhost:50144/signin-oidc",
response_type: "id_token token",
post_logout_redirect_uri: "http://localhost:50144/signout-callback-oidc",
automaticSilentRenew: true; //here
});
and you can use UserManager events to load the new user when the token is refreshed
this.mgr.events.addUserLoaded(args => {
this.mgr.getUser().then(user => {
this._user = user; // load the new user
});
});

Null Identity Token Using AuthorizeRequest & AuthorizeResponse

I'm trying to use use AuthorizeRequest and AuthorizeResponse inside an MVC action to see if the user is already authenticated, but pretty much everything in the response object comes back null besides Raw, Scope, and State. IsError is false.
I have an Application A and an Application B. I want to know in Application A if the user already signed in for Application B.
var request = new AuthorizeRequest("https://localhost:5000/connect/authorize");
var url = request.CreateAuthorizeUrl(
clientId: "Portal-Local",
scope: "openid profile email",
responseType: OidcConstants.ResponseTypes.IdToken,
responseMode: OidcConstants.ResponseModes.FormPost,
redirectUri: "https://localhost/Portal/home/index",
state: CryptoRandom.CreateUniqueId(),
nonce: CryptoRandom.CreateUniqueId());
var response = new AuthorizeResponse(url);
var accessToken = response.AccessToken;
var idToken = response.IdentityToken;
var state = response.State;

OAuth Token Response to Angular View (cookie)

I've been struggling with this for a couple hours now and need some help. I've created a simple app that presents the user with a "Login Using Google" button in an angular view that redirects the user to the Google Oauth page. Here's the controller code that calls the login() function when the button is pressed:
angular.module('dashApp').controller('SigninCtrl', function ($scope) {
$scope.login=function() {
var client_id="191641883719-5eu80vgnbci49dg3fk47grs85e0iaf9d.apps.googleusercontent.com";
var scope="email";
var redirect_uri="http://local.host:9000/api/auth/google";
var response_type="code";
var url="https://accounts.google.com/o/oauth2/auth?scope="+scope+"&client_id="+client_id+"&redirect_uri="+redirect_uri+
"&response_type="+response_type;
window.location.replace(url);
};
});
The redirect URI set in my google project redirects to a server page to this server page:
'use strict';
var _ = require('lodash');
var request = require('request');
var qs = require('querystring');
var fs = require('fs');
// Get list of auths
exports.google_get = function (req,res){
var code = req.query.code,
error = req.query.error;
if(code){
//make https post request to google for auth token
var token_request = qs.stringify({
grant_type: "authorization_code",
code: code,
client_id: "191641883719-5eu80vgnbci49dg3fk47grs85e0iaf9d.apps.googleusercontent.com",
client_secret: process.env.GOOGLE_SECRET,
redirect_uri: "http://local.host:9000/api/auth/google"
});
var request_length = token_request.length;
var headers = {
'Content-length': request_length,
'Content-type':'application/x-www-form-urlencoded'
};
var options = {
url:'https://www.googleapis.com/oauth2/v3/token',
method: 'POST',
headers: headers,
body:token_request
};
request.post(options,function(error, response, body){
if(error){
console.error(error);
}else{
//WHAT GOES HERE?
}
});
}
if(error){
res.status(403);
}
}
I'm able to exchange the code returned by google for an auth token object successfully and log it to the terminal. I've been told that I should set a cookie using:
res.setHeader('Content-Type', 'text/plain');
res.setCookie('SID','yes',{
domain:'local.host',
expires:0,
path:'/dashboard',
httpOnly:false
});
res.status(200);
res.end();
Followed by a controller on the page I'm directing the user to that validates the session.
What am I doing wrong?
Since you have already done the hard work so there is no point talking about passport.js which is actually written to simplify these kind of social login authentication.
So let's come directly to session implementaion logic.
You need to set the following header in your app.js/server.js :
app.use(function(req, res, next) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST');
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type, Authorization');
next();
});
Let's say you are returning this token after successful login :
{
name : "some name",
role : "some role",
info : "some info"
}
You can have a client side function in your angular service or controller :
function(user,callback){
var loginResource = new LoginResource(); //Angular Resource
loginResource.email = user.email;
loginResource.password = user.password;
loginResource.$save(function(result){
if(typeof result !== 'undefined'){
if(result.type){
$localStorage.token = result.token;
$cookieStore.put('user',result.data);
$rootScope.currentUser = result.data;
}
}
callback(result);
});
}
LoginResource calls your REST endpoint which returns auth token.
You can store your auth token in localStorage and cookieStore.
localStorage makes sure that we are having the token saved even when user has closed the browser session.
If he clears the localStorage and cookieStorage both then log him out as you don't have any valid token to authorize user.
This is the same logic which i am using here. If you need more help then let me know.

Sign in Twitter OAuth without Sessions aka token_secret (AngularJS Fail)

This is an express route from angularjs satellizer example, implementing 3 legged OAuth with Twitter:
/*
|--------------------------------------------------------------------------
| Login with Twitter
|--------------------------------------------------------------------------
*/
app.get('/auth/twitter', function(req, res) {
var requestTokenUrl = 'https://api.twitter.com/oauth/request_token';
var accessTokenUrl = 'https://api.twitter.com/oauth/access_token';
var authenticateUrl = 'https://api.twitter.com/oauth/authenticate';
if (!req.query.oauth_token || !req.query.oauth_verifier) {
var requestTokenOauth = {
consumer_key: config.TWITTER_KEY,
consumer_secret: config.TWITTER_SECRET,
callback: config.TWITTER_CALLBACK
};
// Step 1. Obtain request token for the authorization popup.
request.post({ url: requestTokenUrl, oauth: requestTokenOauth }, function(err, response, body) {
var oauthToken = qs.parse(body);
var params = qs.stringify({ oauth_token: oauthToken.oauth_token });
// Step 2. Redirect to the authorization screen.
res.redirect(authenticateUrl + '?' + params);
});
} else {
var accessTokenOauth = {
consumer_key: config.TWITTER_KEY,
consumer_secret: config.TWITTER_SECRET,
token: req.query.oauth_token,
verifier: req.query.oauth_verifier
};
// Step 3. Exchange oauth token and oauth verifier for access token.
request.post({ url: accessTokenUrl, oauth: accessTokenOauth }, function(err, response, profile) {
profile = qs.parse(profile);
// Step 4a. Link user accounts.
if (req.headers.authorization) {
User.findOne({ twitter: profile.user_id }, function(err, existingUser) {
if (existingUser) {
return res.status(409).send({ message: 'There is already a Twitter account that belongs to you' });
}
var token = req.headers.authorization.split(' ')[1];
var payload = jwt.decode(token, config.TOKEN_SECRET);
User.findById(payload.sub, function(err, user) {
if (!user) {
return res.status(400).send({ message: 'User not found' });
}
user.twitter = profile.user_id;
user.displayName = user.displayName || profile.screen_name;
user.save(function(err) {
res.send({ token: createToken(user) });
});
});
});
} else {
// Step 4b. Create a new user account or return an existing one.
User.findOne({ twitter: profile.user_id }, function(err, existingUser) {
if (existingUser) {
var token = createToken(existingUser);
return res.send({ token: token });
}
var user = new User();
user.twitter = profile.user_id;
user.displayName = profile.screen_name;
user.save(function() {
var token = createToken(user);
res.send({ token: token });
});
});
}
});
}
});
The problem is Step 3:
var accessTokenOauth = {
consumer_key: config.TWITTER_KEY,
consumer_secret: config.TWITTER_SECRET,
token: req.query.oauth_token,
verifier: req.query.oauth_verifier
};
// Step 3. Exchange oauth token and oauth verifier for access token.
request.post({ url: accessTokenUrl, oauth: accessTokenOauth });
Because the node-request documentation describes Step 3 as:
// step 3
// after the user is redirected back to your server
var auth_data = qs.parse(body)
, oauth =
{ consumer_key: CONSUMER_KEY
, consumer_secret: CONSUMER_SECRET
, token: auth_data.oauth_token
, token_secret: req_data.oauth_token_secret
, verifier: auth_data.oauth_verifier
}
, url = 'https://api.twitter.com/oauth/access_token'
;
request.post({url:url, oauth:oauth}
The difference is, in the satellizer example, it doesn't pass token_secret to sign-in, but it should. So is this a mistake or what am I missing?
The real problem for me was, 3 legged twitter sign-in flow actually requires session on server side, but the satellizer example doesn't use any sessions, so I was wondering how this possible without sessions, but either it is not possible and satellizer example is wrong, or I don't understand something.

Resources