Friday, June 14, 2024

Azure AD B2C SSO to Microsoft Entra ID (AD) using OIDC - User Flow

 How to Setup SSO on Azure AD B2C to Azure AD Entra ID


Prerequesties: 

- Create Azure AD Entra ID Tenant

- Create Azure AD B2C Tenant

- Create Azure AD B2C App Registration. Ref: Balajiprasad's useful codes: Azure AD B2C App Registration (rbalajiprasad.blogspot.com)


On Azure AD Entra ID:

- Go to "Microsoft Entra ID", click "Enterprise Applications"

- Click "New Application" then click "Create your own application"

- Choose account type as Single Tenant

- Set Redirect URL as Web & URL to https:// {b2ctenantname}.b2clogin.com/{b2ctenantname}.onmicrosoft.com/oauth2/authresp  (Replace b2cTenantName)

- Go to "Certificates & Secrets" tab, create new client secret, give some unique name and expiration, store the secret for later purpose


On Azure AD B2C: 

- Go to "Azure AD B2C"

- Go to "Identity Provider", Click to "New OpenID Connect Provider"

- Enter the below details and save,

Name: {{SomeUniqueName}}

Metadata url: https://login.microsoftonline.com/{{ADtenantname}}.onmicrosoft.com/.well-known/openid-configuration

Client ID: {{EntraIDADAppRegistrationApplicationId}}

Client secret:  {{EntraIDADAppRegistrationSecret}}

Scope:  open

Response type: id_token

Response mode: form_post

Domain hint: {{SomeUniqueName}}

User ID: oid

Display name:  name

Given name:  given_name

Surname:  family_name

Email:  unique_name


- Go To "User Flows", Choose the SignIn or SignupSignIn flow then select the identity providers

- Now test the policy run flow or through Web Application, you can see the new Login Button for AD tenant added in Signin page

Azure AD B2C App Registration

 To Create Azure AD B2C App, please follow the below simple steps,


Prerequisites:

- You need login account for https://portal.azure.com/

- You need Directory to create Azure AD B2C Tenant  

- You need Azure AD B2C Tenant under Directory Created


 Steps to create Azure AD B2C App:

- Go to "App Registration" and Create "New Registration" 

  • Give some unique name
  • Choose option for "Account with any identity provider...."
  • Redirect URI can be Web & give your application URL (For ex: https://localhost:5000 and also add https://jwt.ms for testing)
  • Grant consent enabled
- Go to "Authentication" tab of registration page once created

  • Set both Access Token & ID token
  • Public client flow to No
- Go to "Certificates & Secret" tab

  •     Add new client secret with some name and store the secret details for later
- Go to "Api Permissions" tab

  • Client "Add Permission", select Microsoft Graph 
  • Add Delegated permission, all available
  • Add Application permission, Directory.read.all, Directory.readwrite.all, User.read.all, User.readwrite.all (include as required)
  • Grand Admin Consent checkbox for every permissions added, make sure all set to true


Login pages:

In previous steps, we created Azure AD B2C App registration to use in our web applications. But we need Login, Registration, Password Reset pages required to add. There are two ways we can do it, Microsoft providing by default pages to use or we can customize our pages. Ref: User flows and custom policies in Azure Active Directory B2C - Azure AD B2C | Microsoft Learn


Default Pages:  (User Flows)

- Using Microsoft provided default pages called User Flows, On Azure AD B2C page, you can see the tab "User Flow" to add these pages. 
- We can add different flows in here for SignIn, SignUp, Signup_SignIn, PasswordReset
- Any policy xml file name referred as B2C_1_{...} it is user flow policy



Customized Pages: (Identity Experience Framework)

- Using our customized pages called Identity Experience Framework or Custom policies. On Azure AD B2C page, you can see the tab "Identity Experience Framework" to add custom policies which is nothing but manually created xml policy files. Don't worry about xml files steps, all are provided in Microsoft starter pack to reuse.
- Any policy xml file name referred as B2C_1A_{...} it is custom policy


Test: 
- Once user flow or custom policies are added, you can click go and search for the SignUpSignin or SignIn policy file and click to "Run flow" then choose the app registered with one of URL to test
- These details we can configure in our Web Application as well. There are samples available to use these web application code. Ref: Web App Samples

Thursday, April 25, 2024

Html JS - Handle Browser Back Button on SPA

In Single page applications, we will not have dedicated URLs. So on click of Browser Back button it will navigate us to previous page and we lost our current page changes. We can handle this with native JavaScript functions,

Create Html Page as below to mimic SPA, 


On click of each Page button, it can switch the content,




On Each Page navigation we can set the hash URL something like below using history.pushstate or location.hash



Now on click of back button use the hashchange event to detect and open the page accordingly


Here is full code:

<html>

<head>

    <script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>

</head>

<body>

    <input type="button" value="Page1" onclick="openPage('1');" />

    <input type="button" value="Page2" onclick="openPage('2');" />

    <input type="button" value="Page3" onclick="openPage('3');" />

 

    <div id="page1" class="page">Page 1 Content</div>

    <div id="page2" class="page">Page 2 Content</div>

    <div id="page3" class="page">Page 3 Content</div>

 

 

    <div id="history"></div>

</body>

 

<script>

    $(document).ready(function () {

        hideAllPages();

     });

 

    $(window).on('popstate', (e) => {

        e.preventDefault();

        var currentLocation = window.location.pathname;

        history.push(currentLocation)

    });

     window.onbeforeunload = function () {

        return "Are you sure you want to leave this page?";

     };

    addEventListener("hashchange", (event) => {

        alert("New" + event.newURL);

        alert("Old" + event.oldURL);

        var currentHash = event.currentTarget.location.hash;

        openPage(currentHash.replace("#", ""), true);

     });

    function hideAllPages() {

        $(".page").hide();

    }

    function openPage(pageId, ignoreHistoryPush = false) {

        hideAllPages();

        $("#page" + pageId).show();

        if (!ignoreHistoryPush)  //This is to not add history on everytime

            history.pushState({ page: pageId }, pageId, '#' + pageId);
            //
location.hash = pageId  //This way also can set it

    }

 

 

</script>

</html>


Tuesday, February 20, 2024

SQL - Defined vs Dynamic table column values into JSON

 Convert Defined vs Dynamic table column values into JSON using SQL Syntax, using SQL Pivot and Subquery execution achieved to construct JSON,


IF OBJECT_ID('tempdb..#tempDefinedTable1') IS NOT NULL

       DROP TABLE #tempDefinedTable1

IF OBJECT_ID('tempdb..#tempDefinedTable2') IS NOT NULL

       DROP TABLE #tempDefinedTable2

IF OBJECT_ID('tempdb..#tempAllColumns') IS NOT NULL

       drop table #tempAllColumns

IF OBJECT_ID('tempdb..#tempMatchedColumns') IS NOT NULL

       drop table #tempMatchedColumns

IF OBJECT_ID('tempdb..#tempUnMatchedColumns') IS NOT NULL

       drop table #tempUnMatchedColumns

 


--create some defined column names as values

 create table #tempDefinedTable1 (

 definedcolnames varchar(max)

)

