diff --git a/packages/console/src/ds-components/Textarea/index.module.scss b/packages/console/src/ds-components/Textarea/index.module.scss
index 3ae2622c9..3e872d142 100644
--- a/packages/console/src/ds-components/Textarea/index.module.scss
+++ b/packages/console/src/ds-components/Textarea/index.module.scss
@@ -39,3 +39,14 @@
     }
   }
 }
+
+.errorMessage {
+  font: var(--font-body-2);
+  color: var(--color-error);
+  margin-top: _.unit(1);
+
+  a {
+    color: var(--color-error);
+    text-decoration: underline;
+  }
+}
diff --git a/packages/console/src/ds-components/Textarea/index.tsx b/packages/console/src/ds-components/Textarea/index.tsx
index 01dfa80f9..7bc7b4163 100644
--- a/packages/console/src/ds-components/Textarea/index.tsx
+++ b/packages/console/src/ds-components/Textarea/index.tsx
@@ -14,9 +14,14 @@ function Textarea(
   reference: ForwardedRef<HTMLTextAreaElement>
 ) {
   return (
-    <div className={classNames(styles.container, Boolean(error) && styles.error, className)}>
-      <textarea {...rest} ref={reference} />
-    </div>
+    <>
+      <div className={classNames(styles.container, Boolean(error) && styles.error, className)}>
+        <textarea {...rest} ref={reference} />
+      </div>
+      {Boolean(error) && typeof error !== 'boolean' && (
+        <div className={styles.errorMessage}>{error}</div>
+      )}
+    </>
   );
 }
 
diff --git a/packages/console/src/pages/ApplicationDetails/SamlApplicationDetailsContent/Settings.tsx b/packages/console/src/pages/ApplicationDetails/SamlApplicationDetailsContent/Settings.tsx
index ea3b970e9..34cd27db8 100644
--- a/packages/console/src/pages/ApplicationDetails/SamlApplicationDetailsContent/Settings.tsx
+++ b/packages/console/src/pages/ApplicationDetails/SamlApplicationDetailsContent/Settings.tsx
@@ -1,7 +1,13 @@
-import { type SamlApplicationSecretResponse, type SamlApplicationResponse } from '@logto/schemas';
+/* eslint-disable max-lines */
+import { type AdminConsoleKey } from '@logto/phrases';
+import {
+  type SamlApplicationSecretResponse,
+  type SamlApplicationResponse,
+  NameIdFormat,
+} from '@logto/schemas';
 import { appendPath, type Nullable } from '@silverhand/essentials';
 import { useCallback, useContext, useMemo, useState } from 'react';
-import { useForm } from 'react-hook-form';
+import { Controller, useForm } from 'react-hook-form';
 import { toast } from 'react-hot-toast';
 import { useTranslation } from 'react-i18next';
 import useSWR, { type KeyedMutator } from 'swr';
@@ -15,8 +21,11 @@ import { AppDataContext } from '@/contexts/AppDataProvider';
 import Button from '@/ds-components/Button';
 import CopyToClipboard from '@/ds-components/CopyToClipboard';
 import FormField from '@/ds-components/FormField';
+import Select from '@/ds-components/Select';
+import Switch from '@/ds-components/Switch';
 import Table from '@/ds-components/Table';
 import TextInput from '@/ds-components/TextInput';
+import Textarea from '@/ds-components/Textarea';
 import useApi, { type RequestError } from '@/hooks/use-api';
 import useCustomDomain from '@/hooks/use-custom-domain';
 import { trySubmitSafe } from '@/utils/form';
@@ -32,15 +41,19 @@ import {
   samlApplicationManagementApiPrefix,
   samlApplicationMetadataEndpointSuffix,
   samlApplicationSingleSignOnEndpointSuffix,
+  validateCertificate,
 } from './utils';
 
 export type SamlApplicationFormData = Pick<
   SamlApplicationResponse,
