Setting up an authentication page

This page will describe the steps involved for setting up a page for authenticating with the Strava API using OAuth 2.0.

To get started, we need to initialize a new instance of the StravaOAuthClient class. This class is used for the authentication, as well as the raw communication with the Strava API once the user is authenticated. The class has properties which should be populated with information about your Strava app:

// Initialize a new OAuth client
StravaOAuthClient client = new StravaOAuthClient {
    ClientId = "Your client ID",
    ClientSecret = "Your client secret",
    RedirectUri = "Your redirect URI"
};

If you don't already know this information, but can go to the My API Application page at strava.com to either creater a new application or get the client ID and client secret for you existing application. The redirect URI is simply the URL of your authentication page (so Strava knows which URL it should redirect the user back to after a successful authentication).

Generating the authorization URL

Once we have an instance of StravaOAuthClient, we can use it go generate a new authorization. This is the URL you should redirect the user to in order for them to grant your application the requested access.

// Generate a random state
string state = Guid.NewGuid().ToString();

// Generate the authorization URL
string authorizationUrl = client.GetAuthorizationUrl(state, StravaApprovalPrompt.Force, StravaScopes.ActivityReadAll);

As part of OAuth 2.0, its important that you generate a unique state that is then passed on to the authorization page. When the user grants your application the requested access, Strava will redirect the user back to your redirect URI and then include the state in the query string. If you saved the state (e.g. in the user session) prior to redirecting the user to Strava's authorization page, you can use this to validate the user once thet are redirected back to your page. This step is designed to prevent against man in the middle attacks, so it is very important.

For the GetAuthorizationUrl method, we use the second parameter to specify that the user should grant your app the necessary permissions even if they have already done so before. Alternatively you can specify StravaApprovalPrompt.Auto instead so to user only has to grannt your application the necessary permissions if they haven't already done so before.

The third parameter should be one more more scopes, which is essentially the permissions that the user should grant your app. In this case, the user should grant the StravaScopes.ActivityReadAll scope, which is read permissions to both public and private activities of the authenticated user.

Obtaining an access token

The access token can be obtained using the StravaOAuthClient.GetAccessTokenFromAuthCodeAsync method, where the response body will expose the access token.

For the example below, the parameter for the method is the authorization code received when the user has successfully logged in through the Strava authorization dialog, and been redirected back to your site. At this point, the code parameter in the query string will contain the authorization code.

The code for exchanging the authorization code for the access token will look like:

// Make the request to the Strava API
StravaTokenResponse response = await client.GetAccessTokenFromAuthCodeAsync(code);
        
// Get the access token from the response body
string accessToken = response.Body.AccessToken;
        
// Get the refresh token from the response body
string refreshsToken = response.Body.RefreshToken;

You can then use the access token form making calls to the Strava API. It is however worth mentioning that the access token will expire within a number of days (currently seems to be in just under six days).

If you need to access the API beyond this time (or generally when the user isn't directly interacting with your app), you should use the refresh token instead. You can use the refresh token to generate new access token, which you then can use to access the API on behalf of the user.

Initializing an instance of FacebookService

Once you have obtained an access token, you can initialize a new instance of the StravaHttpService class as shown in the example below:

// Initialize a new service instance from an access token
StravaHttpService strava = StravaHttpService.CreateFromAccessToken(response.Body.AccessToken);

The StravaHttpService class serves as your starting point to making calls to the Strava API.

Complete example

In the example below, I've tried to demonstrate how an authentication page can be implemented (involving the steps explained above).

Notice that the example generates a state value that is saved to the user's session. When redirecting the user to Strava's authorization page, we supply the state, and once the user confirms (or cancels) the authorization, the same state is specified in the URL the user is redirected back to (at your site). We can then check whether the state is saved in the user session to make sure it's still the same user making the request.

@using Microsoft.AspNetCore.Http.Extensions
@using Skybrud.Social.Strava.OAuth
@using Skybrud.Social.Strava.Scopes
@using Skybrud.Social.Strava.Responses.Authentication
@using Skybrud.Social.Strava
@inherits UmbracoViewPage

@{

    // When the user is redirected back from Strava's login page following a successful login, the query string will
    // contain a "code" parameter with the authorization code
    string? code = Context.Request.Query["code"];

    // If you pass a "state" parameter to Strava's authorization page, the same "state" parameter will be part of the
    // query once the user is redirected back to your login page
    string? state = Context.Request.Query["state"];

    // Initialize a new OAuth client
    StravaOAuthClient client = new StravaOAuthClient {
        ClientId = "Your client ID",
        ClientSecret = "Your client secret",
        RedirectUri = "Your redirect URI"
    };

    if (Context.Request.Query["do"] == "login") {

        // Generate a random scope
        state = Guid.NewGuid().ToString();

        // Generate the session key for the state
        string stateKey = "StravaOAuthState_" + state;

        // Store the state in the session of the user
        Context.Session.SetString(stateKey, Context.Request.GetDisplayUrl());

        // Generate the authorization URL
        string authorizationUrl = client.GetAuthorizationUrl(state, StravaApprovalPrompt.Force, StravaScopes.ActivityReadAll); ;

        // Redirect the user
        Context.Response.Redirect(authorizationUrl);
        return;

    }

    if (!string.IsNullOrWhiteSpace(code)) {

        // Generate the session key for the state
        string stateKey = "StravaOAuthState_" + state;

        if (Context.Session.GetString(stateKey) == null) {
            <p>Has your session expired?</p>
            <p>
                <a class="btn btn-default" href="/strava/oauth?do=login">Re-try login</a>
            </p>
            return;
        }

        // Exchange the authorization code for an access token
        StravaTokenResponse response = await client.GetAccessTokenFromAuthCodeAsync(code);

        // Initialize a new service instance from an access token
        StravaHttpService strava = StravaHttpService.CreateFromAccessToken(response.Body.AccessToken);

        <div>Access Token:</div>
        <pre>@response.Body.AccessToken</pre>

        <div>Refresh Token:</div>
        <pre>@response.Body.RefreshToken</pre>

        <div>Expires in:</div>
        <pre>@response.Body.ExpiresIn</pre>

        <div>Token type:</div>
        <pre>@response.Body.TokenType</pre>

        return;

    }

}

<p>
    <a class="btn btn-default" href="/strava/oauth?do=login">Login with Strava</a>
</p>

Please be aware that the purpose of the example above is to illustrate the indidual steps involved setting up an authentication. Ideally you shouldn't declare the client ID and client secret directly in a Razor view or similar. Instead it's recommended to add this to your appsettings.json file or even better a secure vault.