insert into #tempDefinedTable1

values ('col1'),('col2')

 

select * from #tempDefinedTable1

 

-- create table with defined columns + dynamic columns

create table #tempDefinedTable2 (

id int,

col1 varchar(max),

col2 varchar(max),

dynamicCol1 varchar(max),

dynamicCol2 varchar(max)

)

 

insert into #tempDefinedTable2

values (1, 'a1', 'b1', 'c1', 'd1'),(2, 'a2', 'b2', 'c2', 'd2')

 

select * from #tempDefinedTable2

  

--From the defined+dynamic column list, consolidate columns as values

Select C.* into #tempAllColumns

 From #tempDefinedTable2 A

 Cross Apply ( values (cast((Select A.* for XML RAW) as xml))) B(XMLData)

 Cross Apply (

                Select ColumnName  = a.value('local-name(.)','varchar(100)')

                      ,ColumnValue = a.value('.','varchar(max)')

                                     ,Id

                 From  B.XMLData.nodes('/row')  as C1(n)

                 Cross Apply C1.n.nodes('./@*') as C2(a)

                 Where a.value('local-name(.)','varchar(100)') not in ('ID','ExcludeOtherCol')

             ) C

 

select Id, ColumnName, ColumnValue from #tempAllColumns

 

--now preparing sql sub query to list defined columns in root level, dynamic columns as nested object

DECLARE @Columns as VARCHAR(MAX)

 

SELECT @Columns = COALESCE(@Columns + ', ','') + QUOTENAME([ColumnName])

FROM

    (SELECT DISTINCT ColumnName FROM #tempAllColumns st

       JOIN #tempDefinedTable1 tt on tt.definedcolnames = st.ColumnName

       WHERE tt.definedcolnames is not null) AS B

ORDER BY B.ColumnName

 

select @Columns as DefinedColumns

 

 

DECLARE @DynamicColumns as VARCHAR(MAX)

SELECT @DynamicColumns = COALESCE(@DynamicColumns + ', ','') + QUOTENAME([ColumnName])

FROM

    (SELECT DISTINCT ColumnName FROM #tempAllColumns st

       LEFT JOIN #tempDefinedTable1 tt on tt.definedcolnames = st.ColumnName

       WHERE tt.definedcolnames is null) AS B

ORDER BY B.ColumnName

select @DynamicColumns  as UndefinedColumns

 

DECLARE @SQL as VARCHAR(MAX)

SET @SQL = 'SELECT Id,' + @Columns + ',

       (SELECT PR1.Id,' + @DynamicColumns + ' FROM (select st1.Id, st1.[ColumnName], st1.ColumnValue from #tempAllColumns st1

        LEFT JOIN #tempDefinedTable1 tt1 on tt1.definedcolnames = st1.ColumnName

        WHERE tt1.definedcolnames is null) as PD2

        PIVOT (

          max(ColumnValue)

          FOR [ColumnName] IN (' + @DynamicColumns + ')

       ) AS PR1

       WHERE PR1.Id = PR.Id

       ORDER BY Id

       FOR JSON PATH, INCLUDE_NULL_VALUES) as ExtraFields

FROM

(

 select Id,[ColumnName],ColumnValue from #tempAllColumns st

 JOIN #tempDefinedTable1 tt on tt.definedcolnames = st.ColumnName

 WHERE tt.definedcolnames is not null

) as PD

PIVOT

(

   max(ColumnValue)

   FOR [ColumnName] IN (' + @Columns + ')

) AS PR

ORDER BY Id

FOR JSON PATH, INCLUDE_NULL_VALUES'

 

EXEC(@SQL);

 

Columns:


Output: