2

[This question relates to ASP.NET MVC4, and it is about best-practice approach - so please, don't suggest hacks.]

I want to authenticate users using an auth token sent in the request URL. It works similarly to a password reset token, except in this case it does not go to a reset page but instead grants access to some portion of the site. The idea is to send the URL with the auth token to a verified email address of the user. Users can click the link and perform some actions without typing their password.

Out-of-the-box, ASP.NET has the [Authorize] attribute and the SimpleMembershipProvider - these seem to work great, but they do some voodoo magic under the hood (like auto-generating database tables), so I don't know how to extend them to add this link-based auth token.

I don't expect an exact answer, but please do point me to the right direction.

Thanks!

0

3 Answers 3

3

Uf, broad question. But I will try at least to direct you to a right direction.

So first if suggest that you use Forms Authentication as a base, but you will have to customize using of it. And I presume that you do not want to use cookies for the authentication as this is native behaviour of the Forms Authentication.

The most important point you should consider to have it you custom query string token based authentication.

  1. Create a login action and in this action you will authorize the user, if he have granted access you ask FormsAuthentication to create AuthCookie. For the further on you just take the httpCookie.Value as your auth token that you will carry in query string.

  2. You need to implement the Application_BeginRequest in the Global.asax that will handle this query string tokens and translate it into the cookie. With this approach you can leverage all the ASP.NET Forms Authentication infrastructure.

This is quite high level picture w/o code. If you need more detail help I can also provide it to you.

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks - that's good advice to follow Forms wherever possible. How can I distinguish between a genuine forms auth and link-based auth? The link-based auth should not give the same privileges as the forms-auth - for example, with link-based auth users should not be able to change their password or email address.
You can distinguish this easily. In the Application_BeginRequest, whey you will translate the query string to the cookie, you can decrypt cookie put a custom data (in your case flag if the user auth is from query string) into the ticked, and then you can read this where do you handle user auth (action filter for example). More on custom auth data: danharman.net/2011/07/07/…
Ah... interesting. I'll try that.
1

You should just use a regular Action that accepts HttpGet. Upon receiving the token, immediately invalid it so it can't be used again. Also, only accept tokens that are within your pre-defined range of time period, like 24 or 72 hours.

2 Comments

I don't want to handle authentication in the controller. There are some issues with that when you use output caching. On another point - the token needs to be persistent - that's OK though - it only grants limited access to the website (for example, user cannot change a password or email with just this token)
You can have two handlers then. One for checking on the token (not output cached) and once token is validated, redirect to another action (output cached).
0

Thank you Peter for idea.

If smb need to create JWT token authorization for old ASP.NET MVC5.I wrote small example. I don't serialize cookie to JWT. I create a JWT and after I am checking it in the BeginRequest. If everything is ok, I create a cookie and set it to the httpContext.Request. I used authentication mode="Forms" for application and it require cookies.

For create JWT token:

const string secret = "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk";
[AllowAnonymous]
[HttpPost]
public ActionResult LoginJWT(LoginViewModel model)
{
    ActionResult response = null;
    if (ModelState.IsValid)
    {
        if (true) //todo: check user login&password
        {
            var payload = new Dictionary<string, object>
            {
                { "iss", "subject" },
                { "sub", "api" },
                { "exp", DateTimeOffset.UtcNow.AddHours(2).ToUnixTimeSeconds()},
                { "iat",  DateTimeOffset.UtcNow.ToUnixTimeSeconds()},
                { "jti", Guid.NewGuid() },
                { "uid", "64" } //custom field for identificate user
            };

            IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); // symmetric
            IJsonSerializer serializer = new JsonNetSerializer();
            IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
            IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);

            var token = encoder.Encode(payload, secret);
            response = Content(token);
        }
        else
        {
            response = new HttpStatusCodeResult(System.Net.HttpStatusCode.BadRequest, "Login or password are not found");
        }
    }
    else
    {
        response = new HttpStatusCodeResult(System.Net.HttpStatusCode.BadRequest, "Errors in Model");
    }
    return response;
}

For check JWT token in Global.asax:

public override void Init()
        {
            this.BeginRequest += this.BeginRequestHandler;
            base.Init();
        }

private void BeginRequestHandler(object sender, EventArgs e)
        {
            var bearerToken = this.Context.Request.Headers["Authorization"];
            if (bearerToken != null)
            {
                var token = bearerToken.StartsWith("Bearer ") ? bearerToken.Substring(7) : bearerToken;
                const string secret = "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk";
                int userId = 0;
                try
                {
                    IJsonSerializer serializer = new JsonNetSerializer();
                    var provider = new UtcDateTimeProvider();
                    IJwtValidator validator = new JwtValidator(serializer, provider);
                    IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
                    IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); // symmetric
                    IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder, algorithm);

                    var json = decoder.DecodeToObject<IDictionary<string, string>>(token, secret, verify: true);

                    if (json.TryGetValue("uid", out var uid))
                    {
                        userId = Convert.ToInt32(uid);
                    }
                }
                catch (TokenExpiredException)
                {
                    Console.WriteLine("Token has expired");
                }
                catch (SignatureVerificationException)
                {
                    Console.WriteLine("Token has invalid signature");
                }
                if (userId != 0)
                {
                    // check user by id, if found create cookie.
                }
            }
        }

I used: jwt-dotnet/jwt library 7.2.1

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.