-  'id' | 'description' | 'name' | 'entityId'
+  'id' | 'description' | 'name' | 'entityId' | 'nameIdFormat'
 > & {
   // Currently we only support HTTP-POST binding
   // Keep the acsUrl as a string in the form data instead of the object
   acsUrl: Nullable<string>;
+  encryptSamlAssertion: boolean;
+  encryptThenSignSamlAssertion: boolean;
+  certificate?: string;
 };
 
 type Props = {
@@ -49,6 +62,25 @@ type Props = {
   readonly isDeleted: boolean;
 };
 
+type NameIdFormatToTranslationKey = {
+  [key in NameIdFormat]: AdminConsoleKey;
+};
+
+const nameIdFormatToOptionMap = Object.freeze({
+  [NameIdFormat.EmailAddress]: 'application_details.saml_idp_name_id_format.email_address',
+  [NameIdFormat.Transient]: 'application_details.saml_idp_name_id_format.transient',
+  [NameIdFormat.Persistent]: 'application_details.saml_idp_name_id_format.persistent',
+  [NameIdFormat.Unspecified]: 'application_details.saml_idp_name_id_format.unspecified',
+}) satisfies NameIdFormatToTranslationKey;
+
+const nameIdFormatToOptionDescriptionMap = Object.freeze({
+  [NameIdFormat.EmailAddress]:
+    'application_details.saml_idp_name_id_format.email_address_description',
+  [NameIdFormat.Transient]: 'application_details.saml_idp_name_id_format.transient_description',
+  [NameIdFormat.Persistent]: 'application_details.saml_idp_name_id_format.persistent_description',
+  [NameIdFormat.Unspecified]: 'application_details.saml_idp_name_id_format.unspecified_description',
+}) satisfies NameIdFormatToTranslationKey;
+
 function Settings({ data, mutateApplication, isDeleted }: Props) {
   const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
   const { tenantEndpoint } = useContext(AppDataContext);
@@ -60,6 +92,8 @@ function Settings({ data, mutateApplication, isDeleted }: Props) {
   );
 
   const {
+    watch,
+    control,
     register,
     handleSubmit,
     reset,
@@ -280,6 +314,75 @@ function Settings({ data, mutateApplication, isDeleted }: Props) {
               }}
             />
           </FormField>
+          <FormField title="application_details.saml_idp_name_id_format.title">
+            <Controller
+              name="nameIdFormat"
+              control={control}
+              render={({ field: { onChange, value } }) => (
+                <Select
+                  options={Object.values(NameIdFormat).map((format) => ({
+                    value: format,
+                    title: (
+                      <span>
+                        {t(nameIdFormatToOptionMap[format])}
+                        <span className={styles.nameIdFormatDescription}>
+                          ({t(nameIdFormatToOptionDescriptionMap[format])})
+                        </span>
+                      </span>
+                    ),
+                  }))}
+                  value={value}
+                  onChange={onChange}
+                />
+              )}
+            />
+          </FormField>
+          <FormField title="application_details.saml_encryption_config.encrypt_assertion">
+            <Switch
+              label={t('application_details.saml_encryption_config.encrypt_assertion_description')}
+              {...register('encryptSamlAssertion')}
+            />
+          </FormField>
+          {watch('encryptSamlAssertion') && (
+            <>
+              <FormField title="application_details.saml_encryption_config.encrypt_then_sign">
+                <Switch
+                  label={t(
+                    'application_details.saml_encryption_config.encrypt_then_sign_description'
+                  )}
+                  {...register('encryptThenSignSamlAssertion')}
+                />
+              </FormField>
+              <FormField
+                title="application_details.saml_encryption_config.certificate"
+                tip={t('application_details.saml_encryption_config.certificate_tooltip')}
+              >
+                <Textarea
+                  rows={5}
+                  error={errors.certificate?.message}
+                  {...register('certificate', {
+                    validate: (value) => {
+                      if (!value) {
+                        return t(
+                          'application_details.saml_encryption_config.certificate_missing_error'
+                        );
+                      }
+
+                      return (
+                        validateCertificate(value) ||
+                        t(
+                          'application_details.saml_encryption_config.certificate_invalid_format_error'
+                        )
+                      );
+                    },
+                  })}
+                  placeholder={t(
+                    'application_details.saml_encryption_config.certificate_placeholder'
+                  )}
+                />
+              </FormField>
+            </>
+          )}
         </FormCard>
       </DetailsForm>
       <UnsavedChangesAlertModal hasUnsavedChanges={!isDeleted && isDirty} onConfirm={reset} />
@@ -288,3 +391,4 @@ function Settings({ data, mutateApplication, isDeleted }: Props) {
 }
 
 export default Settings;
+/* eslint-enable max-lines */
diff --git a/packages/console/src/pages/ApplicationDetails/SamlApplicationDetailsContent/index.module.scss b/packages/console/src/pages/ApplicationDetails/SamlApplicationDetailsContent/index.module.scss
index c256f2441..e52a22584 100644
--- a/packages/console/src/pages/ApplicationDetails/SamlApplicationDetailsContent/index.module.scss
+++ b/packages/console/src/pages/ApplicationDetails/SamlApplicationDetailsContent/index.module.scss
@@ -40,3 +40,8 @@
 button.add {
   margin-top: _.unit(2);
 }
+
+.nameIdFormatDescription {
+  margin-inline-start: _.unit(2);
+  color: var(--color-text-secondary);
+}
diff --git a/packages/console/src/pages/ApplicationDetails/SamlApplicationDetailsContent/utils.ts b/packages/console/src/pages/ApplicationDetails/SamlApplicationDetailsContent/utils.ts
index d9b8bf58c..4703d73cf 100644
--- a/packages/console/src/pages/ApplicationDetails/SamlApplicationDetailsContent/utils.ts
+++ b/packages/console/src/pages/ApplicationDetails/SamlApplicationDetailsContent/utils.ts
@@ -3,14 +3,14 @@ import {
   type PatchSamlApplication,
   type SamlApplicationResponse,
 } from '@logto/schemas';
-import { removeUndefinedKeys } from '@silverhand/essentials';
+import { cond, removeUndefinedKeys } from '@silverhand/essentials';
 
 import { type SamlApplicationFormData } from './Settings';
 
 export const parseSamlApplicationResponseToFormData = (
   data: SamlApplicationResponse
 ): SamlApplicationFormData => {
-  const { id, description, name, entityId, acsUrl } = data;
+  const { id, description, name, entityId, acsUrl, encryption, nameIdFormat } = data;
 
   return {
     id,
@@ -18,6 +18,10 @@ export const parseSamlApplicationResponseToFormData = (
     name,
     entityId,
     acsUrl: acsUrl?.url ?? null,
+    nameIdFormat,
+    encryptSamlAssertion: encryption?.encryptAssertion ?? false,
+    encryptThenSignSamlAssertion: encryption?.encryptThenSign ?? false,
+    certificate: encryption?.certificate,
   };
 };
 
@@ -27,7 +31,17 @@ export const parseFormDataToSamlApplicationRequest = (
   id: string;
   payload: PatchSamlApplication;
 } => {
-  const { id, description, name, entityId, acsUrl } = data;
+  const {
+    id,
+    description,
+    name,
+    entityId,
+    acsUrl,
+    encryptSamlAssertion,
+    encryptThenSignSamlAssertion,
+    certificate,
+    nameIdFormat,
+  } = data;
 
   // If acsUrl value is empty string, it should be removed. Convert it to null.
   const acsUrlData = acsUrl ? { url: acsUrl, binding: BindingType.Post } : null;
@@ -39,6 +53,17 @@ export const parseFormDataToSamlApplicationRequest = (
       name,
       entityId,
       acsUrl: acsUrlData,
+      nameIdFormat,
+      ...cond(
+        encryptSamlAssertion &&
+          certificate && {
+            certificate: {
+              encryptAssertion: encryptSamlAssertion,
+              certificate,
+              encryptThenSign: encryptThenSignSamlAssertion,
+            },
+          }
+      ),
     }),
   };
 };
@@ -62,3 +87,34 @@ export const camelCaseToSentenceCase = (input: string): string => {
   const capitalizedFirstWord = words[0].charAt(0).toUpperCase() + words[0].slice(1);
   return [capitalizedFirstWord, ...words.slice(1)].join(' ');
 };
+
+export const validateCertificate = (certificate: string) => {
+  // Remove any whitespace and newline characters for consistent validation
+  const normalizedCert = certificate.replaceAll(/\s/g, '');
+
+  // Check if the certificate starts with the header and ends with the footer
+  if (
+    !normalizedCert.startsWith('-----BEGINCERTIFICATE-----') ||
+    !normalizedCert.endsWith('-----ENDCERTIFICATE-----')
+  ) {
+    return false;
+  }
+
+  // Extract the base64 content between the header and footer
+  const base64Content = normalizedCert
+    .replace('-----BEGINCERTIFICATE-----', '')
+    .replace('-----ENDCERTIFICATE-----', '');
+
+  // Check if the content is valid base64
+  try {
+    if (base64Content.length % 4 !== 0) {
+      return false;
+    }
+    if (!/^[\d+/A-Za-z]*={0,2}$/.test(base64Content)) {
+      return false;
+    }
+    return true;
+  } catch {
+    return false;
+  }
+};
diff --git a/packages/core/src/saml-applications/SamlApplication/index.test.ts b/packages/core/src/saml-applications/SamlApplication/index.test.ts
index 14fccc29e..cdfa18b1e 100644
--- a/packages/core/src/saml-applications/SamlApplication/index.test.ts
+++ b/packages/core/src/saml-applications/SamlApplication/index.test.ts
@@ -1,3 +1,4 @@
+import { NameIdFormat } from '@logto/schemas';
 import nock from 'nock';
 
 import { SamlApplication } from './index.js';
@@ -25,6 +26,7 @@ describe('SamlApplication', () => {
     privateKey: 'mock-private-key',
     certificate: 'mock-certificate',
     secret: 'mock-secret',
+    nameIdFormat: NameIdFormat.Persistent,
   };
 
   const mockUser = {
diff --git a/packages/core/src/saml-applications/SamlApplication/index.ts b/packages/core/src/saml-applications/SamlApplication/index.ts
index 6961920df..4caaa333b 100644
--- a/packages/core/src/saml-applications/SamlApplication/index.ts
+++ b/packages/core/src/saml-applications/SamlApplication/index.ts
@@ -2,9 +2,14 @@
 // TODO: refactor this file to reduce LOC
 import { parseJson } from '@logto/connector-kit';
 import { Prompt, QueryKey, ReservedScope, UserScope } from '@logto/js';
-import { type SamlAcsUrl, BindingType } from '@logto/schemas';
+import {
+  type SamlAcsUrl,
+  BindingType,
+  type NameIdFormat,
+  type SamlEncryption,
+} from '@logto/schemas';
 import { generateStandardId } from '@logto/shared';
-import { tryThat, appendPath, deduplicate } from '@silverhand/essentials';
+import { tryThat, appendPath, deduplicate, type Nullable, cond } from '@silverhand/essentials';
 import camelcaseKeys, { type CamelCaseKeys } from 'camelcase-keys';
 import { XMLValidator } from 'fast-xml-parser';
 import saml from 'samlify';
@@ -41,6 +46,23 @@ type ValidSamlApplicationDetails = {
   redirectUri: string;
   privateKey: string;
   certificate: string;
+  nameIdFormat: NameIdFormat;
+  encryption: Nullable<SamlEncryption>;
+};
+
+type SamlIdentityProviderConfig = {
+  entityId: string;
+  certificate: string;
+  singleSignOnUrl: string;
+  privateKey: string;
+  nameIdFormat: NameIdFormat;
+  encryptSamlAssertion: boolean;
+};
+
+type SamlServiceProviderConfig = {
+  entityId: string;
+  acsUrl: SamlAcsUrl;
+  certificate?: string;
 };
 
 // Used to check whether xml content is valid in format.
@@ -68,6 +90,8 @@ const validateSamlApplicationDetails = (
     privateKey,
     certificate,
     secret,
+    nameIdFormat,
+    encryption,
   } = details;
 
   assertThat(acsUrl, 'application.saml.acs_url_required');
@@ -84,6 +108,8 @@ const validateSamlApplicationDetails = (
     redirectUri: redirectUris[0],
     privateKey,
     certificate,
+    nameIdFormat,
+    encryption,
   };
 };
 
@@ -112,12 +138,9 @@ const buildSamlIdentityProvider = ({
   certificate,
   singleSignOnUrl,
   privateKey,
-}: {
-  entityId: string;
-  certificate: string;
-  singleSignOnUrl: string;
-  privateKey: string;
-}): saml.IdentityProviderInstance => {
+  nameIdFormat,
+  encryptSamlAssertion,
+}: SamlIdentityProviderConfig): saml.IdentityProviderInstance => {
   // eslint-disable-next-line new-cap
   return saml.IdentityProvider({
     entityID: entityId,
@@ -133,12 +156,9 @@ const buildSamlIdentityProvider = ({
       },
     ],
     privateKey,
-    isAssertionEncrypted: false,
+    isAssertionEncrypted: encryptSamlAssertion,
     loginResponseTemplate: buildLoginResponseTemplate(),
-    nameIDFormat: [
-      saml.Constants.namespace.format.emailAddress,
-      saml.Constants.namespace.format.persistent,
-    ],
+    nameIDFormat: [nameIdFormat],
   });
 };
 
@@ -193,10 +213,12 @@ export class SamlApplication {
   }
 
   public get sp(): saml.ServiceProviderInstance {
+    const { certificate: encryptCert, ...rest } = this.buildSpConfig();
     this._sp ||= buildSamlServiceProvider({
-      ...this.buildSpConfig(),
+      ...rest,
       certificate: this.details.certificate,
       isWantAuthnRequestsSigned: this.idp.entityMeta.isWantAuthnRequestsSigned(),
+      ...cond(encryptCert && { encryptCert }),
     });
     return this._sp;
   }
@@ -234,7 +256,8 @@ export class SamlApplication {
       null,
       'post',
       userInfo,
-      this.createSamlTemplateCallback(userInfo)
+      this.createSamlTemplateCallback(userInfo),
+      this.details.encryption?.encryptThenSign
     );
 
     // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
@@ -360,6 +383,7 @@ export class SamlApplication {
       );
 
       const { nameIDFormat } = this.idp.entitySetting;
+      assertThat(nameIDFormat, 'application.saml.name_id_format_required');
       const { NameIDFormat, NameID } = buildSamlAssertionNameId(user, nameIDFormat);
 
       const id = `ID_${generateStandardId()}`;
@@ -406,19 +430,22 @@ export class SamlApplication {
       };
     };
 
