Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Authenticate Users and use Graph API using Azure AD, ReactJS and NodeJS

I have an project app created using NodeJS + ReactJS and all that I want is to use the Azure AD to authenticate the user and get his data like name, groups, picture, occupation, etc, using the Graph API from Azure.

I already have the Azure AD and the App properly configured on Azure Portal. Including the permissions delegations and all these staff.

I'm trying to understand how is best way to do that but without success. I've been trying to find on google, StackOverflow, Microsoft Documentations, even projects samples.

Some samples have worked, but no one of them I could understand and put into my project to work as a production App.

I've used this for Authenticate the user, but the returned AccessToken is not valid to call the Graph API:

passport.use(new OIDCStrategy({
    redirectUrl: config.creds.returnURL,
    realm: config.creds.realm,
    clientID: config.creds.clientID,
    clientSecret: config.creds.clientSecret,
    oidcIssuer: config.creds.issuer,
    identityMetadata: config.creds.identityMetadata,
    skipUserProfile: config.creds.skipUserProfile,
    responseType: config.creds.responseType,
    responseMode: config.creds.responseMode,
    allowHttpForRedirectUrl: config.creds.allowHttpForRedirectUrl
  },
  function(iss, sub, profile, accessToken, refreshToken, done) {
    console.log(accessToken);
    profile = profile._json;
    if (!profile.email) {
      return done(new Error("No email found"), null);
    }
    // asynchronous verification, for effect...
    process.nextTick(function () {
      findByEmail(profile.email, function(err, user) {
        if (err) {
          return done(err);
        }
        if (!user) {
          // "Auto-registration"
          users.push(profile);
          return done(null, profile);
        }
        return done(null, user);
      });
    });
  }
));

And this one gives me right AccessToken to use the Graph API, but I cannot understand how to use it instead of the Passport:

function createAuthorizationUrl(state) {
  var authorizationUrl = templateAuthzUrl.replace('<client_id>', sampleParameters.clientId);
  authorizationUrl = authorizationUrl.replace('<redirect_uri>',redirectUri);
  authorizationUrl = authorizationUrl.replace('<state>', state);
  authorizationUrl = authorizationUrl.replace('<resource>', resource);
  return authorizationUrl;
}

// Clients get redirected here in order to create an OAuth authorize url and redirect them to AAD.
// There they will authenticate and give their consent to allow this app access to
// some resource they own.
app.get('/auth', function(req, res) {
  crypto.randomBytes(48, function(ex, buf) {
    var token = buf.toString('base64').replace(/\//g,'_').replace(/\+/g,'-');

    res.cookie('authstate', token);
    var authorizationUrl = createAuthorizationUrl(token);

    res.redirect(authorizationUrl);
  });
});

// After consent is granted AAD redirects here.  The ADAL library is invoked via the
// AuthenticationContext and retrieves an access token that can be used to access the
// user owned resource.
app.get('/getAToken', function(req, res) {
  if (req.cookies.authstate !== req.query.state) {
    res.send('error: state does not match');
  }
  var authenticationContext = new AuthenticationContext(authorityUrl);
  authenticationContext.acquireTokenWithAuthorizationCode(req.query.code, redirectUri, resource, sampleParameters.clientId, sampleParameters.clientSecret, function(err, response) {
    var message = '';
    if (err) {
      message = 'error: ' + err.message + '\n';
    }
    message += 'response: ' + JSON.stringify(response);

    if (err) {
      res.send(message);
      return;
    }

    // Later, if the access token is expired it can be refreshed.
    authenticationContext.acquireTokenWithRefreshToken(response.refreshToken, sampleParameters.clientId, sampleParameters.clientSecret, resource, function(refreshErr, refreshResponse) {
      if (refreshErr) {
        message += 'refreshError: ' + refreshErr.message + '\n';
      }
      message += 'refreshResponse: ' + JSON.stringify(refreshResponse);

      res.send(message); 
    }); 
  });
});

If anyone could help me with some real app sample, video or something, will be very good. I'm losing my mind to figure it out.

Thanks!

like image 523
Rafael Grilli Avatar asked Nov 28 '16 00:11

Rafael Grilli


1 Answers

The passport's OIDCStrategy follows Authorize access to web applications using OpenID Connect and Azure Active Directory to authenticate & authorize users. And if you need the access for resources, you still need to complete the step of Use the authorization code to request an access token.

I add some code snippets based on the openid connection sample:

The return route function will get the use the code to acquire the access token and save into user seesion object.

app.post('/auth/openid/return',
  passport.authenticate('azuread-openidconnect', { failureRedirect: '/login' }),
  function (req, res) {
    let option = {
      method:'POST',
      uri:'https://login.microsoftonline.com/<tenant_id>/oauth2/token',
      headers:{
        'Content-Type':'application/x-www-form-urlencoded'
      },
      form:{
        grant_type:'authorization_code',
        client_id:'<client_id>',
        resource:'https://graph.windows.net',
        client_secret:'<secret_key>',
        code:req.body.code,
        redirect_uri:'http://localhost:3000/auth/openid/return'
      }
    }
    console.log(option);
    request(option,function(err,res,body){
      req.user.access_token = JSON.parse(body).access_token;
    })

    // log.info('We received a return from AzureAD.');
    res.redirect('/');
  });

The route '/me' is to sample to use access token to require resourece.

app.get('/me',ensureAuthenticated ,function(req,response){
  request.get("https://graph.windows.net/<tenant_id>/me?api-version=1.5", {
    'headers': {
      'Authorization': "Bearer " + req.user.access_token,
      'Content-Type': 'application/json'
    }
  }, function(err, res, body){
    if(err){
      console.log("err: " + err);
    }
    else{
      console.log("res: " + res);
      response.send(res);
    }
  });
});

Any further concern, please feel free to let me know.

like image 59
Gary Liu Avatar answered Oct 13 '22 00:10

Gary Liu