Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
@attribute [Authorize]

@* This route is disabled by default. Uncomment and enable it only if necessary,
as it may expose authentication-related endpoints. *@
@*
@attribute [Route(Urls.Authorize)]
@attribute [Route("{culture?}" + Urls.Authorize)]
*@

@inherits AppComponentBase

<LoadingComponent />
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
namespace Boilerplate.Client.Core.Components.Pages;

/// <summary>
/// If you need an authentication process similar to SSO/OAuth, navigate to /authorize with `client_id`,
/// an appropriately encoded `redirect_uri`, and `state`.
///
/// The user can sign in (or sign up if necessary) using various authentication methods provided by project template,
/// such as social login, 2FA, magic link, and OTP. After authentication, the system redirects to the specified app
/// with an access token and other relevant authentication details.
///
/// Example Usage:
/// Opening:
/// http://localhost:5030/authorize?client_id=SampleClient&redirect_uri=https://sampleclient.azurewebsites.net/loginCallback&state=/carts
/// http://localhost:5030/authorize?client_id=NopClient&redirect_uri=https%3A%2F%2Fsampleclient.azurewebsites.net%2FLoginCallback&state=%2Fcarts
///
/// Redirects to:
/// https://sampleclient.azurewebsites.net/loginCallback?access_token=di1d98cxh913fh29ufhnfunxw9&token_type=Bearer&expires_in=3600&state=/carts
///
/// Note:
/// This route is **disabled by default** for security reasons.
/// To enable it, **uncomment the route definition in the corresponding Razor page** (`@attribute [Route(Urls.Authorize)]`)
/// </summary>
public partial class Authorize
{
[AutoInject] private AuthManager authManager = default!;
[AutoInject] private IAuthTokenProvider authTokenProvider = default!;

[Parameter, SupplyParameterFromQuery(Name = "client_id")] public string? ClientId { get; set; }
[Parameter, SupplyParameterFromQuery(Name = "redirect_uri")] public string? RedirectUri { get; set; }
[Parameter, SupplyParameterFromQuery(Name = "state")] public string? State { get; set; }

private Dictionary<string, string[]> clients = new(StringComparer.OrdinalIgnoreCase) // Client configurations; can also be fetched from a server API.
{
{
"SampleClient",
[
"https://sampleclient.azurewebsites.net/loginCallback"
]
}
};

protected override async Task OnAfterFirstRenderAsync()
{
if (clients.TryGetValue(ClientId!, out var clientAllowedRedirectUrls) is false)
{
NavigationManager.NavigateTo($"{RedirectUri}#error=Invalid or missing client_id&state={Uri.EscapeDataString(State ?? "")}");
return;
}

if (clientAllowedRedirectUrls.Any(clientUrl => string.Equals(clientUrl, RedirectUri, StringComparison.InvariantCultureIgnoreCase)) is false)
{
NavigationManager.NavigateTo($"{RedirectUri}#error=Invalid redirect uri&state={Uri.EscapeDataString(State ?? "")}");
return;
}

// Attempt to refresh the user's authentication token.
var accessToken = await authManager.RefreshToken(requestedBy: "AuthorizePage");

if (string.IsNullOrEmpty(accessToken))
return; // If the token is expired, the session is deleted, or the user is logged out, redirecting back to sign-in will occur.

// Parse the access token and calculate its expiration duration.
var token = IAuthTokenProvider.ParseAccessToken(accessToken, validateExpiry: false);
var expiresIn = long.Parse(token.FindFirst("exp")!.Value) - long.Parse(token.FindFirst("iat")!.Value);

NavigationManager.NavigateTo($"{RedirectUri}?access_token={accessToken}&token_type=Bearer&expires_in={expiresIn}&state={Uri.EscapeDataString(State ?? "")}");

await base.OnAfterFirstRenderAsync();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ namespace Boilerplate.Client.Maui.Platforms.Android;
DataPaths = [Urls.HomePage],
DataPathPrefixes = [
"/en-US", "/en-GB", "/fa-IR", "/nl-NL",
Urls.ConfirmPage, Urls.ForgotPasswordPage, Urls.SettingsPage, Urls.ResetPasswordPage, Urls.SignInPage, Urls.SignUpPage, Urls.NotAuthorizedPage, Urls.NotFoundPage, Urls.TermsPage, Urls.AboutPage,
Urls.ConfirmPage, Urls.ForgotPasswordPage, Urls.SettingsPage, Urls.ResetPasswordPage, Urls.SignInPage,
Urls.SignUpPage, Urls.NotAuthorizedPage, Urls.NotFoundPage, Urls.TermsPage, Urls.AboutPage, Urls.Authorize,
//#if (module == "Admin")
Urls.AddOrEditCategoryPage, Urls.CategoriesPage, Urls.DashboardPage, Urls.ProductsPage,
//#endif
Expand Down
2 changes: 2 additions & 0 deletions src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Urls.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public static partial class Urls
public const string ProductPage = "/product";
//#endif

public const string Authorize = "/authorize";

public static readonly string[] All = typeof(Urls).GetFields()
.Where(f => f.FieldType == typeof(string))
.Select(f => f.GetValue(null)!.ToString()!)
Expand Down
Loading