-  private buildIdpConfig() {
+  private buildIdpConfig(): SamlIdentityProviderConfig {
     return {
       entityId: buildSamlIdentityProviderEntityId(this.tenantEndpoint, this.samlApplicationId),
       privateKey: this.details.privateKey,
       certificate: this.details.certificate,
       singleSignOnUrl: buildSingleSignOnUrl(this.tenantEndpoint, this.samlApplicationId),
+      nameIdFormat: this.details.nameIdFormat,
+      encryptSamlAssertion: this.details.encryption?.encryptAssertion ?? false,
     };
   }
 
-  private buildSpConfig() {
+  private buildSpConfig(): SamlServiceProviderConfig {
     return {
       entityId: this.details.entityId,
       acsUrl: this.details.acsUrl,
+      certificate: this.details.encryption?.certificate,
     };
   }
 }
diff --git a/packages/core/src/saml-applications/SamlApplication/utils.test.ts b/packages/core/src/saml-applications/SamlApplication/utils.test.ts
index ddcb12dbd..95417ef31 100644
--- a/packages/core/src/saml-applications/SamlApplication/utils.test.ts
+++ b/packages/core/src/saml-applications/SamlApplication/utils.test.ts
@@ -1,3 +1,5 @@
+import { NameIdFormat } from '@logto/schemas';
+
 import { generateAutoSubmitForm, buildSamlAssertionNameId } from './utils.js';
 
 describe('buildSamlAssertionNameId', () => {
@@ -8,7 +10,7 @@ describe('buildSamlAssertionNameId', () => {
       email_verified: true,
     };
 
-    const result = buildSamlAssertionNameId(user);
+    const result = buildSamlAssertionNameId(user, [NameIdFormat.EmailAddress]);
 
     expect(result).toEqual({
       NameIDFormat: 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
@@ -23,7 +25,7 @@ describe('buildSamlAssertionNameId', () => {
       email_verified: false,
     };
 
-    const result = buildSamlAssertionNameId(user);
+    const result = buildSamlAssertionNameId(user, [NameIdFormat.Persistent]);
 
     expect(result).toEqual({
       NameIDFormat: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
@@ -36,7 +38,7 @@ describe('buildSamlAssertionNameId', () => {
       sub: 'user123',
     };
 
-    const result = buildSamlAssertionNameId(user);
+    const result = buildSamlAssertionNameId(user, [NameIdFormat.Persistent]);
 
     expect(result).toEqual({
       NameIDFormat: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
@@ -52,7 +54,7 @@ describe('buildSamlAssertionNameId', () => {
     };
     const format = 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent';
 
-    const result = buildSamlAssertionNameId(user, format);
+    const result = buildSamlAssertionNameId(user, [format]);
 
     expect(result).toEqual({
       NameIDFormat: format,
diff --git a/packages/core/src/saml-applications/SamlApplication/utils.ts b/packages/core/src/saml-applications/SamlApplication/utils.ts
index 9980b0331..02a2aafdc 100644
--- a/packages/core/src/saml-applications/SamlApplication/utils.ts
+++ b/packages/core/src/saml-applications/SamlApplication/utils.ts
@@ -1,7 +1,9 @@
-// TODO: refactor this file to reduce LOC
-import saml from 'samlify';
+import { NameIdFormat } from '@logto/schemas';
+import { generateStandardId } from '@logto/shared';
 
+import RequestError from '#src/errors/RequestError/index.js';
 import { type IdTokenProfileStandardClaims } from '#src/sso/types/oidc.js';
+import assertThat from '#src/utils/assert-that.js';
 
 /**
  * Determines the SAML NameID format and value based on the user's claims and IdP's NameID format.
@@ -13,43 +15,41 @@ import { type IdTokenProfileStandardClaims } from '#src/sso/types/oidc.js';
  */
 export const buildSamlAssertionNameId = (
   user: IdTokenProfileStandardClaims,
-  idpNameIDFormat?: string | string[]
+  idpNameIDFormat: string[]
 ): { NameIDFormat: string; NameID: string } => {
-  if (idpNameIDFormat) {
-    // Get the first name ID format
-    const format = Array.isArray(idpNameIDFormat) ? idpNameIDFormat[0] : idpNameIDFormat;
-    // If email format is specified, try to use email first
-    if (
-      format === saml.Constants.namespace.format.emailAddress &&
-      user.email &&
-      user.email_verified
-    ) {
-      return {
-        NameIDFormat: format,
-        NameID: user.email,
-      };
-    }
-    // For other formats or when email is not available, use sub
-    if (format === saml.Constants.namespace.format.persistent) {
-      return {
-        NameIDFormat: format,
-        NameID: user.sub,
-      };
-    }
-  }
-  // No nameIDFormat specified, use default logic
-  // Use email if available
-  if (user.email && user.email_verified) {
+  // Get the first name ID format
+  const format = Array.isArray(idpNameIDFormat) ? idpNameIDFormat[0] : idpNameIDFormat;
+
+  // If email format is specified, try to use email first
+  if (format === NameIdFormat.EmailAddress) {
+    assertThat(user.email, 'application.saml.missing_email_address');
+    assertThat(user.email_verified, 'application.saml.email_address_unverified');
     return {
-      NameIDFormat: saml.Constants.namespace.format.emailAddress,
+      NameIDFormat: format,
       NameID: user.email,
     };
   }
-  // Fallback to persistent format with user.sub
-  return {
-    NameIDFormat: saml.Constants.namespace.format.persistent,
-    NameID: user.sub,
-  };
+
+  // For persistent and unspecified formats, we use Logto user ID.
+  if (format === NameIdFormat.Persistent || format === NameIdFormat.Unspecified) {
+    return {
+      NameIDFormat: format,
+      NameID: user.sub,
+    };
+  }
+
+  // For transient format, we generate a random ID.
+  if (format === NameIdFormat.Transient) {
+    return {
+      NameIDFormat: format,
+      NameID: generateStandardId(),
+    };
+  }
+
+  throw new RequestError({
+    code: 'application.saml.unsupported_name_id_format',
+    details: { idpNameIDFormat, user },
+  });
 };
 
 export const generateAutoSubmitForm = (actionUrl: string, samlResponse: string): string => {
diff --git a/packages/core/src/saml-applications/queries/index.ts b/packages/core/src/saml-applications/queries/index.ts
index 2ad6039f1..df9f84c95 100644
--- a/packages/core/src/saml-applications/queries/index.ts
+++ b/packages/core/src/saml-applications/queries/index.ts
@@ -34,7 +34,10 @@ export type SamlApplicationDetails = Pick<
   Application,
   'id' | 'secret' | 'name' | 'description' | 'customData' | 'oidcClientMetadata'
 > &
-  Pick<SamlApplicationConfig, 'attributeMapping' | 'entityId' | 'acsUrl'> &
+  Pick<
+    SamlApplicationConfig,
+    'attributeMapping' | 'entityId' | 'acsUrl' | 'encryption' | 'nameIdFormat'
+  > &
   NullableObject<SamlApplicationSecretDetails>;
 
 const samlApplicationDetailsGuard = Applications.guard
@@ -51,6 +54,8 @@ const samlApplicationDetailsGuard = Applications.guard
       attributeMapping: true,
       entityId: true,
       acsUrl: true,
+      nameIdFormat: true,
+      encryption: true,
     })
   )
   .merge(
@@ -66,7 +71,7 @@ const samlApplicationDetailsGuard = Applications.guard
 export const createSamlApplicationQueries = (pool: CommonQueryMethods) => {
   const getSamlApplicationDetailsById = async (id: string): Promise<SamlApplicationDetails> => {
     const result = await pool.one(sql`
-      select ${fields.id} as id, ${fields.secret} as secret, ${fields.name} as name, ${fields.description} as description, ${fields.customData} as custom_data, ${fields.oidcClientMetadata} as oidc_client_metadata, ${samlApplicationConfigsFields.attributeMapping} as attribute_mapping, ${samlApplicationConfigsFields.entityId} as entity_id, ${samlApplicationConfigsFields.acsUrl} as acs_url, ${samlApplicationSecretsFields.privateKey} as private_key, ${samlApplicationSecretsFields.certificate} as certificate, ${samlApplicationSecretsFields.active} as active, ${samlApplicationSecretsFields.expiresAt} as expires_at
+      select ${fields.id} as id, ${fields.secret} as secret, ${fields.name} as name, ${fields.description} as description, ${fields.customData} as custom_data, ${fields.oidcClientMetadata} as oidc_client_metadata, ${samlApplicationConfigsFields.attributeMapping} as attribute_mapping, ${samlApplicationConfigsFields.entityId} as entity_id, ${samlApplicationConfigsFields.acsUrl} as acs_url, ${samlApplicationConfigsFields.encryption} as encryption, ${samlApplicationConfigsFields.nameIdFormat} as name_id_format, ${samlApplicationSecretsFields.privateKey} as private_key, ${samlApplicationSecretsFields.certificate} as certificate, ${samlApplicationSecretsFields.active} as active, ${samlApplicationSecretsFields.expiresAt} as expires_at
       from ${table}
       left join ${samlApplicationConfigsTable} on ${fields.id}=${samlApplicationConfigsFields.applicationId}
       left join ${samlApplicationSecretsTable} on ${fields.id}=${samlApplicationSecretsFields.applicationId}
diff --git a/packages/phrases/src/locales/en/errors/application.ts b/packages/phrases/src/locales/en/errors/application.ts
index 89b692332..d052df379 100644
--- a/packages/phrases/src/locales/en/errors/application.ts
+++ b/packages/phrases/src/locales/en/errors/application.ts
@@ -28,6 +28,10 @@ const application = {
     can_not_delete_active_secret: 'Can not delete the active secret.',
     no_active_secret: 'No active secret found.',
     entity_id_required: 'Entity ID is required to generate metadata.',
+    name_id_format_required: 'Name ID format is required.',
+    unsupported_name_id_format: 'Unsupported name ID format.',
+    missing_email_address: 'User does not have an email address.',
+    email_address_unverified: 'User email address is not verified.',
     invalid_certificate_pem_format: 'Invalid PEM certificate format',
     acs_url_required: 'Assertion Consumer Service URL is required.',
     private_key_required: 'Private key is required.',
diff --git a/packages/phrases/src/locales/en/translation/admin-console/application-details.ts b/packages/phrases/src/locales/en/translation/admin-console/application-details.ts
index 3c7a144f9..ebfaada8b 100644
--- a/packages/phrases/src/locales/en/translation/admin-console/application-details.ts
+++ b/packages/phrases/src/locales/en/translation/admin-console/application-details.ts
@@ -219,6 +219,33 @@ const application_details = {
     active: 'Active',
     inactive: 'Inactive',
   },
+  saml_idp_name_id_format: {
+    title: 'Name ID format',
+    description: 'Select the name ID format of the SAML IdP.',
+    persistent: 'Persistent',
+    persistent_description: 'Use Logto user ID as Name ID',
+    transient: 'Transient',
+    transient_description: 'Use one-time user ID as Name ID',
+    unspecified: 'Unspecified',
+    unspecified_description: 'Use Logto user ID as Name ID',
+    email_address: 'Email address',
+    email_address_description: 'Use email address as Name ID',
+  },
+  saml_encryption_config: {
+    encrypt_assertion: 'Encrypt SAML assertion',
+    encrypt_assertion_description: 'By enabling this option, the SAML assertion will be encrypted.',
+    encrypt_then_sign: 'Encrypt then sign',
+    encrypt_then_sign_description:
+      'By enabling this option, the SAML assertion will be encrypted and then signed; otherwise, the SAML assertion will be signed and then encrypted.',
+    certificate: 'Certificate',
+    certificate_tooltip:
+      'Copy and paste the x509 certificate you get from your service provider to encrypt the SAML assertion.',
+    certificate_placeholder:
+      '-----BEGIN CERTIFICATE-----\nMIICYDCCAcmgAwIBA...\n-----END CERTIFICATE-----\n',
+    certificate_missing_error: 'Certificate is required.',
+    certificate_invalid_format_error:
+      'Invalid certificate format detected. Please check the certificate format and try again.',
+  },
   saml_app_attribute_mapping: {
     name: 'Attribute mappings',
     title: 'Base attribute mappings',